Compare commits

..

657 Commits

Author SHA1 Message Date
Peter Steinberger
ea908ac594 fix(config): improve missing channel plugin diagnostics 2026-05-04 23:10:31 +01: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
Pavan Kumar Gondhi
ea75cd8971 Gate zalouser startup name matching [AI] (#77411)
* fix: gate zalouser startup name matching

* addressing codex review

* docs: add changelog entry for PR merge
2026-05-04 22:47:19 +05:30
Pavan Kumar Gondhi
37c0520a0b fix(device-pair): require pairing scope for pair command [AI] (#76377)
* fix: restrict device pairing command access

* addressing review-skill

* addressing review-skill

* addressing codex review

* address codex review feedback

* addressing codex review

* addressing codex review

* addressing codex review

* addressing codex review

* docs: add changelog entry for PR merge
2026-05-04 22:12:06 +05:30
Vincent Koc
30e259b9c5 test(qa-lab): accept native Windows paths 2026-05-04 09:20:03 -07:00
Vincent Koc
9008031e96 fix(qa-channel): settle aborted bus polls 2026-05-04 09:20:03 -07:00
Vincent Koc
6c2573e37a test(anthropic-vertex): accept native ADC home paths 2026-05-04 09:20:03 -07:00
Vincent Koc
2fe2dbdb7d test(openshell): accept native symlink targets 2026-05-04 09:20:02 -07:00
Vincent Koc
3d3b0dad77 test(whatsapp): accept native Windows auth paths 2026-05-04 09:20:02 -07:00
Vincent Koc
15b9966781 test(telegram): accept native Windows session file paths 2026-05-04 09:20:02 -07:00
Vincent Koc
0dd30c804c test(memory): cover native Windows paths and locks 2026-05-04 09:20:02 -07:00
Vincent Koc
fa1d826a41 test(matrix): cover native Windows file semantics 2026-05-04 09:20:02 -07:00
Vincent Koc
7c6bf331b8 test(feishu): accept native oversized body resets 2026-05-04 09:20:02 -07:00
Vincent Koc
4f2f5e0461 test(feishu): cover native Windows webhook and workspace paths 2026-05-04 09:20:02 -07:00
Vincent Koc
48a3a23d40 test(discord): accept native voice temp paths 2026-05-04 09:20:02 -07:00
Vincent Koc
40f92b8d78 test(diffs): use native viewer asset file URLs 2026-05-04 09:20:02 -07:00
Vincent Koc
981767516d test(bluebubbles): accept native contact database paths 2026-05-04 09:20:02 -07:00
Vincent Koc
03d04c243b test(acpx): cover Windows extension test paths 2026-05-04 09:20:02 -07:00
Michael Appel
c1da0ddd54 fix(security): block workspace env from overriding Windows system root paths [AI] (#74458)
* fix: address issue

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address codex review feedback

* fix: address codex review feedback

* changelog: PR #74458

---------

Co-authored-by: Devin Robison <drobison@nvidia.com>
2026-05-04 10:13:50 -06:00
zhang-guiping
1df2ac442a fix #77296: [Bug]: Plugin manifest skills field not published to agent skill discovery paths (#77328)
Summary:
- The PR publishes enabled plugin-declared skill directories into a generated `~/.openclaw/plugin-skills` syml ... plugin-skill precedence, cleans stale generated links, adds regression coverage, and updates the changelog.
- Reproducibility: yes. source-based. Current main resolves plugin-declared skill directories for prompt loadi ... ble generated discovery path, and the linked issue provides a concrete ENOENT path for a plugin `SKILL.md`.

Automerge notes:
- Ran the ClawSweeper repair loop before final review.
- Included post-review commit in the final squash: fix: resolve issue #77296
- Included post-review commit in the final squash: fix: publish plugin manifest skills for agent discovery
- Included post-review commit in the final squash: fix(clawsweeper): address review for automerge-openclaw-openclaw-7732…

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

Prepared head SHA: 0f52865ee3
Review: https://github.com/openclaw/openclaw/pull/77328#issuecomment-4371415857

Co-authored-by: zhang-guiping <zhang.guiping@xydigit.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
2026-05-04 15:31:53 +00:00
Eva
cb38535875 [plugin sdk] Project session extension slots (#75609)
Merged via squash.

Prepared head SHA: d9b670a867
Co-authored-by: 100yenadmin <239388517+100yenadmin@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-05-04 08:04:27 -07:00
Pavan Kumar Gondhi
e3364ae3bd fix(qqbot): keep private commands off framework surface [AI] (#77212)
* fix: keep private qqbot commands off framework surface

* addressing codex review

* docs: add changelog entry for PR merge
2026-05-04 20:23:51 +05:30
hcl
d5edeae6ee fix(memory): prevent memory-hit starvation in corpus=all by capping per-corpus results (#77337) (#77356)
Summary:
- The PR adds balanced, backfilled all-corpus result merging for `memory_search` and `wiki_search`, regression tests, and a changelog entry for #77337.
- Reproducibility: yes. Current main is source-reproducible: both affected paths fetch both corpora for `corpus=all`, raw-sort wiki integer scores against memory similarity scores, and slice to `maxResults`.

Automerge notes:
- Ran the ClawSweeper repair loop before final review.
- Included post-review commit in the final squash: fix(memory): prevent all-corpus memory hit starvation

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

Prepared head SHA: a5b4f6a932
Review: https://github.com/openclaw/openclaw/pull/77356#issuecomment-4371767658

Co-authored-by: HCL <chenglunhu@gmail.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
2026-05-04 14:49:14 +00:00
Kevin Lin
89db1e5440 feat(cron): surface run diagnostics in status (#75928)
* feat(cron): surface run diagnostics in status

* docs: add cron diagnostics changelog

* fix(cron): preserve latest run diagnostics

* test(cron): update diagnostics regression deps
2026-05-04 07:05:28 -07:00
Eva
8afc9ef73c [plugin sdk] Harden finalize retry and run context cleanup (#75600)
Merged via squash.

Prepared head SHA: ec58a6212b
Co-authored-by: 100yenadmin <239388517+100yenadmin@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-05-04 07:04:22 -07:00
Val Alexander
042d7b8823 fix(telegram): clean up tool-only previews 2026-05-04 08:55:41 -05:00
Dallin Romney
fc1f1f4fdf fix(tui): preserve code spans, code blocks, and dotted/hyphenated identifiers from long-token sanitizer (#77335)
The display sanitizer's long-token chunker (`\S{33,}` -> 32-char chunks
joined by spaces) was injecting literal spaces inside inline code spans,
fenced code blocks, and bare identifiers it didn't recognize. Tokens like
`requireConfirmationForMutatingActions`, `ubuntu-budgie-desktop-environment`,
and `binary_sensor.sense_energy_monitor_power` rendered with mid-word
spaces, contaminating copy/paste of package names, entity IDs, and shell
line-continuations.

Fix:

- Make sanitizer code-aware: split text into fenced/inline-code segments
  and prose, and only run the chunker on prose segments. Code regions
  pass through verbatim.
- Widen `isCopySensitiveToken` to use the punctuation-stripped candidate
  for all classification, and accept any `FILE_LIKE_RE` token that
  contains `_`, `-`, or `.` (covers package names, dotted IDs, kebab
  flags). Picks up the goals of #69340 and #39565.
- Skip chunking for symbol-only runs (box-drawing rows, dashes, equals)
  so table borders aren't corrupted.
- Preserve the original goal of narrow-terminal protection: long
  unidentifiable prose tokens (e.g. accidental base64 dumps) are still
  chunked so they don't blow out terminal layout.

Security ordering preserved: ANSI strip / control-char strip / binary
redaction still run on the whole string before segmentation, so code
regions cannot smuggle escapes, control characters, or binary garbage
past the sanitizer.

16 new regression tests cover: camelCase config keys in inline code,
hyphenated package names (bare and in code), dotted entity IDs (bare
and in code), backtick and tilde fenced blocks, base64-like blobs in
code, prose-token chunking unchanged, prose-around-code mixed content,
box-drawing horizontal rules, multi-line shell `\\` continuations,
plus three explicit security-ordering tests asserting ANSI/control/
binary stripping still runs inside code segments.

Fixes #48432, #39505.
Supersedes #69340, #39565 (carries forward both ideas in a more
general fix). Carries forward the code-fence-aware approach from the
closed #48445.
2026-05-04 21:50:40 +08:00
Josh Lehman
0b3a86cab0 docs(changelog): restore 2026.5.3 release notes 2026-05-04 06:49:27 -07:00
Dallin Romney
5f373ae4d3 fix(tui): abort run during pre-event waiting gap (#77199)
* fix(tui): abort run during pre-event waiting gap

Track the runId returned from chat.send so pressing Esc while `activeChatRunId` is still null aborts the in-flight run instead of repeatedly printing "no active run". Identified in #1296.

* fix(tui): drop redundant comment on pendingChatRunId set
2026-05-04 21:36:52 +08:00
Vincent Koc
a90be474f4 test: repair current main checks 2026-05-04 05:09:21 -07:00
github-actions[bot]
c59c20e9fd chore(ui): refresh fa control ui locale 2026-05-04 12:05:27 +00:00
github-actions[bot]
1ce136ce16 chore(ui): refresh nl control ui locale 2026-05-04 12:04:59 +00:00
github-actions[bot]
909894c8c4 chore(ui): refresh th control ui locale 2026-05-04 12:04:35 +00:00
github-actions[bot]
df7d18f6d3 chore(ui): refresh vi control ui locale 2026-05-04 12:04:28 +00:00
github-actions[bot]
2db259503b chore(ui): refresh pl control ui locale 2026-05-04 12:04:20 +00:00
github-actions[bot]
4abba333fe chore(ui): refresh id control ui locale 2026-05-04 12:03:58 +00:00
github-actions[bot]
0909ff16d9 chore(ui): refresh uk control ui locale 2026-05-04 12:03:22 +00:00
github-actions[bot]
87e3f3779f chore(ui): refresh it control ui locale 2026-05-04 12:03:17 +00:00
github-actions[bot]
863e8d0c38 chore(ui): refresh tr control ui locale 2026-05-04 12:03:13 +00:00
github-actions[bot]
ea8d5b1877 chore(ui): refresh ar control ui locale 2026-05-04 12:02:50 +00:00
github-actions[bot]
e069675c1d chore(ui): refresh fr control ui locale 2026-05-04 12:02:01 +00:00
github-actions[bot]
47b7df3c5d chore(ui): refresh ko control ui locale 2026-05-04 12:01:57 +00:00
github-actions[bot]
7c696e0e73 chore(ui): refresh ja-JP control ui locale 2026-05-04 12:01:53 +00:00
github-actions[bot]
510a2dc80c chore(ui): refresh es control ui locale 2026-05-04 12:01:49 +00:00
github-actions[bot]
ad534fdb1b chore(ui): refresh pt-BR control ui locale 2026-05-04 12:01:00 +00:00
github-actions[bot]
bd183072e4 chore(ui): refresh zh-TW control ui locale 2026-05-04 12:00:48 +00:00
github-actions[bot]
1d16ce3f24 chore(ui): refresh de control ui locale 2026-05-04 12:00:43 +00:00
github-actions[bot]
a68c6e20e9 chore(ui): refresh zh-CN control ui locale 2026-05-04 12:00:39 +00:00
Val Alexander
8469a51326 Control UI explicit action feedback
Add explicit Control UI feedback for repeated actions: session switches now announce through the chat controls live-status path and flash the active session selector, config actions show inline busy state, and session list empty states distinguish filtered results with a Show all reset. Also refresh generated Control UI locale metadata and fallback markers.
2026-05-04 06:58:31 -05:00
Peter Steinberger
14f756c05b test(release): leave Windows updater timeout headroom 2026-05-04 12:58:28 +01:00
Val Alexander
626e078863 fix: refresh stale codex auth profile routing
Summary:
- Promotes fresh Codex OAuth relogin profiles ahead of stale per-agent auth order entries.
- Repairs invalidated per-agent Codex order and session overrides toward healthy relogin profiles.
- Adds focused regression coverage for auth order, invalidated profile repair, and session override re-resolution.

Verification:
- pnpm test src/agents/auth-profiles/profiles.test.ts src/agents/auth-profiles.ensureauthprofilestore.test.ts src/agents/auth-profiles/session-override.test.ts src/commands/models/auth.test.ts -- --reporter=verbose
- pnpm exec oxfmt --check --threads=1 CHANGELOG.md src/agents/auth-profiles.ensureauthprofilestore.test.ts src/agents/auth-profiles/persisted.ts src/agents/auth-profiles/profiles.test.ts src/agents/auth-profiles/profiles.ts src/agents/auth-profiles/session-override.test.ts src/agents/auth-profiles/session-override.ts src/commands/models/auth.test.ts src/commands/models/auth.ts
- git diff --check origin/main...HEAD
- pnpm check:changed via Blacksmith Testbox tbx_01kqscwvkywnt72qx1t8a07tp8
- GitHub CI on 1a6f93a372, with checks-node-core-runtime-infra-state rerun passing after an unrelated stale-lock timing failure
2026-05-04 06:56:02 -05:00
Vincent Koc
a7c5a04259 test: stabilize full crabbox sweep 2026-05-04 04:07:33 -07:00
Jesse Merhi
d5b0083300 fix: proxy direct APNs HTTP2 sessions (#74905)
Summary:
- This PR routes direct APNs HTTP/2 sends through an APNs allowlisted managed-proxy CONNECT wrapper, adds APNs proxy validation/docs/guardrails, and expands regression and live-test coverage.
- Reproducibility: yes. source-reproducible: current main `sendApnsRequest()` still uses raw `http2.connect(au ... nly covers HTTP/global-agent/Undici hooks. I did not run a live APNs reproduction in this read-only review.

Automerge notes:
- PR branch already contained follow-up commit before automerge: test: guard raw HTTP2 APNs connections
- PR branch already contained follow-up commit before automerge: test: guard raw HTTP2 with OpenGrep
- PR branch already contained follow-up commit before automerge: lint: ban raw HTTP2 imports
- PR branch already contained follow-up commit before automerge: fix: use managed proxy state for APNs
- PR branch already contained follow-up commit before automerge: test: exercise APNs active proxy state
- PR branch already contained follow-up commit before automerge: fix: reject conflicting managed proxy activation

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

Prepared head SHA: dab7c86a75
Review: https://github.com/openclaw/openclaw/pull/74905#issuecomment-4350181159

Co-authored-by: jesse-merhi <79823012+jesse-merhi@users.noreply.github.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
2026-05-04 11:04:17 +00:00
Vincent Koc
5efbb3078a docs(changelog): credit recent plugin fixes 2026-05-04 03:48:05 -07:00
Peter Steinberger
a9f1882047 test: harden plugin and UI isolation checks 2026-05-04 11:46:05 +01:00
Vincent Koc
24ec2aebe8 test(agents): update model auth fixture shape 2026-05-04 03:40:36 -07:00
Vincent Koc
57f9a558e4 fix(types): wire plugin package metadata 2026-05-04 03:36:36 -07:00
Vincent Koc
97d35f4c57 fix(gateway): clarify systemd service scope 2026-05-04 03:33:49 -07:00
Vincent Koc
23eb44b045 feat(models): list auth profiles 2026-05-04 03:31:55 -07:00
Vincent Koc
e0430e2e15 fix(plugins): clean replaced managed installs 2026-05-04 03:28:53 -07:00
Vincent Koc
51d3ec7395 fix(plugins): recover source-only install shadows 2026-05-04 03:26:54 -07:00
Vincent Koc
4c40686f9e fix(plugins): trust official Codex package commands 2026-05-04 03:25:26 -07:00
Vincent Koc
89a15fddaf fix(plugins): ignore invalid managed runtime shadows 2026-05-04 03:17:57 -07:00
Vincent Koc
b8f6e16ba5 fix(update): order stable correction releases after base 2026-05-04 03:05:56 -07:00
Vincent Koc
feb9a5af6a fix(plugins): scope commands to channels 2026-05-04 03:01:56 -07:00
Peter Steinberger
3434cfa381 test: speed up import-heavy suites 2026-05-04 11:00:44 +01:00
Vincent Koc
54300e5270 fix(plugins): quiet official npm install scan warnings 2026-05-04 02:40:55 -07:00
Vincent Koc
33e19fb5ae fix(security): ignore scanner comment context 2026-05-04 02:35:43 -07:00
Vincent Koc
6b7f9eafed fix(doctor): drop stale bundled install records 2026-05-04 02:26:03 -07:00
Peter Steinberger
061af13bf3 fix: avoid plugin install scanner false positives 2026-05-04 10:24:32 +01:00
Pavan Kumar Gondhi
04aa4a3fe6 fix: harden backend message action gateway routing [AI] (#76374)
* fix: harden backend message action gateway routing

* docs: add changelog entry for PR merge
2026-05-04 14:53:52 +05:30
Pavan Kumar Gondhi
1f724bc50b Gate QQBot streaming command auth [AI] (#76375)
* fix: gate QQBot streaming command

* addressing codex review

* addressing review-skill

* addressing review-skill

* addressing codex review

* addressing claude review

* docs: add changelog entry for PR merge
2026-05-04 14:50:58 +05:30
Peter Steinberger
5d9752ba18 build(release): refresh base config schema 2026-05-04 10:19:59 +01:00
Vincent Koc
05d6c62152 fix(release): reject blank plugin runtime entries 2026-05-04 02:18:11 -07:00
Peter Steinberger
b7ce9439e7 fix: repair bundled plugin shadow cleanup 2026-05-04 10:17:50 +01:00
Vincent Koc
dade5f9133 fix(web-fetch): scope fallback cache by provider 2026-05-04 02:11:43 -07:00
Val Alexander
098b72910d Refine responsive Control UI chat controls
Summary:
- Add agent-scoped Control chat session filtering and agent-first session controls.
- Refine responsive chat controls, transcript, result-panel, and duplicate-message behavior.
- Reduce chat load churn by avoiding duplicate initial avatar refreshes.

Verification:
- pnpm test ui/src/ui/app-gateway.node.test.ts ui/src/ui/app-gateway-chat-load.node.test.ts ui/src/ui/chat/chat-responsive.browser.test.ts ui/src/ui/app-render.helpers.browser.test.ts ui/src/ui/app-render.helpers.node.test.ts ui/src/ui/views/chat.test.ts ui/src/ui/app-scroll.test.ts
- pnpm test src/plugin-sdk/file-lock.test.ts
- pnpm exec oxfmt --check --threads=1 ui/src/ui/chat/chat-responsive.browser.test.ts src/plugin-sdk/file-lock.test.ts
- pnpm --dir ui build
- Testbox pnpm check:changed: https://github.com/openclaw/openclaw/actions/runs/25309629891
- PR CI on cd22d3d1ab: https://github.com/openclaw/openclaw/actions/runs/25310534399
2026-05-04 04:10:33 -05:00
Peter Steinberger
5397667272 chore(release): prepare 2026.5.4 2026-05-04 10:09:55 +01:00
Peter Steinberger
b37fba7c07 ci(release): harden clawhub plugin publish 2026-05-04 10:09:55 +01:00
Peter Steinberger
5b528f4dfe docs: add plugin install example 2026-05-04 10:08:29 +01:00
Vincent Koc
304fa098f2 fix(web-search): honor late-bound disabled config 2026-05-04 02:01:06 -07:00
Vincent Koc
88b21427f8 fix(plugins): reject invalid inferred package runtimes 2026-05-04 01:55:50 -07:00
Vincent Koc
7482754aca fix(plugins): avoid duplicate native fallback loads 2026-05-04 01:49:36 -07:00
Peter Steinberger
474bea162b fix: bound trajectory runtime flush (#77154)
* fix: bound trajectory runtime flush

* fix: keep trajectory export cap compatible

* test: keep followup delivery test pure
2026-05-04 09:48:03 +01:00
Alex Knight
be41b8cbc7 test: stabilize gateway server shard (#77131) 2026-05-04 18:42:05 +10:00
Vincent Koc
a9282f3571 fix(plugins): reject blank runtime entries 2026-05-04 01:41:20 -07:00
Vincent Koc
23950b5664 test(agents): align slack target normalization assertion 2026-05-04 01:40:58 -07:00
Vincent Koc
9b95e477be test(e2e): run crestodian planner harness without tsx 2026-05-04 01:40:58 -07:00
Vincent Koc
baecb6b4d6 fix(plugin): preserve sdk alias fallback for native loads 2026-05-04 01:40:58 -07:00
Vincent Koc
6e8cdd7d59 test(plugin): harden source loader fallback tests 2026-05-04 01:40:58 -07:00
Vincent Koc
da1e1435ad fix(doctor): prune stale plugin lock entries 2026-05-04 01:33:21 -07:00
Vincent Koc
43bdb886e9 fix(plugin-state): preserve fresh evicted entries 2026-05-04 01:25:12 -07:00
Alex Knight
fcb396bf65 feat(plugin-state): add registerIfAbsent keyed store (#77135) 2026-05-04 18:20:04 +10:00
Vincent Koc
071db2ca69 fix(whatsapp): capture login outcome output 2026-05-04 01:18:52 -07:00
Val Alexander
a1304c92c6 Fix Control UI i18n tooltip placeholders
Summary:
- Render the Sessions active filter tooltip with the configured minute count instead of a literal N.
- Update all Control UI locale bundles and i18n translation memory rows to preserve the {count} placeholder.
- Add a placeholder parity guard to the Control UI i18n check with regression coverage.

Verification:
- pnpm ui:i18n:check
- pnpm test src/scripts/control-ui-i18n.test.ts ui/src/ui/views/sessions.test.ts
- git diff --check
- Testbox exact-head pnpm check:changed passed on prior rebased head 1333aac90b6094b9944298e7ff80e7d22614e9fd before latest main churn.
- GitHub CI on fd2068c378 only failed the pre-existing unrelated checks-node-core-fast timeout in src/auto-reply/reply/followup-delivery.test.ts:176, also present on recent main runs b31c001a2b and e5f5989aa9.
2026-05-04 03:18:34 -05:00
Peter Steinberger
281b5bd511 fix: repair stale managed plugin shadows 2026-05-04 09:17:04 +01:00
Vincent Koc
be21d64d08 fix(gateway): preserve canvas tls urls 2026-05-04 01:12:51 -07:00
Vincent Koc
f0537e93fb fix(ci): plan openwebui functional image 2026-05-04 01:10:50 -07:00
Peter Steinberger
9efbae7acd fix(whatsapp): route login qr through runtime 2026-05-04 09:07:42 +01:00
Peter Steinberger
03ad3c0684 fix(gateway): log canvas host mount after bind 2026-05-04 09:05:35 +01:00
Vincent Koc
ef79347763 fix(ui): retry errored talk sessions 2026-05-04 01:05:10 -07:00
Vincent Koc
e5f5989aa9 fix(ui): stop stale talk sessions 2026-05-04 00:54:51 -07:00
Vincent Koc
b31c001a2b fix(googlechat): isolate auth transports 2026-05-04 00:48:28 -07:00
Val Alexander
e622223bcd feat(control-ui): collapse cron new job panel
Add a collapsible Control UI cron New Job panel so operators can reclaim list space while keeping create/edit one click away.

Verification:
- pnpm exec oxfmt --check --threads=1 CHANGELOG.md ui/src/styles/components.css ui/src/ui/controllers/cron.ts ui/src/ui/controllers/cron.test.ts ui/src/ui/views/cron.ts ui/src/ui/views/cron.test.ts ui/src/ui/app.ts ui/src/ui/app-render.ts ui/src/ui/app-view-state.ts
- pnpm test ui/src/ui/views/cron.test.ts ui/src/ui/controllers/cron.test.ts
- Browser preview at http://localhost:5173/cron
- Testbox check:changed passed guard/type lanes; lint:core hit unrelated existing origin/main sessionsShowArchived Boolean findings.
2026-05-04 02:46:48 -05:00
Vincent Koc
e8d0cf75ea test(ui): remove duplicate archived fixture key 2026-05-04 00:41:18 -07:00
Vincent Koc
87e3b1a241 fix(ui): clean archived session state reads 2026-05-04 00:41:18 -07:00
Vincent Koc
f2e7f33d69 fix(ui): cap responsiveness event logs 2026-05-04 00:41:18 -07:00
Val Alexander
cf03fe6b6a fix(control-ui): contain access settings fields (#77171)
* fix(control-ui): contain access settings fields

* docs: update changelog for access overflow fix

* fix(control-ui): preserve archived session defaults
2026-05-04 02:40:52 -05:00
Peter Steinberger
e524878998 fix(googlechat): normalize auth response headers 2026-05-04 08:40:28 +01:00
Peter Steinberger
7129db1960 perf: lighten gateway watch startup 2026-05-04 08:36:50 +01:00
Peter Steinberger
e11a8a84ac fix(control-ui): dismiss talk startup errors 2026-05-04 08:32:06 +01:00
Peter Steinberger
585ce38015 fix(telegram): stabilize topic dispatch runtime 2026-05-04 08:25:09 +01:00
Vincent Koc
48e1256810 fix(ci): build live image for openwebui lanes 2026-05-04 00:23:54 -07:00
Peter Steinberger
0f7cd6d905 perf: overlap gateway watch startup 2026-05-04 08:22:33 +01:00
github-actions[bot]
2484f37378 chore(ui): refresh fa control ui locale 2026-05-04 07:19:07 +00:00
github-actions[bot]
f8d7182f81 chore(ui): refresh nl control ui locale 2026-05-04 07:19:01 +00:00
github-actions[bot]
e23f3a859c chore(ui): refresh vi control ui locale 2026-05-04 07:18:15 +00:00
github-actions[bot]
573ecd8660 chore(ui): refresh th control ui locale 2026-05-04 07:18:03 +00:00
github-actions[bot]
17c05bbb21 chore(ui): refresh pl control ui locale 2026-05-04 07:17:55 +00:00
github-actions[bot]
b171f6e081 chore(ui): refresh id control ui locale 2026-05-04 07:17:50 +00:00
github-actions[bot]
92a00ebef5 chore(ui): refresh uk control ui locale 2026-05-04 07:17:00 +00:00
github-actions[bot]
54f243d696 chore(ui): refresh tr control ui locale 2026-05-04 07:16:52 +00:00
github-actions[bot]
49ab43477e chore(ui): refresh it control ui locale 2026-05-04 07:16:45 +00:00
github-actions[bot]
42abef0afb chore(ui): refresh ar control ui locale 2026-05-04 07:16:39 +00:00
github-actions[bot]
85e9af7767 chore(ui): refresh ko control ui locale 2026-05-04 07:15:44 +00:00
github-actions[bot]
dbde49f44e chore(ui): refresh fr control ui locale 2026-05-04 07:15:39 +00:00
github-actions[bot]
7c38f0997f chore(ui): refresh ja-JP control ui locale 2026-05-04 07:15:35 +00:00
github-actions[bot]
1cbe32ef23 chore(ui): refresh es control ui locale 2026-05-04 07:15:26 +00:00
github-actions[bot]
1621d9f27d chore(ui): refresh pt-BR control ui locale 2026-05-04 07:14:27 +00:00
github-actions[bot]
6e9c0bfbe4 chore(ui): refresh zh-TW control ui locale 2026-05-04 07:14:23 +00:00
github-actions[bot]
0dd1b11e83 chore(ui): refresh zh-CN control ui locale 2026-05-04 07:14:21 +00:00
github-actions[bot]
f409b093fd chore(ui): refresh de control ui locale 2026-05-04 07:14:19 +00:00
Peter Steinberger
c36f8f1e39 fix(deepseek): expose v4 thinking profile in policy surface 2026-05-04 08:13:16 +01:00
Val Alexander
a5dcf3d300 fix(control-ui): filter archived sessions (#77132)
Summary:
- Use sessions.list as the Control UI source of truth for available sessions.
- Hide archived sessions by default and keep the Sessions filter UI explicit, compact, and reversible.
- Preserve session-change behavior, checkpoint details, generated i18n output, and chat/session picker consistency.

Validation:
- pnpm ui:i18n:check
- pnpm test ui/src/styles/components.test.ts ui/src/ui/views/sessions.test.ts ui/src/ui/app-render.helpers.node.test.ts
- pnpm test ui/src/ui/controllers/sessions.test.ts ui/src/ui/app-gateway.sessions.node.test.ts ui/src/ui/views/sessions.test.ts ui/src/styles/components.test.ts ui/src/ui/app-render.helpers.node.test.ts
- pnpm tsgo:test:ui
- Blacksmith Testbox: pnpm check:changed -- <PR paths>
2026-05-04 02:12:16 -05:00
Val Alexander
b2efd19648 fix(ios): harden gateway pairing setup
Harden iOS gateway setup-code pairing by rejecting non-loopback plaintext ws:// setup URLs before bootstrap token issuance, consolidating iOS setup parsing, and adding QR scan support from Settings.

Verification:
- pnpm test extensions/device-pair/index.test.ts
- swift test --package-path apps/shared/OpenClawKit --filter DeepLinksSecurityTests
- XcodeBuildMCP OpenClawLogicTests/DeepLinkParserTests
- targeted SwiftLint for touched iOS/OpenClawKit files
- pnpm exec oxfmt --check --threads=1 extensions/device-pair/index.ts extensions/device-pair/index.test.ts
- git diff --check origin/main...HEAD
- GitHub PR checks green on 58e5e60a5c
2026-05-04 02:11:47 -05:00
Val Alexander
5fe8cde28f feat(ui): show active agent in dashboard header
Show the active agent name in the Control UI dashboard breadcrumb without adding the current session key/name.

Verification:
- pnpm test ui/src/ui/app-render.helpers.node.test.ts
- node scripts/run-oxlint.mjs --tsconfig config/tsconfig/oxlint.core.json ui/src/ui/components/dashboard-header.ts ui/src/ui/app-render.helpers.ts ui/src/ui/app-render.ts ui/src/ui/app-render.helpers.node.test.ts
- git diff --check
- Testbox pnpm check:changed
2026-05-04 02:09:52 -05:00
Vincent Koc
3c971255fa fix(auth): quiet codex oauth manual fallback 2026-05-04 00:07:13 -07:00
Peter Steinberger
826786b114 feat: add control UI responsiveness diagnostics 2026-05-04 08:04:18 +01:00
Vincent Koc
fbf9132b32 fix(web-fetch): late-bind runtime config 2026-05-04 00:02:52 -07:00
Dallin Romney
cdc00614cc fix(plugins): warn on source-only installed packages instead of blocking config 2026-05-03 23:59:20 -07:00
Vincent Koc
f4f98f45c7 fix(gateway): cancel post-ready maintenance on close
Fixes the post-ready maintenance shutdown race by marking close before gateway_stop hooks, clearing delayed timers, and suppressing already-fired maintenance work during shutdown.

Verification:
- pnpm test:serial src/gateway/server-runtime-services.test.ts src/gateway/server-import-boundary.test.ts
- pnpm exec oxfmt --check --threads=1 src/gateway/server.impl.ts src/gateway/server-import-boundary.test.ts src/gateway/server-runtime-services.ts src/gateway/server-runtime-services.test.ts
- git diff --check
- crabbox blacksmith-testbox tbx_01kqrw87d527jwcfxbp6qk1wc3: pnpm check:changed (exit 0)
2026-05-03 23:56:56 -07:00
Vincent Koc
8a8a12559d fix(discord): clear failed startup probe status 2026-05-03 23:54:58 -07:00
Vincent Koc
3f045d9129 fix(web-search): scope explicit provider runtime loading 2026-05-03 23:47:35 -07:00
Val Alexander
80acedaf0a docs(changelog): credit gateway install fix
Summary:
- Credit @BunsDev on the unreleased gateway install changelog entry.
- Keep the already-landed managed-service Node selection fix associated with #76339.

Verification:
- PR checks passed for head d9865b1b0c: preflight, check-docs, actionlint, no-tabs, security-scm-fast, security-dependency-audit, security-fast.
- Local scoped checks before force-update: pnpm check:changelog-attributions, git diff --check, pnpm exec oxfmt --check --threads=1 CHANGELOG.md.

Fixes #76339.
2026-05-04 01:45:31 -05:00
Peter Steinberger
31bba9ea22 docs(agents): require PR descriptions 2026-05-04 07:44:34 +01:00
Peter Steinberger
128cc2c84b refactor(web-tools): share runtime provider context 2026-05-04 07:44:34 +01:00
Peter Steinberger
605e89468e fix(discord): avoid blocking startup on probe (#77129)
* fix(discord): avoid blocking startup on probe

* fix(discord): clear degraded probe status

* test(plugin-sdk): isolate jiti loader override

* test(plugin-sdk): fix circular facade fixture path

* fix(plugins): preserve sdk aliases for native loads

* fix(plugins): route sdk alias loads through transform
2026-05-04 07:41:42 +01:00
Peter Steinberger
fa689295c6 fix: resolve small triage issues 2026-05-04 07:38:42 +01:00
Peter Steinberger
deffd11a43 fix: fork google meet agent context 2026-05-04 07:36:09 +01:00
Vincent Koc
f29aaa2e04 fix(release): resolve beta smoke workflow run 2026-05-03 23:35:04 -07:00
Peter Steinberger
86fc9e3279 perf: trim gateway startup plugin imports 2026-05-04 07:32:37 +01:00
Vincent Koc
3dcff3b267 fix(media): require HEIC conversion fallback 2026-05-03 23:30:38 -07:00
Peter Steinberger
d8da04e58e chore: improve beta smoke release tooling 2026-05-04 07:28:57 +01:00
Zander
8412b189df ui(chat): remove unsupported line-clamp declaration
Remove the unsupported unprefixed line-clamp CSS declaration from the chat queue text rule while keeping the existing -webkit-line-clamp truncation behavior.\n\nValidation:\n- git diff --check origin/main...HEAD\n- pnpm exec oxfmt --check --threads=1 CHANGELOG.md ui/src/styles/components.css\n- pnpm check:changelog-attributions\n- Testbox: OPENCLAW_TESTBOX=1 pnpm check:changed\n\nCI note: exact-SHA CI failed in unrelated plugin loader/plugin SDK jobs outside this PR's touched files.
2026-05-04 01:28:14 -05:00
Vincent Koc
92d33e4de8 fix(agents): sanitize presentation reasoning 2026-05-03 23:20:22 -07:00
Joey Krug
bbdf1fe11c docs(changelog): credit brave web search fix 2026-05-04 07:18:10 +01:00
Joey Krug
ab24e93573 fix(web-search): preserve runtime auto-detect fallback 2026-05-04 07:18:10 +01:00
Joey Krug
c76d8f5a7c fix(web-search): keep first-class web_search runtime providers visible
When createWebSearchTool is wired with lateBindRuntimeConfig: true, the
first-class assistant tool now lives off whatever runtime is active at
execute time. That works in the gateway process where runtime metadata
and the active secrets snapshot are populated, but in agent contexts that
do not share that in-process state, both fall through to undefined and
the tool returned "web_search is disabled or no provider is available"
even though `openclaw capability web search` and direct provider runtime
execution succeeded.

Two fixes:

- src/agents/tools/web-search.ts: when late-binding, fall back to
  options.runtimeWebSearch when the active runtime web tools metadata is
  null, and fall back to options.config when getActiveSecretsRuntimeSnapshot
  is null. Derive a configured provider id from
  config.tools.web.search.provider and use it together with the runtime
  selection when deciding preferRuntimeProviders, so an explicit Brave/
  Perplexity selection still discovers the configured plugin even when
  no runtime provider id is bound.
- src/plugins/web-provider-runtime-shared.ts: the active gateway plugin
  registry may be otherwise compatible with the active config while
  contributing zero web providers (channels, memory, harnesses, and
  sidecars without Brave/web). Treating that empty active registry as
  authoritative meant first-class tools resolved to "no provider".
  Fall through to the scoped provider plugin load when the active
  registry returns no providers. Explicit `onlyPluginIds: []` still
  short-circuits to [] to preserve the empty-scope contract.

Adds regression tests for both seams.
2026-05-04 07:18:10 +01:00
Peter Steinberger
70850d15ee docs: document google meet elevenlabs voice setup 2026-05-04 07:16:48 +01:00
Peter Steinberger
02f455fda3 perf: reduce gateway startup import graph 2026-05-04 07:15:38 +01:00
Vincent Koc
51e847fb96 fix(telegram): preserve safe progress previews 2026-05-03 23:10:08 -07:00
Vincent Koc
0907c60dd7 fix(plugins): preserve native loader errors 2026-05-03 23:09:12 -07:00
Vincent Koc
3c4f67141d docs(changelog): credit @davemorin for TUI stale-response notice fix
#77120 added user-facing TUI copy that replaces the stale-response
watchdog notice with plain language, but the entry landed without
contributor attribution. Add the merging PR ref and credit the human
contributor @davemorin per CLAUDE.md changelog-attribution rules.
2026-05-03 23:08:47 -07:00
Val Alexander
21ac476904 fix(telegram): stabilize reply dispatch runtime
Summary:
- Add a stable provider-dispatcher dist entry and legacy alias coverage for stale reply-dispatch chunks.
- Make Telegram reasoning stream previews transient after final delivery and harden visible-send reasoning sanitization.
- Document transient /reasoning stream behavior and credit @BunsDev in the changelog.

Verification:
- pnpm test src/agents/tools/message-tool.test.ts src/infra/tsdown-config.test.ts test/scripts/runtime-postbuild.test.ts extensions/telegram/src/bot-message-dispatch.test.ts src/plugin-sdk/channel-streaming.test.ts src/plugin-sdk/channel-entry-contract.test.ts
- OPENCLAW_VITEST_MAX_WORKERS=1 pnpm test src/channels/plugins/module-loader.test.ts src/plugin-sdk/channel-entry-contract.test.ts
- pnpm exec oxfmt --check --threads=1 <changed files>
- git diff --check
- pnpm build
- GitHub PR checks for b8b7a91834
2026-05-04 01:07:57 -05:00
Vincent Koc
7050af56d4 fix(voice-call): bound realtime audio pacing 2026-05-03 22:58:28 -07:00
Paul Frederiksen
83037720d9 test: force channel loader jiti fallback path 2026-05-04 06:56:35 +01:00
Peter Steinberger
eeff1f7cb6 test: satisfy jiti mock type contracts 2026-05-04 06:56:35 +01:00
Peter Steinberger
ea04e019ac test: restore jiti override seams for loader tests 2026-05-04 06:56:35 +01:00
Peter Steinberger
38d6b43792 docs: add media fallback changelog (#77117) (thanks @pfrederiksen) 2026-05-04 06:56:35 +01:00
Paul Frederiksen
ac09ec00e8 fix(media): tolerate missing image optimizer for in-limit images 2026-05-04 06:56:35 +01:00
Vincent Koc
361737d1f1 fix(tts): honor telephony voice overrides 2026-05-03 22:52:18 -07:00
Peter Steinberger
a224810a7f fix(gateway): bound sessions list responses
Bound default Gateway sessions.list responses to 100 rows when callers omit limit, with response metadata for totalCount, limitApplied, and hasMore.\n\nFixes #77062.
2026-05-04 06:51:56 +01:00
Dave Morin
1df6226d90 TUI: simplify stale response notice (#77120) 2026-05-03 22:50:24 -07:00
Peter Steinberger
a9d77b3eb0 fix: scope Control UI assistant media tickets 2026-05-04 06:49:28 +01:00
Peter Steinberger
bc0b54e844 fix: keep gateway shutdown runtime stable across updates 2026-05-04 06:46:45 +01:00
Vincent Koc
4c68bfdb6c ci(release): filter QA live lanes 2026-05-03 22:44:59 -07:00
Vincent Koc
b6f9b5f21e fix(agents): keep grouped subagent completions 2026-05-03 22:41:34 -07:00
Peter Steinberger
cbd91676ac fix: log google meet agent tts backend 2026-05-04 06:41:22 +01:00
Vincent Koc
47134d1ce6 Merge branch 'main' of https://github.com/openclaw/openclaw
* 'main' of https://github.com/openclaw/openclaw:
  docs(changelog): credit subagent announce fix
  fix(slack): keep newest rich progress lines
  fix(agents): preserve full subagent announce output
  ci: preserve Windows Testbox phone-home POST
  fix(agents): suppress mid-turn continuation prompts
2026-05-03 22:38:35 -07:00
Vincent Koc
5ab18100e2 docs(changelog): credit subagent announce fix 2026-05-03 22:37:16 -07:00
Vincent Koc
ccb94a6282 fix(slack): keep newest rich progress lines 2026-05-03 22:33:43 -07:00
Vincent Koc
e80de466e5 fix(agents): preserve full subagent announce output
* fix(agents): preserve full subagent announce output

* fix(agents): tighten subagent prefix fallback

* fix(agents): broaden subagent prefix fallback
2026-05-03 22:33:00 -07:00
Vincent Koc
8f75a4ebdf ci: preserve Windows Testbox phone-home POST 2026-05-03 22:29:29 -07:00
Vincent Koc
36bab71abc ci: preserve Windows Testbox phone-home POST 2026-05-03 22:28:30 -07:00
Vincent Koc
1d935cce51 fix(agents): suppress mid-turn continuation prompts 2026-05-03 22:25:51 -07:00
Vincent Koc
5a6cedc14a ci: follow Windows Blacksmith phone-home redirects 2026-05-03 22:24:20 -07:00
Vincent Koc
e2f4aa4617 fix(exec): detect combined env split carriers 2026-05-03 22:17:08 -07:00
Vincent Koc
18db16471b ci: keep Windows Blacksmith testbox ready 2026-05-03 22:13:06 -07:00
Vincent Koc
20ade148be fix(voice-call): end realtime completed calls 2026-05-03 22:12:08 -07:00
Peter Steinberger
66267b5435 docs: clarify Pi transcript ownership 2026-05-04 06:10:33 +01:00
Peter Steinberger
705bde4594 perf(gateway): avoid jiti on native plugin loads 2026-05-04 06:07:41 +01:00
Vincent Koc
3d0563dee2 ci: support Windows Blacksmith testbox phone-home 2026-05-03 22:05:42 -07:00
Peter Steinberger
a6d67ccf29 fix: log google meet realtime models 2026-05-04 06:03:53 +01:00
Vincent Koc
1bf824f586 fix(exec): detect exec carrier risks 2026-05-03 22:02:18 -07:00
Peter Steinberger
dcb3e64e2f docs: reference stale replay fix PR 2026-05-04 06:00:01 +01:00
Peter Steinberger
0fcf2c64c0 fix: prevent persisted turn replay 2026-05-04 06:00:01 +01:00
Vincent Koc
7be29b2801 ci: add Windows Blacksmith testbox 2026-05-03 21:58:33 -07:00
Vincent Koc
f2d9b2c493 fix(tests): restore progress draft changed gate 2026-05-03 21:53:22 -07:00
Peter Steinberger
1360cec546 fix: narrow diagnostic session tail 2026-05-04 05:53:03 +01:00
Peter Steinberger
117364e2b9 fix: unwrap env path carrier commands 2026-05-04 05:53:03 +01:00
Peter Steinberger
cf1991d27d fix: harden sudo command carrier parsing 2026-05-04 05:53:03 +01:00
Peter Steinberger
7d26fb32a7 fix: preserve sudo shell carrier commands 2026-05-04 05:53:03 +01:00
Peter Steinberger
809f5ae150 refactor: share carrier command parsing 2026-05-04 05:53:03 +01:00
Peter Steinberger
5eac4686aa fix: preserve env split-string payloads 2026-05-04 05:53:03 +01:00
Peter Steinberger
1a573d33bc fix: parse attached carrier option values 2026-05-04 05:53:03 +01:00
Val Alexander
d60eef3b74 docs: add Crabbox maintainer instructions
Summary:
- Add maintainer-facing Crabbox instructions to the CI docs.
- Document blacksmith-testbox as the normal backend plus focused rerun, full suite, cleanup, reuse, direct Blacksmith fallback, and owned AWS fallback commands.

Verification:
- pnpm check:docs
- git diff --check origin/main...HEAD
- PR exact-head CI: check-docs, security, workflow sanity, preflight, labels passed; broad lanes skipped as docs-only.
2026-05-03 23:52:53 -05:00
Peter Steinberger
dd83f72a7f fix(cron): keep pre-transcript rows non-resumable
Refs #77011.
2026-05-04 05:45:27 +01:00
Peter Steinberger
8d6db59cf7 docs: preserve voice parity changelog highlight (#77064) 2026-05-04 05:42:59 +01:00
Peter Steinberger
7d98e7f1fe docs: document realtime voice parity (#77064) 2026-05-04 05:42:59 +01:00
scoootscooob
b2f2185348 fix(google-meet): keep realtime Twilio joins alive 2026-05-04 05:42:59 +01:00
scoootscooob
0c1df35315 fix(voice-call): scope call control gateway methods 2026-05-04 05:42:59 +01:00
scoootscooob
309ff6bada perf(voice-call): trim realtime audio copies 2026-05-04 05:42:59 +01:00
scoootscooob
7fc9a82dca fix(voice-call): pace realtime Twilio audio 2026-05-04 05:42:59 +01:00
Vincent Koc
19f948af2e fix(plugins): gate package test api aliases 2026-05-03 21:40:32 -07:00
Peter Steinberger
796d4ab43d fix: wait for meet microphone readiness 2026-05-04 05:39:47 +01:00
Peter Steinberger
65f2c2a0db fix: enrich stalled session recovery logs 2026-05-04 05:39:47 +01:00
Peter Steinberger
b5d408cd69 feat: add rich Slack progress drafts 2026-05-04 05:38:56 +01:00
Vincent Koc
654b70dde8 fix(gateway): keep cron startup after maintenance failure 2026-05-03 21:33:07 -07:00
Vincent Koc
32b4d1ec8a ci: expand Windows WSL probe runners 2026-05-03 21:32:35 -07:00
Vincent Koc
dadf0005ec fix(plugins): alias bundled public surfaces in source loaders 2026-05-03 21:29:55 -07:00
Vincent Koc
b2f0f67e0d ci: support Ubuntu import in Windows WSL probe 2026-05-03 21:28:22 -07:00
Peter Steinberger
472763238d test: remove unused gateway startup shim 2026-05-04 05:27:46 +01:00
Vincent Koc
71c7232764 fix(agents): keep current status on run session 2026-05-03 21:25:59 -07:00
Peter Steinberger
2949171fcc perf: reduce gateway startup readiness latency 2026-05-04 05:20:39 +01:00
Vincent Koc
e9ca63cf06 fix(codex): preserve run session status key 2026-05-03 21:20:03 -07:00
Vincent Koc
90d25d59c6 ci: keep Windows probe alive after WSL check 2026-05-03 21:19:48 -07:00
Vincent Koc
9dc3271efb ci: add Windows testbox probe 2026-05-03 21:17:49 -07:00
Vincent Koc
09e7eb6687 fix(plugins): preserve optional tool metadata 2026-05-03 21:06:46 -07:00
Alex Knight
3f732aee83 fix: session_status 'current' resolves to live run session instead of stale sandbox key (#76708) (#76995)
Summary:
- The PR threads a live `runSessionKey` through embedded tool construction, updates `session_status({sessionKey:"current"})` resolution, and adds unit, Telegram QA, workflow, and changelog coverage for #76708.
- Reproducibility: yes. Source inspection shows current main gives `session_status` only the sandbox/requester ... plus PR follow-up describe a focused Telegram Docker scenario that fails pre-fix and passes with this head.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix: preserve session visibility semantics for runSessionKey (#76708)
- PR branch already contained follow-up commit before automerge: fix: cover Telegram current session status

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

Prepared head SHA: c3c964ecfd
Review: https://github.com/openclaw/openclaw/pull/76995#issuecomment-4367445187

Co-authored-by: Alex Knight <aknight@atlassian.com>
Co-authored-by: Alex Knight <15041791+amknight@users.noreply.github.com>
2026-05-04 04:04:43 +00:00
Vincent Koc
02b9dbde39 fix(matrix): scope progress tool status config 2026-05-03 20:57:16 -07:00
Vincent Koc
12af95a55e Merge branch 'main' of https://github.com/openclaw/openclaw
* 'main' of https://github.com/openclaw/openclaw:
  fix: guard debug proxy CONNECT under managed proxy (#77010)
2026-05-03 20:54:48 -07:00
Jesse Merhi
f42a2c738c fix: guard debug proxy CONNECT under managed proxy (#77010)
Summary:
- The PR adds a managed-proxy-aware debug proxy direct-upstream guard, a diagnostics override env var, regression tests, docs, and a changelog entry.
- Reproducibility: yes. Source inspection on current main shows direct HTTP forwarding and CONNECT net.connect() can run while managed proxy mode is active, against the documented managed-proxy egress guardrail.

Automerge notes:
- Ran the ClawSweeper repair loop before final review.
- Included post-review commit in the final squash: fix(clawsweeper): address review for automerge-openclaw-openclaw-7701…

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

Prepared head SHA: aaa52a7f5f
Review: https://github.com/openclaw/openclaw/pull/77010#issuecomment-4367600656

Co-authored-by: jesse-merhi <79823012+jesse-merhi@users.noreply.github.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
2026-05-04 03:54:18 +00:00
Vincent Koc
5ca0aa1d15 fix(plugins): accept stable correction releases 2026-05-03 20:53:23 -07:00
Vincent Koc
973e240bb3 fix(channels): scope progress tool status config 2026-05-03 20:51:21 -07:00
Vincent Koc
e3cba91ef0 fix(plugins): respect manifest optional tool siblings 2026-05-03 20:44:18 -07:00
Peter Steinberger
a8b38bb742 test: improve Parallels beta validation 2026-05-04 04:43:24 +01:00
Vincent Koc
616a4e9782 fix(plugins): restore preferred clawhub installs 2026-05-03 20:34:12 -07:00
Vincent Koc
fe107d5256 fix(google-meet): require voice call setup entry 2026-05-03 20:28:17 -07:00
Peter Steinberger
484195d14e fix(media): ignore EPERM during best-effort fsync 2026-05-04 04:26:19 +01:00
Peter Steinberger
cf40284544 fix(mcp): expose channel payloads in primary content 2026-05-04 04:26:19 +01:00
Peter Steinberger
a4df85e55f fix(ui): render text-block tool results 2026-05-04 04:26:19 +01:00
Peter Steinberger
6f8b9bb573 fix(ui): render dream diary markdown 2026-05-04 04:26:19 +01:00
Peter Steinberger
143db94701 fix(ui): update tweakcn appearance link 2026-05-04 04:26:19 +01:00
Vincent Koc
a90bc434dd fix(google-meet): preserve realtime provider fallback 2026-05-03 20:22:23 -07:00
Vincent Koc
c52b5657a2 fix(google-meet): expose voice call delay schema 2026-05-03 20:16:50 -07:00
Vincent Koc
9cc802241c fix(qa): accept testbox smoke lease ids 2026-05-03 20:10:38 -07:00
Peter Steinberger
11c600cf19 fix: split google meet realtime providers 2026-05-04 04:07:41 +01:00
Vincent Koc
51fea3826a fix(qa): return slack smoke failure screenshot 2026-05-03 20:05:01 -07:00
Vincent Koc
eb3922f1a5 test(qa): avoid spread in slack smoke lint 2026-05-03 19:59:47 -07:00
Vincent Koc
8846fe0998 fix(channels): balance compact progress markdown 2026-05-03 19:59:47 -07:00
Vincent Koc
bc924889be fix(test): keep Open WebUI live lane image-free 2026-05-03 19:56:16 -07:00
Vincent Koc
0b6db06d7d fix(test): skip Open WebUI in no-live Docker plans 2026-05-03 19:54:15 -07:00
Vincent Koc
ac00d7882a fix(plugins): clean resolved npm load paths 2026-05-03 19:51:09 -07:00
Peter Steinberger
a3c36a0931 fix: compact progress draft lines 2026-05-04 03:50:19 +01:00
Peter Steinberger
f632f5e60b feat(qa): add mantis Slack desktop smoke 2026-05-04 03:47:27 +01:00
Vincent Koc
471489159b fix(mcp): honor plugin tool policy 2026-05-03 19:41:03 -07:00
Peter Steinberger
c956946b26 fix(google-meet): clamp audio buffer config 2026-05-04 03:38:32 +01:00
Vincent Koc
571d75aab3 fix(plugins): honor plugin tool denylists 2026-05-03 19:33:00 -07:00
Vincent Koc
eeed33e61e fix(doctor): keep plugin allowlists passive 2026-05-03 19:23:03 -07:00
Peter Steinberger
30b201eff0 fix(google-meet): hide realtime alias from agent schema 2026-05-04 03:22:08 +01:00
Vincent Koc
b0b5983ce3 fix(telegram): send interactive fallback replies 2026-05-03 19:17:14 -07:00
Vincent Koc
be438cf887 fix(mattermost): suppress draft progress chatter 2026-05-03 19:11:23 -07:00
openperf
7e296aef4b fix(cli-runner): drop stale claude-cli sessionId when transcript missing (#77011)
Probe ~/.claude/projects/.../<sid>.jsonl in prepareCliRunContext before
emitting `claude --resume <sid>`. When the on-disk transcript no longer
exists (e.g. after a half-installed update.run, manual prune, or Claude
CLI reinstall), drop the saved cliSessionBinding so this turn starts a
fresh session instead of timing out on a dead resume target. The post-run
session-store flow then writes the new sessionId back, ending the loop.
2026-05-04 03:09:08 +01:00
Vincent Koc
708c7cd2e2 fix(channels): align preview tool progress help 2026-05-03 19:06:28 -07:00
Vincent Koc
50da306c0a fix(telemetry): bound message diagnostics labels 2026-05-03 19:02:58 -07:00
Vincent Koc
111df161df fix(feishu): share streaming tool progress labels 2026-05-03 18:58:35 -07:00
Vincent Koc
1fe2b8b548 test(codex): sync app-server model auth mock 2026-05-03 18:56:03 -07:00
Vincent Koc
7b29fc36c3 test(whatsapp): sync auto-reply runtime mock 2026-05-03 18:54:36 -07:00
Peter Steinberger
18bd7b60e4 fix(gateway): cache session list thinking enrichment 2026-05-04 02:53:31 +01:00
Peter Steinberger
36f8a8603d fix(agents): gate optional media factories by tool policy 2026-05-04 02:53:31 +01:00
Peter Steinberger
5be66ca648 fix(agents): avoid secrets snapshot clones in plugin tool prep 2026-05-04 02:53:31 +01:00
Peter Steinberger
45cfe1dfa1 feat(google-meet): default talk-back to agent mode 2026-05-04 02:53:02 +01:00
Vincent Koc
1c2eda206e fix(matrix): bind approval reactions before option emoji 2026-05-03 18:52:01 -07:00
Vincent Koc
90c0edcb61 fix(mattermost): share progress draft labels 2026-05-03 18:48:16 -07:00
Kelaw - Keshav's Agent
56b83230df docs: note telegram interactive button fix 2026-05-04 02:47:03 +01:00
Kelaw - Keshav's Agent
01a22d4ec9 fix(telegram): render interactive reply buttons 2026-05-04 02:47:03 +01:00
Vincent Koc
c979ed3a3a fix(channels): pass raw progress detail to drafts 2026-05-03 18:43:11 -07:00
Vincent Koc
0659c58df8 test(qa): keep Matrix approval artifacts typed 2026-05-03 18:34:34 -07:00
Vincent Koc
fcfb6500da test(qa): resolve Matrix target-both approvals via gateway 2026-05-03 18:34:34 -07:00
Vincent Koc
df39e611f8 fix(channels): quiet disabled preview tool progress 2026-05-03 18:33:09 -07:00
Peter Steinberger
828d071ada test: fix whatsapp reply delivery mocks 2026-05-04 02:31:21 +01:00
Peter Steinberger
57c37ef933 fix(doctor): respect channel owner plugin repairs 2026-05-04 02:25:55 +01:00
Vincent Koc
eb1a0aa574 fix(codex): honor app-server auth order 2026-05-03 18:25:19 -07:00
Peter Steinberger
3a8ea14fe3 ci(qa): fix Crabbox desktop flag guard 2026-05-04 02:25:02 +01:00
Peter Steinberger
a04d9060d3 ci(qa): build Crabbox CLI for Mantis desktop runs 2026-05-04 02:21:12 +01:00
Vincent Koc
857580108d fix(ci): continue Windows upgrade fallback checks 2026-05-03 18:19:15 -07:00
Vincent Koc
8e79392dcc test(qa): accept Matrix progress edits without draft root 2026-05-03 18:14:03 -07:00
Peter Steinberger
9b397b414a ci(qa): use Mantis Crabbox secret aliases 2026-05-04 02:12:56 +01:00
Vincent Koc
642e1dfcdf fix(agents): preserve messaging dedupe thread ids 2026-05-03 18:11:32 -07:00
Vincent Koc
dfadf03e1f test(acp): isolate persistent binding lifecycle coverage 2026-05-03 18:10:56 -07:00
Vincent Koc
c151573f4c test(agents): align thinking default policy coverage 2026-05-03 18:10:56 -07:00
scoootscooob
b0f947f61c fix(whatsapp): honor group visible reply mode (#76973)
* fix(whatsapp): honor group visible reply mode

* fix(whatsapp): preserve direct reply defaults
2026-05-03 18:07:38 -07:00
Vincent Koc
c1db7df2ea fix(ci): run cross-os checks on Windows 2026-05-03 18:05:50 -07:00
Vincent Koc
0362f64eac fix(qa): pass Mantis desktop runtime env 2026-05-03 18:03:06 -07:00
Peter Steinberger
786fdeb366 refactor: centralize bootstrap system prompt assembly
Centralize embedded attempt system prompt assembly so override and default prompts share bootstrap Project Context handling and provider transforms. Make bootstrap context routing explicit: full bootstrap can enter system Project Context, while runtime/user-message context remains disabled.
2026-05-04 02:02:40 +01:00
Vincent Koc
d5ecee2cf3 fix(google-meet): tighten realtime echo overlap 2026-05-03 17:58:12 -07:00
Peter Steinberger
5ef1885ce3 ci(qa): guard missing Mantis artifact path 2026-05-04 01:57:55 +01:00
Vincent Koc
36bcf88ffc test(qa): accept path-qualified Matrix error progress 2026-05-03 17:55:03 -07:00
Jesse Merhi
9c3b7b7b15 docs: clarify IRC managed proxy coverage (#76822)
Summary:
- The PR adds a changelog note plus IRC and network-proxy documentation stating that IRC raw TCP/TLS egress is outside operator-managed forward proxy routing and should be disabled unless direct egress is approved.
- Reproducibility: not applicable. for this docs-only PR. Source inspection establishes the documented premise ... kets while managed proxy routing covers normal HTTP/WebSocket paths and documents raw-socket bypass limits.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(clawsweeper): address review for automerge-openclaw-openclaw-7682…

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

Prepared head SHA: 7dde35adb9
Review: https://github.com/openclaw/openclaw/pull/76822#issuecomment-4366671907

Co-authored-by: jesse-merhi <79823012+jesse-merhi@users.noreply.github.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
2026-05-04 00:52:47 +00:00
Peter Steinberger
4856cbb017 feat(qa): publish Mantis desktop screenshots 2026-05-04 01:52:14 +01:00
Vincent Koc
ecab09870a test(google-meet): fix consult assertion typing 2026-05-03 17:51:24 -07:00
Vincent Koc
0468ebe200 test(google-meet): type realtime consult assertion 2026-05-03 17:51:24 -07:00
Vincent Koc
08762aa290 fix(google-meet): preserve silent realtime joins 2026-05-03 17:51:24 -07:00
Vincent Koc
0633cb4504 test(plugins): stabilize prerelease surface checks 2026-05-03 17:49:03 -07:00
Peter Steinberger
d85fa16e8f fix: suppress Google Meet realtime echo 2026-05-04 01:48:00 +01:00
Peter Steinberger
ecec68d06d fix: apply undici family fallback to guarded fetch 2026-05-04 01:47:26 +01:00
Peter Steinberger
2b01bcf6c8 refactor: source service env install planning 2026-05-04 01:47:02 +01:00
Vincent Koc
53426cf611 test(google-meet): type embedded agent mock args 2026-05-03 17:44:30 -07:00
Vincent Koc
1be1131631 test(qa): accept Matrix error progress labels 2026-05-03 17:44:30 -07:00
Tak Hoffman
a8467c9fce docs(contributing): align PR cap 2026-05-03 19:44:06 -05:00
Peter Steinberger
419bcd26f0 docs: clarify webchat transcript persistence 2026-05-04 01:40:48 +01:00
Peter Steinberger
2493ab1978 docs: clarify tool-only Discord replies 2026-05-04 01:37:22 +01:00
Peter Steinberger
eb66def656 fix: scope messaging tool final reply dedupe
Co-authored-by: HCL <chenglunhu@gmail.com>
2026-05-04 01:35:58 +01:00
Peter Steinberger
5d09b4b92c feat(agents): add tool progress detail modes 2026-05-04 01:35:27 +01:00
Peter Steinberger
0fa70f5a47 fix: keep bootstrap context in system prompt
Keep pending BOOTSTRAP.md and bootstrap truncation notices in system-prompt Project Context instead of WebChat/runtime user context. Preserve bootstrap instructions when systemPromptOverride is configured.
2026-05-04 01:34:04 +01:00
Peter Steinberger
57b2d29761 feat(qa): add Mantis desktop browser smoke 2026-05-04 01:30:20 +01:00
Peter Steinberger
9c37cfcbdb fix: harden gateway install recovery paths 2026-05-04 01:28:17 +01:00
Vincent Koc
9799e412f8 fix(plugins): clean pinned externalized load paths 2026-05-03 17:27:18 -07:00
Peter Steinberger
b13e9f1864 fix: stabilize Google Meet realtime talkback 2026-05-04 01:24:01 +01:00
Vincent Koc
c42a349b42 fix(plugins): trust official externalized npm installs 2026-05-03 17:20:47 -07:00
Vincent Koc
5f416f09f6 test(qa): accept Matrix read progress labels 2026-05-03 17:18:32 -07:00
Vincent Koc
f5927cbb43 fix(plugins): update trusted prerelease installs 2026-05-03 17:17:10 -07:00
Shadow
40b8d52240 chore: Update active PR limit to 20 2026-05-03 19:15:55 -05:00
Vincent Koc
443f7035a2 fix(plugins): filter unavailable optional tools 2026-05-03 17:10:41 -07:00
Peter Steinberger
c308d04bca test: harden Codex binding provider normalization 2026-05-04 01:10:30 +01:00
Kelaw - Keshav's Agent
8ea04f994a fix: resolve Codex native auth by profile provider 2026-05-04 01:10:30 +01:00
Kelaw - Keshav's Agent
12d90a26f7 docs(changelog): note Codex binding auth fix 2026-05-04 01:10:30 +01:00
Kelaw - Keshav's Agent
71f55214ec fix: select Codex OAuth profile for bound app-server turns 2026-05-04 01:10:30 +01:00
Kelaw - Keshav's Agent
05d11a4318 fix: preserve Codex binding OAuth transport
(cherry picked from commit f45dc3168aea29030b80381dc9017e9ee7e82ba4)
2026-05-04 01:10:30 +01:00
Vincent Koc
f1340be051 feat(openrouter): expand app attribution categories 2026-05-03 17:07:22 -07:00
Vincent Koc
52dbc4d680 test(qa): narrow Matrix approval test id 2026-05-03 17:07:00 -07:00
Vincent Koc
e782f47eca test(qa): wait for Matrix approval reaction echo 2026-05-03 17:07:00 -07:00
Vincent Koc
4dc2aedb76 fix(openai): flatten realtime transcription session update 2026-05-03 17:06:20 -07:00
Vincent Koc
ecd562b2b5 fix(realtime): label pre-ready transcription closes 2026-05-03 17:04:49 -07:00
Vincent Koc
34b3471f85 feat(openrouter): add opt-in response caching
Adds opt-in OpenRouter response caching params, preserves alias precedence across config scopes, and documents the behavior.\n\nVerification:\n- pnpm test:serial src/agents/pi-embedded-runner/proxy-stream-wrappers.test.ts src/agents/pi-embedded-runner-extraparams-openrouter.test.ts -- --reporter=verbose\n- pnpm exec oxfmt --check --threads=1 src/agents/pi-embedded-runner/proxy-stream-wrappers.ts src/plugin-sdk/provider-stream.ts src/agents/pi-embedded-runner/proxy-stream-wrappers.test.ts src/agents/pi-embedded-runner-extraparams-openrouter.test.ts docs/providers/openrouter.md CHANGELOG.md\n- git diff --check\n- Testbox tbx_01kqr4dakpsk9rswz9pem49nz0: pnpm check:changed (https://github.com/openclaw/openclaw/actions/runs/25294515012)
2026-05-03 17:02:18 -07:00
Vincent Koc
f88e1f4c1c fix(openai): fail realtime voice pre-ready closes 2026-05-03 16:58:48 -07:00
Vincent Koc
d057a308f3 fix(openai): omit realtime transcription session type 2026-05-03 16:55:01 -07:00
Vincent Koc
de82d17de2 fix(qa): require cache probe marker 2026-05-03 16:52:58 -07:00
Peter Steinberger
a94897d99c docs: update maintainer closeout guidance 2026-05-04 00:51:57 +01:00
Peter Steinberger
36c047c026 fix(channels): unify progress draft line formatting 2026-05-04 00:50:09 +01:00
Otto Deng
df5c453625 fix(workspace): allow @whiskeysockets/libsignal-node in onlyBuiltDependencies (#76539)
pnpm v9+ defaults blockExoticSubdeps=true, which rejects
@whiskeysockets/libsignal-node — a tarball-URL subdep of
@whiskeysockets/baileys. This silently breaks the WhatsApp channel and
silences all inbound agent replies on fresh installs.

Add @whiskeysockets/libsignal-node to onlyBuiltDependencies in both
package.json and pnpm-workspace.yaml — the same exemption already used
for @whiskeysockets/baileys itself.

Fixes #76539.
2026-05-03 16:49:36 -07:00
Vincent Koc
ef0f5d8cfa docs(changelog): credit @hclsys for doctor legacy-migration fix
#76800 (fixes #76798) added user-facing `doctor --fix` behavior to
commit safe legacy migrations even when unrelated validation issues
prevent full validation, but the existing entry credited no contributor
and used a bare PR/issue reference. Promote #76798 to a `Fixes` ref,
add the merging PR ref (#76800), and credit the human contributor
@hclsys per CLAUDE.md changelog-attribution rules.
2026-05-03 16:49:25 -07:00
Vincent Koc
c90a828d17 fix(slack): skip empty progress refreshes 2026-05-03 16:48:39 -07:00
Vincent Koc
744b7e56e2 test(qa): relax Matrix tool progress preview wording 2026-05-03 16:45:13 -07:00
Vincent Koc
d2ba09b301 fix(channels): skip empty progress drafts 2026-05-03 16:44:21 -07:00
Peter Steinberger
d609859a8b test: simplify parallels smoke harness 2026-05-04 00:44:05 +01:00
Peter Steinberger
c3f5c20f2c fix(cli): retry admin device approval after ownership denial 2026-05-04 00:41:55 +01:00
Peter Steinberger
baadd74b6b fix(plugins): narrow optional tool cold loads 2026-05-04 00:41:01 +01:00
Vincent Koc
07b52b4a01 fix(qa): align mock tool progress markers 2026-05-03 16:38:18 -07:00
hcl
b1db87fb36 fix(doctor): commit legacy migrations even when unrelated validation fails (#76800)
* fix(doctor): commit legacy migrations even when unrelated validation fails (#76798)

migrateLegacyConfig previously returned config: null when post-migration
validation found any issue (e.g. a missing plugin). The caller then kept
the unmigrated config as the candidate, so doctor --fix never wrote the
legacy migration to disk.

Now when validation fails after a successful migration, the migrated
config is returned with partiallyValid: true. applyLegacyCompatibilityStep
always commits the migrated config to state.candidate, ensuring
agents.defaults.llm and other known-legacy keys are cleaned up on
doctor --fix even when an unrelated provider or plugin issue blocks
the full validator.

Adds regression test asserting that candidate is updated to the migrated
shape when partiallyValid is set.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fixup: extend skipPluginValidation to write path with E2E coverage

Thread skipPluginValidationOnWrite through loadAndMaybeMigrateDoctorConfig
return value into runWriteConfigHealth so replaceConfigFile bypasses plugin
validation when migration is only partially valid. Add E2E test verifying
the flag propagates end-to-end.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fixup(doctor): wire skipPluginValidation through full write path (#76800)

Clawsweeper P2 x2:
1. io.ts exported writeConfigFile wrapper now passes skipPluginValidation to
   createConfigIO so both write-phase validation and post-write loadConfig
   re-read honor the flag.
2. mutate.ts tryWriteSingleTopLevelIncludeMutation now skips plugin validation
   when writeOptions.skipPluginValidation is set, so include-write fast path
   no longer blocks safe legacy migrations with unrelated plugin errors.

Adds regression test: skipPluginValidation bypasses plugin schema rejection
on writeConfigFile and falls back to throwing when flag is not set.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fixup(doctor): bail out of include fast path when skipPluginValidation is set (#76800)

Clawsweeper P2: after the include write, readConfigFileSnapshotForWrite()
calls loadConfig() which validates with plugins; refreshedSnapshot.valid is
false when an unrelated plugin issue exists, causing the include path to
throw even though skipPluginValidation was requested.

Simplest fix: return false from tryWriteSingleTopLevelIncludeMutation when
skipPluginValidation is set, letting the root writer handle the write with
plugin validation disabled end-to-end (including post-write readback via
createConfigIO({ pluginValidation: "skip" })).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(doctor): cover partial legacy migration writes

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-04 00:34:24 +01:00
HCL
f8f881f63f fix(daemon): preserve systemd env-file secrets on re-stage
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 00:34:18 +01:00
Vincent Koc
d841394eba test(qa): harden Matrix tool progress live check 2026-05-03 16:31:47 -07:00
Vincent Koc
50a7b95227 fix(openai): wait for realtime voice session readiness 2026-05-03 16:30:56 -07:00
Peter Steinberger
7915110259 fix(memory-lancedb): declare apache-arrow peer 2026-05-04 00:30:02 +01:00
Vincent Koc
7fb2e5506f test(agents): retry empty live cache probe text 2026-05-03 16:26:17 -07:00
Vincent Koc
cde9591168 fix(plugins): prefer newest official prerelease install 2026-05-03 16:25:02 -07:00
Vincent Koc
b520e40cf6 fix(plugins): merge external catalog channel hints 2026-05-03 16:24:17 -07:00
Peter Steinberger
392897304c fix(channels): delay progress drafts until work is visible 2026-05-04 00:22:13 +01:00
Peter Steinberger
88b983a713 fix: stabilize Google Meet realtime audio 2026-05-04 00:17:57 +01:00
Vincent Koc
12dbfab678 fix(msteams): stream progress tool lines 2026-05-03 16:17:02 -07:00
Vincent Koc
b5affa64b3 fix(plugins): supplement external catalog contracts 2026-05-03 16:15:28 -07:00
Peter Steinberger
4419a9de2a docs: clarify fixed issue closeout 2026-05-04 00:12:47 +01:00
Vincent Koc
3efa82de86 fix(plugins): allow prerelease-only official packages 2026-05-03 16:12:39 -07:00
Vincent Koc
0a8694c522 fix(openai): avoid duplicate realtime setup errors 2026-05-03 16:12:05 -07:00
Vincent Koc
f66af6a5f5 fix(openai): wait for realtime transcription session update 2026-05-03 16:12:05 -07:00
Vincent Koc
3546a54003 fix(mattermost): honor progress tool silence 2026-05-03 16:08:47 -07:00
Vincent Koc
8a1e220273 test(qa): relax Matrix tool progress matching 2026-05-03 16:08:24 -07:00
Vincent Koc
250be27f64 fix(plugins): fall back to stable official npm versions 2026-05-03 16:07:53 -07:00
Peter Steinberger
107aad9742 fix(channels): add ellipses to progress draft defaults 2026-05-04 00:05:02 +01:00
Vincent Koc
60cf8c79fe fix(mattermost): expose streaming config hints 2026-05-03 16:00:22 -07:00
Josh Avant
911ac6dd10 fix(discord): handle SecretRef runtime status (#76987)
* fix(discord): handle SecretRef runtime status

* docs(changelog): mention Discord SecretRef fix
2026-05-03 17:56:36 -05:00
Vincent Koc
b2fd814f91 fix(docs): use additive llm task allowlist 2026-05-03 15:53:30 -07:00
Vincent Koc
d62fb9eac3 test(agents): stabilize anthropic abort live harness 2026-05-03 15:52:25 -07:00
Vincent Koc
9ba7183b63 fix(docs): validate plugin json examples 2026-05-03 15:47:23 -07:00
Peter Steinberger
e5ec14a06a fix(plugins): discover alsoAllow plugin tools
Summary:
- Discover optional plugin tools named in tools.alsoAllow without treating additive alsoAllow as a restrictive plugin-tool allowlist.
- Preserve explicit alsoAllow wildcards and keep default non-optional plugin tools visible.
- Document llm-task and lobster enablement and add changelog coverage.

Verification:
- pnpm test src/agents/tool-policy.test.ts src/gateway/tools-invoke-http.test.ts src/agents/pi-tools.create-openclaw-coding-tools.test.ts src/plugins/tools.optional.test.ts
- pnpm exec oxfmt --check --threads=1 src/agents/sandbox-tool-policy.ts src/agents/tool-policy.ts src/agents/tool-policy.test.ts src/agents/pi-tools.create-openclaw-coding-tools.test.ts src/gateway/tools-invoke-http.test.ts src/plugins/tools.ts src/plugins/tools.optional.test.ts
- git diff --check
- Blacksmith Testbox tbx_01kqr05924hz9kw50myxrqmsf9: pnpm check:changed

Fixes #76616
2026-05-03 23:46:14 +01:00
Vincent Koc
0bf19f540d fix(docs): validate strict channel json fences 2026-05-03 15:44:44 -07:00
Peter Steinberger
6529cad499 fix(cli): avoid local preload for gateway-owned message actions (#76898)
* fix(cli): avoid Telegram send preload

* fix(cli): include telegram target prefixes

* fix(cli): generalize gateway message preload skip

* refactor(cli): clarify message preload planning

* fix(cli): keep broadcast plugin preload
2026-05-03 23:44:19 +01:00
Vincent Koc
9f2f75ff02 docs(changelog): drop docs-only Channel docs and Google Chat setup-example entries
Both entries described pure docs/example fixes (JSON5 channel config
snippets and Google Chat `groups.<space>.enabled` setup example) with
no runtime behavior change. Per CLAUDE.md "Changelog user-facing only"
they are not warranted in Unreleased; drop them while keeping QA, Tlon,
and external-contributor entries intact.
2026-05-03 15:42:58 -07:00
Vincent Koc
1fbc240e70 test(qa): preserve Slack live failure artifacts 2026-05-03 15:39:45 -07:00
Vincent Koc
7e92c440eb test(extensions): keep shard balance assertion stable 2026-05-03 15:38:27 -07:00
Vincent Koc
a027ac0195 fix(qa): fail slack no-reply on any reply 2026-05-03 15:35:49 -07:00
Peter Steinberger
95ef5eb762 test(e2e): require configured plugin npm repair 2026-05-03 23:33:01 +01:00
Vincent Koc
f275e9d4b9 fix(docs): make slack manifest snippets parseable 2026-05-03 15:28:27 -07:00
Vincent Koc
3f27ef8ef8 test(googlechat): mirror gaxios interceptor surface 2026-05-03 15:26:39 -07:00
Vincent Koc
9ae93179e2 fix(docs): validate channel config snippets 2026-05-03 15:23:07 -07:00
Peter Steinberger
940487e20f fix: detect muted Google Meet microphone 2026-05-03 23:22:47 +01:00
Vincent Koc
d3043345ca ci(testbox): remove unusable windows harness 2026-05-03 15:21:04 -07:00
Vincent Koc
31cafbb802 test(qa): add Slack live transport lane 2026-05-03 15:19:55 -07:00
Peter Steinberger
4047f4d0b4 fix(plugins): recover managed npm install ledger 2026-05-03 23:17:49 +01:00
Peter Steinberger
90a5b08fb7 docs: add steer command guide 2026-05-03 23:16:37 +01:00
Vincent Koc
d57b16ff81 fix(tlon): expose group invite allowlist 2026-05-03 15:15:58 -07:00
Vincent Koc
41df8191c5 fix(discord): bind draft boundary callbacks 2026-05-03 15:14:51 -07:00
Vincent Koc
56de90e876 ci(testbox): add maintainer os harnesses 2026-05-03 15:00:26 -07:00
Peter Steinberger
b5d240332f fix: retry delayed Google Meet speech 2026-05-03 22:58:56 +01:00
Peter Steinberger
dd32254607 fix(discord): keep progress drafts through interim blocks 2026-05-03 22:58:47 +01:00
Vincent Koc
d3ee67b420 fix(googlechat): correct group setup example 2026-05-03 14:54:35 -07:00
Peter Steinberger
0872b505b0 fix(cron): clarify no-delivery previews 2026-05-03 22:49:31 +01:00
Peter Steinberger
545a7e5590 docs: document ship it shortcut 2026-05-03 22:46:38 +01:00
Vincent Koc
35f6071d8d fix(mattermost): accept streaming config 2026-05-03 14:45:05 -07:00
Jack Storment
bdd68a75ea fix(doctor): repair configured missing plugins
Fixes #76872.

Doctor now repairs configured-but-missing official plugins during update/doctor recovery, auto-enables the plugin after a successful repair, and preserves config when the download cannot complete. The plugin auto-enable path also honors disabled web search and only enables configured providers/channels when a manifest declares the matching capability.

Verification:
- git diff --check
- fallback-only Korean i18n check
- focused plugin auto-enable/config/doctor Vitest suite
- Crabbox published upgrade-survivor configured-plugin-installs E2E
- CI green on PR head 67ba8ac002

Co-authored-by: Jack Storment <crazycoder131@gmail.com>
2026-05-03 22:44:21 +01:00
Peter Steinberger
5fa7d3b1a4 fix: repair Google Meet media permission grants 2026-05-03 22:40:20 +01:00
Peter Steinberger
3e80805d11 feat(agents): add current-session steer command 2026-05-03 22:37:34 +01:00
Peter Steinberger
66336bf7c8 fix: add trusted env proxy opt-in for web fetch 2026-05-03 22:35:30 +01:00
Val Alexander
bd2f8560fe fix(gateway): dedupe active WebChat sends
Collapse duplicate in-flight internal WebChat text sends onto the active Gateway run so rapid repeat submits do not start fresh `agent:main:main` dispatches.

- Add active-run scoped internal text-send dedupe in `chat.send`.
- Exclude slash commands, attachments, explicit delivery routes, non-internal origins, and completed runs.
- Cover the behavior with a Gateway chat regression test.
- Credit both the reporter and BunsDev in the Unreleased changelog entry.

Validation:
- `pnpm docs:list`
- `git diff --check`
- `pnpm check:changelog-attributions`
- `pnpm exec oxfmt --check --threads=1 src/gateway/server-methods/chat.ts src/gateway/server.chat.gateway-server-chat-b.test.ts`
- `pnpm test src/gateway/server.chat.gateway-server-chat-b.test.ts -t "duplicate WebChat" -- --reporter=dot`
- Blacksmith Testbox `OPENCLAW_TESTBOX=1 pnpm check:changed`
- GitHub PR security/stability checks for head `6884240414997228a136f0fbb85b73a8db4b7fae`

Fixes #75737.
2026-05-03 16:30:17 -05:00
Vincent Koc
8beda86416 fix(channels): expose progress draft config hints 2026-05-03 14:21:57 -07:00
Peter Steinberger
be324914cb fix(discord): seed progress drafts at dispatch start 2026-05-03 22:19:25 +01:00
Vincent Koc
257c5a517f fix(channels): normalize progress auto labels 2026-05-03 14:12:39 -07:00
Vincent Koc
990f931a2e fix(upgrade): unlink stale plugin runtime symlinks 2026-05-03 14:04:39 -07:00
Peter Steinberger
c33e578554 feat: add channel progress drafts
Adds unified progress-draft streaming for chat channels, with docs and per-channel regressions.
2026-05-03 22:01:08 +01:00
Vincent Koc
5355ef0f08 fix(plugins): dedupe manifest diagnostics 2026-05-03 13:51:25 -07:00
Peter Steinberger
9ebdd26020 fix: thread package root into symlink cleanup 2026-05-03 21:49:53 +01:00
Peter Steinberger
3301760567 docs: record upgrade recovery fixes 2026-05-03 21:49:53 +01:00
Peter Steinberger
797d02497e fix: prune stale plugin runtime symlinks 2026-05-03 21:49:53 +01:00
Peter Steinberger
1ace6a0d6a fix: avoid launchd kickstart after fresh bootstrap 2026-05-03 21:49:53 +01:00
Peter Steinberger
d0ad5c3eaa fix(daemon): prefer system node for gateway install 2026-05-03 21:48:57 +01:00
Peter Steinberger
ca620fbd4f fix: honor env proxy for provider guarded fetch 2026-05-03 21:48:26 +01:00
Peter Steinberger
e387764014 ci(qa): upsert Mantis PR comments by marker 2026-05-03 21:35:02 +01:00
Peter Steinberger
ac1e56f4ec docs: note crabbox macos windows targets 2026-05-03 21:31:25 +01:00
Peter Steinberger
d8b82df5d4 ci(qa): trigger Mantis Discord QA from PR comments 2026-05-03 21:27:43 +01:00
Peter Steinberger
d4af125b52 feat(qa): add Mantis before-after CLI 2026-05-03 21:27:43 +01:00
Peter Steinberger
3147efbed4 docs: clarify update diagnostics 2026-05-03 21:21:55 +01:00
Vincent Koc
53cc52981b fix(test): align plugin gauntlet with built runtime 2026-05-03 13:17:21 -07:00
Peter Steinberger
b726214cf3 fix: avoid fresh launchd repair kickstart 2026-05-03 21:04:48 +01:00
Vincent Koc
62fb50d7fc fix(config): refresh config docs baseline 2026-05-03 12:41:22 -07:00
Vincent Koc
a420bb334f fix(plugin-sdk): refresh api baseline hash 2026-05-03 12:35:16 -07:00
Vincent Koc
a0e0bf5848 fix(status): ignore malformed persisted model fields 2026-05-03 12:28:04 -07:00
Peter Steinberger
edb7e00721 fix(network): scope fake-ip SSRF policy to provider hosts 2026-05-03 20:27:39 +01:00
Vincent Koc
1d34564de9 fix(plugins): expose hook timeout overrides 2026-05-03 12:21:59 -07:00
Vincent Koc
c5488ea577 fix(telegram): expose media group flush config 2026-05-03 12:12:53 -07:00
Peter Steinberger
52257fd05e docs: clarify Discord bot mentions 2026-05-03 20:11:55 +01:00
Peter Steinberger
725754e500 fix(update): repair configured plugin installs after update doctor 2026-05-03 20:07:07 +01:00
github-actions[bot]
4b66a1f7f8 chore(ui): refresh ko control ui locale 2026-05-03 19:06:53 +00:00
neilofneils404
904cbec721 fix: reject unowned CLI roots before plugin load (#76379)
Co-authored-by: Neil <neil@neilofneils.com>
2026-05-03 20:06:49 +01:00
Marvinthebored
a64b30705f fix(usage): serve usage from durable transcript aggregate cache
Serve usage.cost and sessions.usage from a durable transcript aggregate cache with guarded refreshes, cache-status UI localization, and regression coverage. Thanks @Marvinthebored.
2026-05-03 20:04:26 +01:00
Peter Steinberger
4795f3474a docs(changelog): fold 2026.4.30 into 2026.4.29 2026-05-03 19:57:52 +01:00
Peter Steinberger
04c724bb0c fix(update): complete deferred plugin install repair 2026-05-03 19:57:52 +01:00
Vincent Koc
69b66dd548 fix(config): coerce visible replies booleans 2026-05-03 11:52:06 -07:00
Vincent Koc
03e35b1d83 fix(feishu): honor block streaming config 2026-05-03 11:42:13 -07:00
Peter Steinberger
f74e901794 fix: clarify blocked plugin validation 2026-05-03 19:39:30 +01:00
Vincent Koc
4e0e6f8ef3 fix(doctor): reset stale plugin slots 2026-05-03 11:33:13 -07:00
Vincent Koc
9d5fedb9b5 fix(plugins): normalize tool name conflicts 2026-05-03 11:27:24 -07:00
Peter Steinberger
2b7e8dacd3 fix: quiet nonblocking diagnostic logs 2026-05-03 19:24:38 +01:00
Vincent Koc
01e2755dc3 fix(googlechat): normalize auth transport headers 2026-05-03 11:20:54 -07:00
Peter Steinberger
579cc23ce0 ci: publish ClawHub plugins as ClawPacks 2026-05-03 19:19:19 +01:00
Vincent Koc
83b14dc46e fix(docs): merge WhatsApp web config example 2026-05-03 11:12:11 -07:00
Vincent Koc
af1c869d10 fix(anthropic): expose bundled thinking policy 2026-05-03 11:10:56 -07:00
Edionwheels
66ffb29679 fix(plugins): cold-load partial tool registries
Fix plugin tool discovery when a selected wildcard plugin set is resolved against a partial active registry.\n\nRequire scoped registries to cover every requested plugin owner, force cold-load incomplete tool discovery registries without replacing active plugin runtime state, and add regression coverage for the partial-registry path.\n\nFixes #76780.\nThanks @lilesjtu.
2026-05-03 19:09:34 +01:00
Peter Steinberger
ee6052a169 fix(bonjour): default LAN discovery on macOS only
Summary:
- add manifest-backed platform-specific default enablement for bundled plugins
- auto-start Bonjour LAN discovery on macOS hosts only
- keep Linux, Windows, and containerized Gateway deployments opt-in while preserving explicit enablement

Verification:
- pnpm test extensions/bonjour/src/advertiser.test.ts src/plugins/bundled-plugin-metadata.test.ts src/plugins/manifest-registry.test.ts src/plugins/channel-plugin-ids.test.ts
- pnpm exec oxfmt --check --threads=1 CHANGELOG.md docs/gateway/bonjour.md docs/gateway/configuration-reference.md docs/gateway/discovery.md docs/gateway/security/index.md docs/plugins/manifest.md extensions/bonjour/openclaw.plugin.json src/plugin-sdk/facade-activation-check.runtime.ts src/plugins/bundled-manifest-contract-plugins.ts src/plugins/bundled-plugin-metadata.test.ts src/plugins/channel-presence-policy.ts src/plugins/default-enablement.ts src/plugins/gateway-startup-plugin-ids.ts src/plugins/installed-plugin-index-record-builder.ts src/plugins/installed-plugin-index-store.ts src/plugins/installed-plugin-index-types.ts src/plugins/installed-plugin-index.ts src/plugins/loader.ts src/plugins/manifest-contract-eligibility.ts src/plugins/manifest-owner-policy.ts src/plugins/manifest-registry-installed.ts src/plugins/manifest-registry.test.ts src/plugins/manifest-registry.ts src/plugins/manifest.ts src/plugins/providers.ts
- git diff --check
- Testbox: pnpm check:changed via Blacksmith Testbox tbx_01kqqf3f8rbrt8afjtcg0ck7qs

Refs #74209
2026-05-03 19:07:27 +01:00
Vincent Koc
fa98d01aa1 fix(discord): skip disabled native command cleanup 2026-05-03 11:03:02 -07:00
Peter Steinberger
5a028489b1 fix(voice-call): fallback on gateway 1006 closes (#76858) 2026-05-03 19:02:21 +01:00
Vincent Koc
863198f0c9 fix(commands): tolerate empty plugin command replies
Fixes #74800.
2026-05-03 10:55:58 -07:00
Vincent Koc
63ebe372e8 fix(openrouter): expose DeepSeek V4 xhigh thinking
Fixes #74788.
2026-05-03 10:51:05 -07:00
Vincent Koc
96886381a3 fix(status): tolerate malformed session model refs 2026-05-03 10:44:55 -07:00
Vincent Koc
fb73f2161e fix(config): unset array index once
Fixes #76290.
2026-05-03 10:42:49 -07:00
Peter Steinberger
a989d248e9 fix: throttle long-running diagnostic warnings 2026-05-03 18:39:57 +01:00
Peter Steinberger
0d97fa3f3a ci(release): skip baseline lifecycle scripts in upgrade checks 2026-05-03 18:35:35 +01:00
Peter Steinberger
aa5f8e6403 fix: reduce slack socket reconnect log noise 2026-05-03 18:33:14 +01:00
Peter Steinberger
11a08e3ef0 [codex] Fix message CLI plugin preload failure exit (#76846)
Summary:
- The branch moves configured-channel plugin registry loading for `openclaw message` into `runCommandWithRuntime`, adds regression coverage for preload failure exit handling, and adds a changelog fix entry for #76168.
- Reproducibility: yes. for the exit-path bug: current main calls plugin registry preload before `runCommandWi ...  the high-CPU child process in this read-only review, but the missing exit boundary is source-reproducible.

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

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

Prepared head SHA: 95b67ea9ba
Review: https://github.com/openclaw/openclaw/pull/76846#issuecomment-4366747104

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-03 17:32:36 +00:00
Dallin Romney
ec73e9985c fix(clawhub): accept live artifact resolver field aliases
Accept live ClawHub artifact resolver kind/sha256 aliases for npm-pack and legacy ZIP installs.\n\nVerified locally after rebase:\n- pnpm exec oxfmt --check --threads=1 src/plugins/clawhub.ts src/plugins/clawhub.test.ts CHANGELOG.md\n- pnpm test:serial src/plugins/clawhub.test.ts
2026-05-03 10:31:27 -07:00
Dallin Romney
68918ab36a docs(tools): scope loop detection recommendation to smaller models (#76837) 2026-05-04 01:28:23 +08:00
Vincent Koc
9d858a401d fix(release): use sync published tar extraction 2026-05-03 10:22:36 -07:00
Vincent Koc
eb5517be51 fix(release): verify published plugin runtime tarballs 2026-05-03 10:22:36 -07:00
Peter Steinberger
2805bbd3d7 feat(commands): add side alias for btw 2026-05-03 18:22:20 +01:00
Peter Steinberger
c30531ddaf ci: remove stale deadcode allowlist entry 2026-05-03 18:21:11 +01:00
Peter Steinberger
0ff09c4e49 ci: configure Mantis bot comment identity (#76839) 2026-05-03 18:20:43 +01:00
Peter Steinberger
d763b83854 fix: explain disabled plugin command aliases
Preserve enabled-by-default metadata for manifest command aliases so missing CLI command diagnostics can point users at the parent bundled plugin and the `openclaw plugins enable <plugin>` repair path.

Also carries the current-main deadcode allowlist entry for the command-analysis barrel that blocked CI.

Verified:
- pnpm test src/cli/run-main.test.ts src/plugins/manifest-command-aliases.test.ts
- pnpm deadcode:unused-files
- pnpm exec oxfmt --check --threads=1 scripts/deadcode-unused-files.allowlist.mjs CHANGELOG.md src/cli/run-main-policy.ts src/cli/run-main.test.ts src/plugins/manifest-command-aliases.ts src/plugins/manifest-command-aliases.test.ts
- git diff --check
- PR CI on 6076ff2d52 green, ignoring cancelled auto-response per landing matrix
2026-05-03 18:19:50 +01:00
Peter Steinberger
7857dfabcc fix: align apply_patch deny policy docs (#76795) 2026-05-03 18:18:45 +01:00
HCL
8da9d8c55f fix(tool-policy): deny write no longer silently hides apply_patch (#76749)
Removing deny coupling between write and apply_patch in
makeToolPolicyMatcher. Previously deny: ["write"] would also block
apply_patch, contradicting the documented allow coupling (apply_patch
inherits write allow). Allow inheritance is preserved; deny must be
stated explicitly.

Adds regression tests verifying:
- deny: ["write"] leaves apply_patch accessible
- deny: ["apply_patch"] still denies directly
- allow: ["write"] still grants apply_patch
- deny: ["write", "apply_patch"] still denies when both explicit

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-03 18:18:45 +01:00
Peter Steinberger
9185e500bd chore: remove unused command analysis barrel 2026-05-03 18:13:42 +01:00
Peter Steinberger
9a22473916 fix: recover stalled embedded diagnostic runs 2026-05-03 18:13:15 +01:00
Peter Steinberger
2416bc668c ci: require Mantis app for QA comments (#76834) 2026-05-03 18:08:46 +01:00
Peter Steinberger
bd0e10a2f6 refactor: route inline eval through command analysis 2026-05-03 18:06:10 +01:00
Peter Steinberger
99176e1950 refactor: share command payload extraction 2026-05-03 18:06:10 +01:00
Peter Steinberger
3f7e6eebc2 refactor: unify command analysis for exec approvals 2026-05-03 18:06:10 +01:00
Peter Steinberger
0ee52e9405 fix: keep config recovery in doctor 2026-05-03 18:04:50 +01:00
Peter Steinberger
dc32acd0d8 ci: post Mantis QA comments as GitHub App (#76825) 2026-05-03 18:00:00 +01:00
Ayaan Zaidi
de16329b93 docs(changelog): note reply target context fix 2026-05-03 22:29:05 +05:30
Ayaan Zaidi
834b4f949f test(reply-context): cover reply target label 2026-05-03 22:29:05 +05:30
Ayaan Zaidi
64e28a6ac9 fix(reply-context): label current reply target 2026-05-03 22:29:05 +05:30
pashpashpash
5bcc321343 Scope Codex heartbeat guidance to heartbeat turns (#76788)
* fix(codex): scope heartbeat guidance to collaboration mode

* fix heartbeat tool direct context

* test prompt heartbeat collaboration snapshots

* fix heartbeat changelog credit
2026-05-04 01:58:39 +09:00
Peter Steinberger
8b4808d543 fix(release): align beta plugin changelog sections 2026-05-03 17:56:07 +01:00
Peter Steinberger
7ea7f06038 docs: add changelog for LaunchAgent recovery (#76790) (thanks @jonathanlindsay) 2026-05-03 17:55:02 +01:00
jonathanlindsay
745c5bd772 fix: recover macos launchagent after updates 2026-05-03 17:55:02 +01:00
clawsweeper[bot]
1bc97aee4e fix(doctor): suppress memory plugin false alarm
Fixes #76792.

- Trust a checked-ready gateway memory probe when CLI-local memory runtime resolution is unavailable.
- Keep the no-active-plugin warning for skipped, unavailable, or not-ready gateway probes.
- Add regression coverage for ready, skipped, and not-ready probe cases.

Thanks @som-686.
2026-05-03 17:54:39 +01:00
Simone
c5b559d4ee fix(plugins): keep npm bridge updates scanned (#76765)
Keep externalized bundled npm bridge updates on the normal plugin security scanner path instead of granting source-linked official trust without artifact provenance.

Thanks @Lucenx9.
2026-05-03 17:50:31 +01:00
Peter Steinberger
4e82cacc84 fix(voice-call): summarize restored call verification logs 2026-05-03 17:44:21 +01:00
Peter Steinberger
ab0f8a79ca test(status): update reaction lifecycle snapshots 2026-05-03 17:44:13 +01:00
Peter Steinberger
1c7cbf55b9 test(discord): type tracked target resolver mock 2026-05-03 17:44:13 +01:00
Peter Steinberger
5039a35a33 fix(discord): preserve tracked reaction targets 2026-05-03 17:44:13 +01:00
Peter Steinberger
788cff1df4 Add opt-in reaction tool tracking 2026-05-03 17:44:13 +01:00
Peter Steinberger
c40f89414c ci: summarize Mantis bug proof comments
Clarify that Mantis screenshot proof belongs on the bug/fix PR and add a top summary to inline evidence comments.
2026-05-03 17:39:46 +01:00
Peter Steinberger
ddae71537d docs(agents): require PR descriptions 2026-05-03 17:38:47 +01:00
Peter Steinberger
a38c2c233a fix(memory): split vector store readiness 2026-05-03 17:38:47 +01:00
Peter Steinberger
3617778aaf test: tolerate prerelease channel metadata 2026-05-03 17:35:37 +01:00
Dallin Romney
aa18254770 feat(clawhub): surface rate-limit headers and sign-in hint on 429s (#76748)
* feat(clawhub): surface rate-limit headers and sign-in hint on 429s

* feat(clawhub): handle no-headers 429s and add changelog entry

* fix(clawhub): simplify 429 message to reset hint only
2026-05-04 00:34:50 +08:00
Peter Steinberger
07a11c4806 ci: post Mantis screenshots inline
Publish redacted Mantis screenshots to qa-artifacts and upsert a PR QA comment with inline before/after images.
2026-05-03 17:28:32 +01:00
Peter Steinberger
5330cbb25d refactor(telegram): clarify reply supersession fence 2026-05-03 17:25:58 +01:00
Peter Steinberger
2696baba81 fix(telegram): suppress superseded turn replies 2026-05-03 17:25:58 +01:00
chinar-amrutkar
463493aa28 fix(telegram): suppress stale replies when dispatch is superseded
When a newer inbound message arrives while an older dispatch is still
processing, buffered final answers from the old dispatch could be
delivered into the new message's thread.

Add `bufferedGeneration` to `BufferedFinalAnswer` to track which
abort fence generation buffered the answer. `takeBufferedFinalAnswer`
now rejects buffered answers whose generation doesn't match the
current dispatch's generation, preventing stale content delivery.

Fixes #76642
2026-05-03 17:25:58 +01:00
Peter Steinberger
7b5a18ae7a fix(google-meet): keep CLI sessions gateway-owned 2026-05-03 17:22:59 +01:00
Peter Steinberger
e267e3afa0 ci: keep Mantis Discord artifacts lean
Keep Mantis Discord comparison worktrees outside the uploaded artifact tree and copy each lane's QA output into the report directory before comparing summaries.
2026-05-03 17:15:27 +01:00
Peter Steinberger
e7f1b10ff8 fix: auto-repair gateway watch startup 2026-05-03 17:11:19 +01:00
Peter Steinberger
0636442cde fix(discord): send early typing cue for accepted DMs
Co-authored-by: chinar-amrutkar <chinar.amrutkar@gmail.com>
2026-05-03 17:10:07 +01:00
Peter Steinberger
fb9030ff67 fix: honor wildcard tool denylists in factory planning (#76773) (thanks @dorukardahan) 2026-05-03 17:09:55 +01:00
Doruk Ardahan
0739cb19b7 fix(tools): skip denied optional media factories 2026-05-03 17:09:55 +01:00
Ayaan Zaidi
7f6798094c docs(changelog): note telegram topic delivery fix 2026-05-03 21:31:56 +05:30
Ayaan Zaidi
fc293b1a53 test(telegram): cover forum topic queued finals 2026-05-03 21:31:56 +05:30
Ayaan Zaidi
5130622750 fix(telegram): require observed final delivery 2026-05-03 21:31:56 +05:30
clawsweeper
453be4216b fix(memory): keep plain status from probing embedding providers 2026-05-03 17:01:42 +01:00
Peter Steinberger
77a50db9ea feat(qa): add Mantis Discord status reaction scenario (#76747)
* feat(qa): add Mantis Discord status reaction scenario

* fix(qa): retry Discord rate limits in Mantis runs

* refactor(qa): reuse Discord API retry helper

* fix(qa): import Discord API through package surface

* fix(ci): generate Discord boundary declarations

* fix(ci): keep xai boundary overrides stable
2026-05-03 17:00:06 +01:00
Peter Steinberger
1e8de7661e fix: canonicalize diagnostic session aliases 2026-05-03 16:58:05 +01:00
hcl
02b1075a57 fix(pdf-tool): defer PDF model resolution (#76728)
Defers automatic PDF model/auth resolution until the PDF tool is executed, avoiding agent-turn tool prep stalls while preserving explicit PDF model registration.

Fixes #76644.
Thanks @hclsys.
2026-05-03 16:55:14 +01:00
Peter Steinberger
6becfcb275 test(telegram): cover visible error fresh final 2026-05-03 16:53:06 +01:00
jack-stormentswe
2b38345c8a fix(telegram): force fresh final after visible intermediate output (#76529) 2026-05-03 16:53:06 +01:00
Peter Steinberger
59c523c6b5 fix: reject source-only plugin package installs 2026-05-03 16:48:46 +01:00
Peter Steinberger
0a2b87c986 test: isolate gateway agent dirs 2026-05-03 16:48:25 +01:00
Peter Steinberger
fa866d562e perf(gateway): trim startup imports and sentinel checks 2026-05-03 16:43:07 +01:00
Peter Steinberger
9dc0f10f8a test: type codex dynamic tool fixture 2026-05-03 16:40:46 +01:00
Peter Steinberger
d286620c03 chore(skills): fold testbox notes into crabbox 2026-05-03 16:36:17 +01:00
Peter Steinberger
1bfc66da33 test: avoid codex app-server factory race 2026-05-03 16:34:49 +01:00
Peter Steinberger
e2c8db2cad fix(telegram): warn on selected quote tool progress 2026-05-03 16:24:38 +01:00
GodsBoy
b336efdd9c docs(telegram, streaming): note replyToMode and toolProgress mutual exclusion
Document that channels.telegram.streaming.preview.toolProgress requires
channels.telegram.replyToMode: 'off'. Quote-reply requires the final message
reference at send time, which is incompatible with preview-edit streaming, so
the two features are mutually exclusive on Telegram.

Adds:
- Note callout in docs/channels/telegram.md after the existing toolProgress
  guidance, explaining the exclusion and how to restore visibility.
- Cross-link bullet in docs/concepts/streaming.md pointing to the Telegram
  channel doc for the full note.

Surfaces a doc/runtime gap that has been silent since v2026.4.22.
2026-05-03 16:24:38 +01:00
Peter Steinberger
5e830508b6 test: stabilize release validation lanes 2026-05-03 16:24:17 +01:00
Shuxin Zheng
b8bc8423d2 fix(discord): normalize CJK/whitespace in slash-command description comparison to prevent spurious reconcile PATCH + 429 (#76588)
* fix(discord): normalize whitespace in command description comparison

Discord server-side storage collapses consecutive whitespace and removes
whitespace between adjacent CJK characters when persisting slash-command
descriptions. Our locally-serialized desired descriptors keep the original
whitespace, so commandsEqual returned false on every startup for any
deployment with multi-line or CJK-heavy descriptions.

This caused reconcile to issue a PATCH for every such command on every
gateway restart. Under Discord's per-application rate limit that quickly
produced a burst of 429s and some commands silently failed to register
until the next restart — a recurring symptom behind several Discord deploy
429 reports (#75341, #75888).

Fix: apply the same two normalizations to description strings in
comparableCommand — collapse runs of whitespace to a single space, then
drop whitespace between CJK boundary characters — before JSON-based
equality. This mirrors Discord's storage semantics so round-tripped
descriptions compare equal.

Adds command-deploy.test.ts with regression coverage for:
- server-side default fields ignored (dm_permission, nsfw, version, ids)
- required:false treated as absent (existing behavior)
- CJK descriptions with \n separators normalized
- mixed CJK/ASCII descriptions with consecutive whitespace
- substantive description diffs still flagged
- ASCII whitespace normalization

* fix(discord): normalize localized command descriptions

* fix(discord): ignore null localization maps in command reconcile

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-03 16:22:41 +01:00
MkDev11
38eea33062 fix(opencode): expose Claude thinking policy (#76760)
Summary:
- Adds an OpenCode provider-policy public artifact that delegates Claude thinking profiles, plus regression tests and a changelog entry for preserving `xhigh` on `opencode/claude-opus-4-7`.
- Reproducibility: yes. Source inspection on current main shows the model-switch path remaps unsupported store ... vider-policy-api` artifact for the fallback resolver; the proposed test exercises that stored-`xhigh` path.

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

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

Prepared head SHA: efa152cca5
Review: https://github.com/openclaw/openclaw/pull/76760#issuecomment-4366483080

Co-authored-by: mkdev11 <MkDev11@users.noreply.github.com>
2026-05-03 15:22:07 +00:00
Peter Steinberger
686585eccf docs(telegram): clarify polling startup network notes (#76768) 2026-05-03 16:21:41 +01:00
hcl
081f873162 fix(active-memory): skip scoped topic channelId for embedded recall run in Telegram forum topics (#76704) (openclaw#76719)
Verified:
- pnpm install --frozen-lockfile
- pnpm test extensions/active-memory/index.test.ts
- pnpm exec oxfmt --check --threads=1 extensions/active-memory/index.ts extensions/active-memory/index.test.ts CHANGELOG.md
- git diff --check origin/main..HEAD

Co-authored-by: hclsys <7755017+hclsys@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-05-03 10:19:32 -05:00
Ayaan Zaidi
5b94c4ce93 fix(telegram): start polling after webhook cleanup timeout (#76735)
Summary:
- The branch changes Telegram polling startup to reuse the successful probe `getMe` result as grammY `botInfo` ... es` after recoverable `deleteWebhook` failures, and updates Telegram docs, changelog, and regression tests.
- Reproducibility: yes. for the narrow PR bug: source inspection shows current main can block before polling o ... d timeout coverage that reaches `run()`. The full linked high-RTT report remains only partially reproduced.

Automerge notes:
- Ran the ClawSweeper repair loop before final review.
- Included post-review commit in the final squash: fix(telegram): start polling after webhook cleanup timeout
- Included post-review commit in the final squash: fix(telegram): extract bot info contract

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

Prepared head SHA: c74bbdd1ff
Review: https://github.com/openclaw/openclaw/pull/76735#issuecomment-4366417178

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-03 15:16:32 +00:00
Sally O'Malley
d0497d13d1 fix(macos): mask sensitive wizard cli prompts (#76746)
Merged via squash.

Prepared head SHA: 8a63773e22
Reviewed-by: @sallyom
2026-05-03 11:10:08 -04:00
Peter Steinberger
ee64b3c100 docs(changelog): note session latency fix (#76676) (thanks @VACInc) 2026-05-03 16:08:35 +01:00
VACInc
a0e526c163 fix(ui): avoid session list reloads for chat turns 2026-05-03 16:08:35 +01:00
Peter Steinberger
9e56cfcc35 perf(gateway): cache startup metadata probes 2026-05-03 16:07:06 +01:00
vinhnguyenthanhdn
f8141da4a6 test(session): prevent regression where session repair trims final assistant response after tool usage (#76538)
Summary:
- Adds a Vitest regression case in `src/agents/session-file-repair.test.ts` for `repairSessionFileIfNeeded` preserving a final stop assistant message after a tool-call/tool-result pair.
- Reproducibility: yes. The added JSONL sequence can be traced through current `repairSessionFileIfNeeded`: no ... ry, so the function returns `repaired: false`; I did not execute the test because this review is read-only.

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

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

Prepared head SHA: ee3aa0e036
Review: https://github.com/openclaw/openclaw/pull/76538#issuecomment-4365670008

Co-authored-by: Vinh Nguyen <vinhnt36@fpt.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-03 15:06:28 +00:00
Peter Steinberger
11e05e86a2 fix(agents): scope gpt-5.4-mini chat reasoning fallback (#76727)
Fixes #76176.

OpenAI live verification showed `gpt-5.4-mini` supports reasoning effort generally, but rejects `/v1/chat/completions` payloads that combine function tools with `reasoning_effort`. This keeps reasoning effort for tool-free Chat Completions and Responses, and omits it only for the rejected Chat Completions + function tools combination.

Validation:
- Live OpenAI API matrix on 2026-05-03
- pnpm test src/agents/openai-reasoning-effort.test.ts src/agents/openai-transport-stream.test.ts -- --reporter=verbose
- GitHub PR CI green on ea3915308c

Thanks @ThisIsAdilah and @chinar-amrutkar.
2026-05-03 16:02:15 +01:00
rolandrscheel
aeac10f5ce fix(plugins): reuse workspace metadata for session model refs (#76655)
Fixes the session-list plugin metadata hot path by reusing the active workspace-scoped plugin metadata snapshot for manifest model-id normalization and setup CLI backend fallback. Also keeps model-pricing collection on its provided manifest registry and fixes the CI-only hoisted test mock failure.

Validated with targeted plugin/gateway/model-selection tests, CI-shaped gateway startup shard, Testbox `pnpm check:changed`, and green PR CI.

Thanks @rolandrscheel.
2026-05-03 15:58:14 +01:00
Peter Steinberger
935f078a89 perf(gateway): skip force port scan when free 2026-05-03 15:52:57 +01:00
Peter Steinberger
72072d8b2e perf(gateway): lazy load ws and aux handlers 2026-05-03 15:52:57 +01:00
Peter Steinberger
bd79b25bb7 perf(gateway): defer task registry startup imports 2026-05-03 15:52:57 +01:00
Peter Steinberger
0949f4fe51 fix(discord): honor explicit tool-only status reactions (#76733) 2026-05-03 15:44:42 +01:00
Peter Steinberger
34bc31fb2c test: accept concise transport replay response 2026-05-03 15:37:21 +01:00
ANURAG BHEEMAPPA GNANAMURTHY
727398f41a fix(onboarding): mask token/credential inputs in CLI wizard prompts (#76693)
Summary:
- The PR adds `sensitive` support to wizard text prompts, routes sensitive Clack prompts through `password()`, ...  preserves existing gateway secrets through masked-preview confirms, and adds tests plus a changelog entry.
- Reproducibility: yes. Source inspection shows current main routes onboarding credential entry through visibl ... y provides a concrete Windows PowerShell `openclaw onboard --install-daemon` reproduction with screenshots.

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

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

Prepared head SHA: a3db64c265
Review: https://github.com/openclaw/openclaw/pull/76693#issuecomment-4366253531

Co-authored-by: anurag-bg-neu <bheemappagnanamurt.a@northeastern.edu>
2026-05-03 14:33:58 +00:00
Vishal Jain
0e4d28aa9e fix(codex): force message tool for source replies (#76663)
* fix(codex): force message tool for source replies

* docs: credit codex source reply fix (#76663) (thanks @VishalJ99)

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-03 15:29:07 +01:00
Peter Steinberger
ffdc76bb0c docs(changelog): thank feishu queue contributor 2026-05-03 15:28:12 +01:00
Peter Steinberger
8b945bcbea fix(feishu): satisfy queue timeout lint 2026-05-03 15:28:12 +01:00
Martin Garramon
0028e6040a fix(feishu): cap per-chat queue task wait so a single hang doesn't starve later messages
Per-chat sequential queue had no timeout: if a single dispatch hung
(e.g. an agent call that never resolved), every subsequent message in
the same chat stayed `queued` until the gateway was restarted.

Add an optional `taskTimeoutMs` (default 5 min) to `createSequentialQueue`.
After the cap, the in-flight task is evicted from the blocking chain so
newer same-key tasks can proceed. The original task is NOT aborted —
it continues running in the background; we just stop starving the queue.
A warning log surfaces the eviction with the offending key.

`taskTimeoutMs: 0` restores legacy unbounded behavior.

Same-chat FIFO ordering for normal-cadence messages is preserved
(see #64324) — only pathologically slow tasks get evicted.

Fixes #70133.
2026-05-03 15:28:12 +01:00
Peter Steinberger
0bf06e953f feat: add Mantis Discord smoke runner (#76696)
* docs: add Mantis QA system design

* feat: add Mantis Discord smoke runner

* fix: harden Mantis Discord smoke

* fix: redact Mantis Discord artifacts

* fix: satisfy Mantis redaction lint

* fix: redact Mantis mismatch failures

* test: avoid promise assertions in Mantis tests
2026-05-03 15:25:56 +01:00
Peter Steinberger
6aa4fb7a69 test(ui): cover deferred skills dialog opening 2026-05-03 15:23:18 +01:00
Nickmopen
69f03f134f chore: add changelog entry for skills dialog showModal fix 2026-05-03 15:23:18 +01:00
Nickmopen
e8227fd7b3 fix(ui): defer showModal until dialog is connected to DOM 2026-05-03 15:23:18 +01:00
MkDev11
d6900ee500 fix(plugins): load explicit hook plugins at startup (#76684) (thanks @MkDev11)
Includes explicitly enabled hook-capable plugins in the Gateway startup runtime scope and adds regression coverage for startup hook plugin gating.
2026-05-03 15:20:42 +01:00
Vincent Koc
877eb1cbed fix(heartbeat): align response tool prompts (#76458)
* fix(heartbeat): align response tool prompts

* docs(changelog): credit heartbeat prompt fix
2026-05-03 07:19:56 -07:00
Peter Steinberger
103b6d50a5 fix: resolve bundled public surfaces from packaged dist
(cherry picked from commit 8771cfb5b7)
2026-05-03 15:15:56 +01:00
Peter Steinberger
1110c249ae test: keep windows smoke compatible with old agent cli
(cherry picked from commit bf91494035)
2026-05-03 15:15:56 +01:00
Peter Steinberger
3a5a2bfaf4 test: accept externalized discord voice fallback
(cherry picked from commit 13424b9b3e)
2026-05-03 15:15:56 +01:00
Peter Steinberger
96b574f486 fix: honor package excludes in channel pack smoke
(cherry picked from commit 5305b172a3)
2026-05-03 15:15:56 +01:00
Tyler Nishida
796c1e67c3 fix message-tool-only telegram fallback (#76272) 2026-05-03 23:15:53 +09:00
Josh Lehman
30018bddc6 fix: restore verbose tool progress in chats (#76716)
* fix: restore verbose tool progress in chats

* test: fix gateway verbose mock types
2026-05-03 07:14:39 -07:00
Peter Steinberger
0b9a063a5b test: keep release dependency fixture title 2026-05-03 15:12:37 +01:00
Peter Steinberger
7488d8f1fe test: align release branch expectations 2026-05-03 15:12:37 +01:00
Peter Steinberger
4457ddd489 chore: drop root oxlint tsconfigs 2026-05-03 15:12:06 +01:00
Peter Steinberger
ad1ccd671b chore: move oxlint tsconfigs under config 2026-05-03 15:12:06 +01:00
Peter Steinberger
39c5bbf08e fix(codex): mark approval event emission fire-and-forget 2026-05-03 15:04:02 +01:00
Peter Steinberger
95f5b265c1 test: align support-boundary expectations 2026-05-03 15:04:02 +01:00
Peter Steinberger
54c0f982d5 fix: keep reply context out of image scanning (#76659) 2026-05-03 15:04:02 +01:00
Michael Romero
abed4231aa fix: preserve reply context in embedded prompts 2026-05-03 15:04:02 +01:00
Dash
d35c79edd6 fix(agents): suppress duplicate user persistence on fallback retries (#63696)
* fix(agents): suppress duplicate user persistence on fallback retries

* refactor(agents): align persisted-user callback types

* docs: note fallback transcript dedupe

* refactor(agents): remove fallback persistence casts

---------

Co-authored-by: Altay <altay@uinaf.dev>
2026-05-03 16:55:45 +03:00
Peter Steinberger
4488382c1c chore: drop root swift config copies 2026-05-03 14:52:12 +01:00
Peter Steinberger
e7bb5d6ddf chore: move swift configs under config 2026-05-03 14:51:56 +01:00
Peter Steinberger
89ac126180 fix: mark plan compaction event delivery fire-and-forget (#76651) (thanks @simplyclever914) 2026-05-03 14:43:13 +01:00
Peter Steinberger
63ff9244c7 fix: allow async compaction event delivery (#76651) (thanks @simplyclever914) 2026-05-03 14:43:13 +01:00
Peter Steinberger
825ad57513 fix: deliver compaction hook messages (#76651) (thanks @simplyclever914) 2026-05-03 14:43:13 +01:00
simplyclever914
9de06e3dee Format compaction PR changes and validate locally
- apply oxfmt formatting to changed files
- keep replay-safety, typed hook, and changelog fixes
- validated targeted tests, oxfmt check, and check:changed locally
2026-05-03 14:43:13 +01:00
simplyclever914
cb4f4f5f3a Address compaction review feedback
- gate the compaction continuation retry on existing replay side-effect metadata
- type the bundled compaction notifier hook without explicit any
- add an Unreleased changelog entry
2026-05-03 14:43:13 +01:00
simplyclever914
e84ceb47f6 Make compaction visible and resume final replies
When an automatic compaction happens mid-turn, chat users currently see a long stall and the run can finish without a final visible answer.

This adds an optional bundled compaction notifier hook and a one-shot compacted-transcript continuation retry when a compaction produced no user-visible final payload.
2026-05-03 14:43:13 +01:00
Peter Steinberger
d0f0fe97a6 chore: move root tool configs 2026-05-03 14:42:56 +01:00
Alex Knight
349106065a fix(tools): allow no-tool llm-task runs (#76686)
Summary:
- The branch marks runtime `toolsAllow` sources as enforceable during disabled-tool runs, filters inherited allowlist sources in the guard, adds focused guard coverage, and updates the changelog.
- Reproducibility: yes. The linked report gives concrete config and invocation steps, and source inspection on ... sables tools while the guard still treats inherited allowlist sources plus zero callable tools as an error.

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

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

Prepared head SHA: 9c3e5f773e
Review: https://github.com/openclaw/openclaw/pull/76686#issuecomment-4366216196

Co-authored-by: Alex Knight <15041791+amknight@users.noreply.github.com>
2026-05-03 13:42:32 +00:00
Peter Steinberger
1f1686bc4d chore: reduce root cleanup files 2026-05-03 14:38:13 +01:00
Peter Steinberger
8c95664e55 fix: keep runtime model auth alias after build
(cherry picked from commit f352caf07e)
2026-05-03 14:37:17 +01:00
Peter Steinberger
82dd68d299 build: avoid ambiguous runtime aliases
(cherry picked from commit c96e62d5ab)
2026-05-03 14:37:17 +01:00
Peter Steinberger
422f1fe9bb fix: align postpublish verification with external plugins
(cherry picked from commit abe2b294ae)
2026-05-03 14:37:17 +01:00
Peter Steinberger
f789f8e394 ci: fix release publish repo context
(cherry picked from commit 202b7fd597)
2026-05-03 14:37:17 +01:00
Peter Steinberger
6d9df1f25a ci: retry performance report publishes 2026-05-03 14:32:35 +01:00
Peter Steinberger
db5f96cdc1 refactor: remove history limit alias 2026-05-03 14:30:52 +01:00
Peter Steinberger
928c70fb6b perf(gateway): trim startup watcher imports 2026-05-03 14:30:36 +01:00
Peter Steinberger
399d7f6178 fix(agents): forward model maxTokens by default 2026-05-03 14:30:16 +01:00
Peter Steinberger
41bbc4c048 test(plugins): cover pinned npm installs 2026-05-03 14:27:58 +01:00
Lucenx9
ff29b5d599 docs(changelog): add npm hardening attribution 2026-05-03 14:27:58 +01:00
Lucenx9
a104dc8795 docs(changelog): note npm plugin install hardening 2026-05-03 14:27:58 +01:00
Lucenx9
fb3ee066d8 fix(plugins): pin npm plugin installs 2026-05-03 14:27:58 +01:00
Peter Steinberger
97cdd73aa6 refactor: remove bootstrap warning alias 2026-05-03 14:24:17 +01:00
Peter Steinberger
8f20d03373 fix: cap idle timeouts without completed progress (#76345)
Co-authored-by: adhiraj <vacationadhi@gmail.com>
2026-05-03 14:12:11 +01:00
Alex Knight
ccce342a24 fix(agents): keep web_search runtime providers visible (#76685)
Summary:
- The PR changes the agent web_search wrapper to keep runtime provider discovery enabled when runtime metadata is absent, adds focused regression coverage, and records an unreleased changelog fix.
- Reproducibility: yes. at source level: current main passes preferRuntimeProviders: false when runtime web-se ... d issue supplies live Brave CLI-vs-agent evidence; this read-only review did not rerun a live Gateway call.

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

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

Prepared head SHA: e7f379c68d
Review: https://github.com/openclaw/openclaw/pull/76685#issuecomment-4366216450

Co-authored-by: Alex Knight <15041791+amknight@users.noreply.github.com>
2026-05-03 13:11:35 +00:00
Peter Steinberger
a4a4cac8e9 ci: split slow CI shards 2026-05-03 14:11:04 +01:00
Peter Steinberger
c02bf2f399 refactor: share transcript append path 2026-05-03 14:10:09 +01:00
Peter Steinberger
fc570d0e58 ci: use current performance report helpers 2026-05-03 14:06:21 +01:00
Peter Steinberger
68b56108f6 fix(plugins): tighten shadowed install diagnostics 2026-05-03 13:59:21 +01:00
Yuyummy
f73a614d66 fix(plugins): warn when npm install is shadowed 2026-05-03 13:59:21 +01:00
1665 changed files with 83320 additions and 11421 deletions

View File

@@ -1,420 +0,0 @@
---
name: blacksmith-testbox
description: Run Blacksmith Testbox for CI-parity checks, secrets, hosted services, migrations, or builds local cannot reproduce.
---
# Blacksmith Testbox
## Scope
Use Testbox when you need remote CI parity, injected secrets, hosted services,
or an OS/runtime image that your local machine cannot provide cheaply.
For OpenClaw, Crabbox is a supported alternative when Blacksmith is unavailable
or owned cloud capacity is preferable.
Do not default to Testbox for every local test/build loop. If the repo has
documented local commands for normal iteration, use those first so you keep
warm caches, local build state, and fast feedback.
Testbox is the expensive path. Reach for it deliberately.
OpenClaw maintainers can opt into Testbox-first validation by setting
`OPENCLAW_TESTBOX=1` in their environment or standing agent rules. This mode is
maintainers-only and requires Blacksmith access.
When `OPENCLAW_TESTBOX=1` is set in OpenClaw:
- Pre-warm a Testbox early for longer, wider, or uncertain work.
- Prefer Testbox for `pnpm` gates, e2e, package-like proof, and broad suites.
- Reuse the same Testbox ID for every run command in the same task/session.
- Use local commands only when the task explicitly sets
`OPENCLAW_LOCAL_CHECK_MODE=throttled|full`, or when the user asks for local
proof.
## Install the CLI
If `blacksmith` is not installed, install it:
curl -fsSL https://get.blacksmith.sh | sh
For the canary channel (bleeding-edge):
BLACKSMITH_CHANNEL=canary sh -c 'curl -fsSL https://get.blacksmith.sh | sh'
Then authenticate:
blacksmith auth login
## Agent-triggered browser auth (non-interactive)
When an agent needs to ensure the user is authenticated before running testbox
commands (e.g. warmup, run), use browser-based auth with non-interactive mode.
This opens the browser for the user to sign in; the agent does not interact with
the browser. The org selector in the dashboard is skipped, so the user only sees
the sign-in flow.
**Required command** (`--organization` is required with `--non-interactive`):
blacksmith auth login --non-interactive --organization <org-slug>
The org slug can come from `BLACKSMITH_ORG` env var or the `--org` global flag.
If neither is set, the agent should use the project's known org (e.g. from repo
config or user context). Example:
blacksmith auth login --non-interactive --organization acme-corp
blacksmith --org acme-corp auth login --non-interactive --organization acme-corp
**Flow**: The CLI starts a local callback server, opens the browser to the
dashboard auth page, and blocks for up to 2 minutes. The user completes sign-in
and authorization in the browser. The dashboard redirects to localhost with the
token; the CLI saves credentials and exits. The agent then proceeds.
**Do not use** `--api-token` for this flow — that is for headless/token-based
auth. This skill focuses on browser-based auth when the user prefers signing in
via the web UI.
Optional flags:
- `--dashboard-url <url>` — Override dashboard URL (e.g. for staging)
## Decide first: local or Testbox
Before warming anything up, check the repo's own instructions.
Prefer local commands when:
- the repo documents a supported local test/build workflow
- you are iterating on unit tests, lint, typecheck, formatting, or other
local-only validation
- the value comes from warm local caches and fast repeat runs
- the command does not need remote secrets, hosted services, or CI-only images
Prefer Testbox when:
- the repo explicitly requires CI-parity or remote validation
- the command needs secrets, service containers, or provisioned infra
- you are reproducing CI-only failures
- you need the exact workflow image/job environment from GitHub Actions
For OpenClaw specifically, normal local iteration stays local unless maintainer
Testbox mode is enabled with `OPENCLAW_TESTBOX=1`:
- `pnpm check:changed`
- `pnpm test:changed`
- `pnpm test <path-or-filter>`
- `pnpm test:serial`
- `pnpm build`
If `OPENCLAW_TESTBOX=1` is enabled, run those same repo commands inside the
warm Testbox. If the user wants laptop-friendly local proof for one command, use
the explicit escape hatch `OPENCLAW_LOCAL_CHECK_MODE=throttled`.
For installable-package product proof, prefer the GitHub `Package Acceptance`
workflow over an ad hoc Testbox command. It resolves one package candidate
(`source=npm`, `source=ref`, `source=url`, or `source=artifact`), uploads it as
`package-under-test`, and runs the reusable Docker E2E lanes against that exact
tarball on GitHub/Blacksmith runners. Use `workflow_ref` for the trusted
workflow/harness code and `package_ref` for the source ref to pack when testing
an older trusted branch, tag, or SHA.
## Setup: Warmup before coding
If you decided Testbox is warranted, warm one up early. This returns an ID
instantly and boots the CI environment in the background while you work:
blacksmith testbox warmup ci-check-testbox.yml
# → tbx_01jkz5b3t9...
Save this ID in the current session. You need it for every `run` command.
Treat `blacksmith testbox list` as diagnostics, not a reusable work queue.
Listed boxes can be visible at the org/repo level while still being unusable or
stale for the current local agent lane.
For OpenClaw maintainer Testbox mode, pre-warm at the start of longer or wider
tasks:
blacksmith testbox warmup ci-check-testbox.yml --ref main --idle-timeout 90
pnpm testbox:claim --id <ID>
Use the build-artifact warmup when e2e/package/build proof benefits from seeded
`dist/`, `dist-runtime/`, and build-all caches:
blacksmith testbox warmup ci-build-artifacts-testbox.yml --ref main --idle-timeout 90
pnpm testbox:claim --id <ID>
Warmup dispatches a GitHub Actions workflow that provisions a VM with the
full CI environment: dependencies installed, services started, secrets
injected, and a clean checkout of the repo at the default branch.
In OpenClaw, raw commit SHAs are not reliable dispatch refs for `warmup --ref`;
use a branch or tag. The build-artifact workflow resolves `openclaw@beta` and
`openclaw@latest` to SHA cache keys internally.
Options:
--ref <branch|tag> Git ref to dispatch against (default: repo's default branch)
--job <name> Specific job within the workflow (if it has multiple)
--idle-timeout <min> Idle timeout in minutes (default: 30)
## CRITICAL: Always run from the repo root
ALWAYS invoke `blacksmith testbox` commands from the **root of the git
repository**. The CLI syncs the current working directory to the testbox
using rsync with `--delete`. If you run from a subdirectory (e.g.
`cd backend && blacksmith testbox run ...`), rsync will mirror only that
subdirectory and **delete everything else** on the testbox — wiping other
directories like `dashboard/`, `cli/`, etc.
# CORRECT — run from repo root, use paths in the command
blacksmith testbox run --id <ID> "cd backend && php artisan test"
blacksmith testbox run --id <ID> "cd dashboard && npm test"
# WRONG — do NOT cd into a subdirectory before invoking the CLI
cd backend && blacksmith testbox run --id <ID> "php artisan test"
If your shell is in a subdirectory, `cd` back to the repo root first:
cd "$(git rev-parse --show-toplevel)"
blacksmith testbox run --id <ID> "cd backend && php artisan test"
## Running commands
blacksmith testbox run --id <ID> "<command>"
The `run` command automatically waits for the testbox to become ready if
it is still booting, so you can call `run` immediately after warmup without
needing to check status first.
In OpenClaw, prefer the guarded runner wrapper so stale/reused ids fail before
the Blacksmith CLI spends time syncing or emits a confusing missing-key error:
pnpm testbox:run --id <ID> -- "OPENCLAW_TESTBOX=1 pnpm check:changed"
The wrapper refuses to run when the local per-Testbox key is missing or when the
id was not claimed by this OpenClaw checkout with `pnpm testbox:claim --id
<ID>`. Treat that as the expected remediation, not as a GitHub account or
normal SSH-key problem. A local key alone is not enough; a ready box may still
carry stale rsync state from another lane.
If the agent crashes, the remote box relies on Blacksmith's idle timeout. The
local OpenClaw claim marker is not deleted automatically, so the wrapper treats
claims older than 12 hours as stale. Override only for intentional long-running
work with `OPENCLAW_TESTBOX_CLAIM_TTL_MINUTES=<minutes>`.
Before spending a broad gate on a manually assembled command, you can also run:
pnpm testbox:sanity -- --id <ID>
## Downloading files from a testbox
Use the `download` command to retrieve files or directories from a running
testbox to your local machine. This is useful for fetching build artifacts,
test results, coverage reports, or any output generated on the testbox.
blacksmith testbox download --id <ID> <remote-path> [local-path]
The remote path is relative to the testbox working directory (same as `run`).
If no local path is specified, the file is saved to the current directory
using the same base name.
To download a directory, append a trailing `/` to the remote path — this
triggers recursive mode:
# Download a single file
blacksmith testbox download --id <ID> coverage/report.html
# Download a file to a specific local path
blacksmith testbox download --id <ID> build/output.tar.gz ./output.tar.gz
# Download an entire directory
blacksmith testbox download --id <ID> test-results/ ./results/
Options:
--ssh-private-key <path> Path to SSH private key (if warmup used --ssh-public-key)
## How file sync works
Understanding this model is critical for using Testbox correctly.
When you call `run`, the CLI performs a **delta sync** of your local changes
to the remote testbox before executing your command:
1. The testbox VM starts from a clean `actions/checkout` at the warmup ref.
The workflow's setup steps (e.g. `npm install`, `pip install`, `composer install`)
run during warmup and populate dependency directories on the remote VM.
2. On each `run`, the CLI uses **git** to detect which files changed locally
since the last sync. It syncs ONLY tracked files and untracked non-ignored
files (i.e. files that `git ls-files` reports).
3. **`.gitignore`'d directories are never synced.** This means directories
like `node_modules/`, `vendor/`, `.venv/`, `build/`, `dist/`, etc. are
NOT transferred from your local machine. The testbox uses its own copies
of those directories, populated during the warmup workflow steps.
4. If nothing has changed since the last sync (same git commit and working
tree state), the sync is skipped entirely for speed.
### Why this matters
- **Changing dependencies**: If you modify `package.json`, `requirements.txt`,
`composer.json`, `go.mod`, or similar dependency manifests, the lock/manifest
file will be synced but the actual dependency directory will NOT. You must
re-run the install command on the testbox:
blacksmith testbox run --id <ID> "npm install && npm test"
blacksmith testbox run --id <ID> "pip install -r requirements.txt && pytest"
blacksmith testbox run --id <ID> "composer install && phpunit"
- **Generated/build artifacts**: If your tests depend on a build step (e.g.
`npm run build`, `make`), and you changed source files that affect the build
output, re-run the build on the testbox before testing.
- **New untracked files**: New files you create locally ARE synced (as long as
they are not gitignored). You do not need to `git add` them first.
- **Deleted files**: Files you delete locally are also deleted on the remote
testbox. The sync model keeps the remote in lockstep with your local managed
file set.
## CRITICAL: Do not ban local tests
Do not assume local validation is forbidden. Many repos intentionally invest in
fast, warm local loops, and forcing every run through Testbox destroys that
advantage.
Use Testbox for the checks that actually need it: remote parity, secrets,
services, CI-only runners, or reproducibility against the workflow image.
If the repo says local tests/builds are the normal path, follow the repo.
OpenClaw maintainer exception: if `OPENCLAW_TESTBOX=1` is set by the user or
agent environment, treat Testbox as the normal validation path for this repo.
Use `OPENCLAW_LOCAL_CHECK_MODE=throttled|full` as the explicit local escape
hatch.
## When to use
Use Testbox when:
- running database migrations or destructive environment checks
- running commands that depend on secrets or environment variables not present locally
- reproducing CI-only failures or validating against the workflow image
- validating behavior that needs provisioned services or remote runners
- doing a final parity check before commit/push when the repo or user wants that
Trim that list based on repo guidance. If the repo documents supported local
tests/builds, prefer local for routine iteration and keep Testbox for the
checks that need parity or remote state.
## Workflow
1. Decide whether the repo's local loop is the right default. For OpenClaw,
`OPENCLAW_TESTBOX=1` makes Testbox the maintainer default.
2. If Testbox is warranted, warm up early:
`blacksmith testbox warmup ci-check-testbox.yml --ref main --idle-timeout 90` → save the ID,
then `pnpm testbox:claim --id <ID>`
3. Write code while the testbox boots in the background.
4. Run the remote command when needed:
`pnpm testbox:run --id <ID> -- "OPENCLAW_TESTBOX=1 pnpm check:changed"`
5. If tests fail, fix code and re-run against the same warm box.
6. If you changed dependency manifests (package.json, etc.), prepend
the install command: `blacksmith testbox run --id <ID> "npm install && npm test"`
7. If a narrow PR reports a full sync or the box was reused/expired, sanity
check the remote copy before a slow gate:
`pnpm testbox:run --id <ID> -- "pnpm testbox:sanity"`.
If it reports missing root files or mass tracked deletions, stop the box and
warm a fresh one. Use `OPENCLAW_TESTBOX_ALLOW_MASS_DELETIONS=1` only for an
intentional large deletion PR.
8. If you need artifacts (coverage reports, build outputs, etc.), download them:
`blacksmith testbox download --id <ID> coverage/ ./coverage/`
9. Once green, commit and push.
## OpenClaw full test suite
For OpenClaw, use the repo package manager and the measured stable full-suite
profile below. It keeps six Vitest project shards active while limiting each
shard to one worker to avoid worker OOMs on Testbox:
blacksmith testbox run --id <ID> "env NODE_OPTIONS=--max-old-space-size=4096 OPENCLAW_TEST_PROJECTS_PARALLEL=6 OPENCLAW_VITEST_MAX_WORKERS=1 pnpm test"
Observed full-suite time on Blacksmith Testbox is about 3-4 minutes:
- 173-180s on a warmed box
- 219s on a fresh 32-vCPU box
When validating before commit/push in maintainer Testbox mode, run
`pnpm check:changed` inside the warmed box first when appropriate, then the full
suite with the profile above if broad confidence is needed.
Run `pnpm testbox:sanity` inside the warmed box before the broad command when
the sync looks suspicious. It checks that root files such as `pnpm-lock.yaml`
still exist and fails on 200 or more tracked deletions. That catches stale or
corrupted rsync state before dependency install or Vitest failures hide the real
problem.
## Examples
blacksmith testbox warmup ci-check-testbox.yml
# → tbx_01jkz5b3t9...
# Run tests
blacksmith testbox run --id <ID> "npm test -- --testPathPattern=handler.test"
blacksmith testbox run --id <ID> "go test ./pkg/api/... -run TestHandler -v"
blacksmith testbox run --id <ID> "python -m pytest tests/test_api.py -k test_auth"
# Re-install deps after changing package.json, then test
blacksmith testbox run --id <ID> "npm install && npm test"
# Build and test
blacksmith testbox run --id <ID> "npm run build && npm test"
# Download artifacts from the testbox
blacksmith testbox download --id <ID> coverage/lcov-report/ ./coverage/
blacksmith testbox download --id <ID> build/output.tar.gz
## Waiting for the testbox to be ready
The `run` command automatically waits for the testbox, so explicit waiting is
usually unnecessary. If you do need to check readiness separately (e.g. before
a series of runs), use the `--wait` flag. Do NOT use a sleep-and-recheck loop.
Correct: block until ready with a timeout:
blacksmith testbox status --id <ID> --wait [--wait-timeout 5m]
Wrong: never use sleep + status in a loop:
# BAD — do not do this
sleep 30 && blacksmith testbox status --id <ID>
while ! blacksmith testbox status --id <ID> | grep ready; do sleep 5; done
`--wait` polls the status and exits as soon as the testbox is ready (or when the
timeout is reached). Default timeout is 5m; use `--wait-timeout` for longer
(e.g. `10m`, `1h`).
## Managing testboxes
# Check status of a specific testbox
blacksmith testbox status --id <ID>
# List all active testboxes for the current repo
blacksmith testbox list
# Stop a testbox when you're done (frees resources)
blacksmith testbox stop --id <ID>
Testboxes automatically shut down after being idle (default: 30 minutes).
If you need a longer session, increase the timeout at warmup time. For OpenClaw
maintainer work, use 90 minutes for long-running sessions:
blacksmith testbox warmup ci-check-testbox.yml --idle-timeout 90
blacksmith testbox warmup ci-build-artifacts-testbox.yml --idle-timeout 90
## With options
blacksmith testbox warmup ci-check-testbox.yml --ref main
blacksmith testbox warmup ci-check-testbox.yml --idle-timeout 90
blacksmith testbox run --id <ID> "go test ./..."

View File

@@ -1,54 +1,272 @@
---
name: crabbox
description: Use Crabbox for OpenClaw remote Linux validation, warmed reusable boxes, GitHub Actions hydration, sync timing, logs, results, caches, and lease cleanup.
description: Use Crabbox for OpenClaw remote Linux validation. Default to Blacksmith Testbox; includes direct Blacksmith and owned AWS/Hetzner fallback notes when Crabbox fails.
---
# Crabbox
Use Crabbox when OpenClaw needs remote Linux proof on owned capacity, a large
runner class, reusable warm state, or a Blacksmith alternative.
Use Crabbox when OpenClaw needs remote Linux proof for broad tests, CI-parity
checks, secrets, hosted services, Docker/E2E/package lanes, warmed reusable
boxes, sync timing, logs/results, cache inspection, or lease cleanup.
## Before Running
Default backend: `blacksmith-testbox`. The separate `blacksmith-testbox` skill
has been removed; this skill owns both the normal Crabbox path and the direct
Blacksmith fallback playbook.
## First Checks
- Run from the repo root. Crabbox sync mirrors the current checkout.
- Prefer local targeted tests for tight edit loops.
- Prefer Blacksmith Testbox when the task explicitly asks for Blacksmith or a
Blacksmith-specific CI comparison.
- Use Crabbox for broad OpenClaw gates when owned AWS/Hetzner capacity is the
right remote lane.
- Check `.crabbox.yaml` for repo defaults before adding flags.
- Sanity-check the selected binary before remote work. OpenClaw scripts prefer
`../crabbox/bin/crabbox` when present; the user PATH shim can be stale:
`command -v crabbox; ../crabbox/bin/crabbox --version; ../crabbox/bin/crabbox --help | sed -n '1,90p'`.
- Install with `brew install openclaw/tap/crabbox`; auth is required before use:
`printf '%s' "$CRABBOX_COORDINATOR_TOKEN" | crabbox login --url https://crabbox.openclaw.ai --provider aws --token-stdin`.
- On macOS the user config is `~/Library/Application Support/crabbox/config.yaml`;
it must include `broker.url`, `broker.token`, and usually `provider: aws`.
## OpenClaw Flow
AWS/owned-capacity flow for `pnpm` tests:
- Check the wrapper and providers before remote work:
```sh
pnpm crabbox:warmup -- --idle-timeout 90m
pnpm crabbox:warmup -- --provider aws --class beast --market on-demand --idle-timeout 90m
pnpm crabbox:hydrate -- --id <cbx_id-or-slug>
pnpm crabbox:run -- --id <cbx_id-or-slug> --timing-json --shell -- "env NODE_OPTIONS=--max-old-space-size=4096 OPENCLAW_TEST_PROJECTS_PARALLEL=6 OPENCLAW_VITEST_MAX_WORKERS=1 OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=900000 pnpm test:changed"
command -v crabbox
../crabbox/bin/crabbox --version
pnpm crabbox:run -- --help | sed -n '1,120p'
```
Blacksmith-backed Crabbox flow can delegate setup to the Testbox workflow:
- OpenClaw scripts prefer `../crabbox/bin/crabbox` when present. The user PATH
shim can be stale.
- Check `.crabbox.yaml` for repo defaults, but override provider explicitly.
Even if config still says AWS, maintainer validation should normally pass
`--provider blacksmith-testbox`.
- Prefer local targeted tests for tight edit loops. Broad gates belong remote.
## macOS And Windows Targets
Use these only when the task needs an existing non-Linux host. OpenClaw broad
validation still defaults to `blacksmith-testbox`.
Crabbox supports static SSH targets:
```sh
pnpm crabbox:run -- --provider blacksmith-testbox --blacksmith-org openclaw --blacksmith-workflow .github/workflows/ci-check-testbox.yml --blacksmith-job check --blacksmith-ref main --idle-timeout 90m --timing-json --shell -- "env NODE_OPTIONS=--max-old-space-size=4096 OPENCLAW_TEST_PROJECTS_PARALLEL=6 OPENCLAW_VITEST_MAX_WORKERS=1 OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=900000 pnpm test:changed"
../crabbox/bin/crabbox run --provider ssh --target macos --static-host mac-studio.local -- xcodebuild test
../crabbox/bin/crabbox run --provider ssh --target windows --windows-mode normal --static-host win-dev.local -- pwsh -NoProfile -Command "dotnet test"
../crabbox/bin/crabbox run --provider ssh --target windows --windows-mode wsl2 --static-host win-dev.local -- pnpm test
```
- `target=macos` and `target=windows --windows-mode wsl2` use the POSIX SSH,
bash, Git, rsync, and tar contract.
- Native Windows uses OpenSSH, PowerShell, Git, and tar; sync is manifest tar
archive transfer into `static.workRoot`.
- `crabbox actions hydrate/register` are Linux-only today; use plain
`crabbox run` loops for static macOS and Windows hosts.
- Live proof needs a reachable, operator-managed SSH host. Without one, verify
with `../crabbox/bin/crabbox run --help`, config/flag tests, and the Crabbox
Go test suite.
## Default Blacksmith Backend
Use this for `pnpm check`, `pnpm check:changed`, `pnpm test`,
`pnpm test:changed`, Docker/E2E/live/package gates, or anything likely to fan
out across many Vitest projects.
Changed gate:
```sh
pnpm crabbox:run -- --provider blacksmith-testbox \
--blacksmith-org openclaw \
--blacksmith-workflow .github/workflows/ci-check-testbox.yml \
--blacksmith-job check \
--blacksmith-ref main \
--idle-timeout 90m \
--ttl 240m \
--timing-json \
--shell -- \
"env CI=1 NODE_OPTIONS=--max-old-space-size=4096 OPENCLAW_TEST_PROJECTS_PARALLEL=6 OPENCLAW_VITEST_MAX_WORKERS=1 OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=900000 pnpm test:changed"
```
Full suite:
```sh
pnpm crabbox:run -- --provider blacksmith-testbox \
--blacksmith-org openclaw \
--blacksmith-workflow .github/workflows/ci-check-testbox.yml \
--blacksmith-job check \
--blacksmith-ref main \
--idle-timeout 90m \
--ttl 240m \
--timing-json \
--shell -- \
"env CI=1 NODE_OPTIONS=--max-old-space-size=4096 OPENCLAW_TEST_PROJECTS_PARALLEL=6 OPENCLAW_VITEST_MAX_WORKERS=1 OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=900000 pnpm test"
```
Focused rerun:
```sh
pnpm crabbox:run -- --provider blacksmith-testbox \
--blacksmith-org openclaw \
--blacksmith-workflow .github/workflows/ci-check-testbox.yml \
--blacksmith-job check \
--blacksmith-ref main \
--idle-timeout 90m \
--ttl 240m \
--timing-json \
--shell -- \
"env CI=1 NODE_OPTIONS=--max-old-space-size=4096 OPENCLAW_VITEST_MAX_WORKERS=1 OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=900000 pnpm test <path-or-filter>"
```
Read the JSON summary. Useful fields:
- `provider`: should be `blacksmith-testbox`
- `leaseId`: `tbx_...`
- `syncDelegated`: should be `true`
- `commandMs` / `totalMs`
- `exitCode`
Crabbox should stop one-shot Blacksmith Testboxes automatically after the run.
Verify cleanup when a run fails, is interrupted, or the command output is
unclear:
```sh
blacksmith testbox list
```
## Reuse And Keepalive
For most Blacksmith-backed Crabbox calls, one-shot is enough. Use reuse only
when you need multiple manual commands on the same hydrated box.
If Crabbox returns a reusable id or you intentionally keep a lease:
```sh
pnpm crabbox:run -- --provider blacksmith-testbox --id <tbx_id> --no-sync --timing-json --shell -- "pnpm test <path>"
```
Stop boxes you created before handoff:
```sh
pnpm crabbox:stop -- <id-or-slug>
blacksmith testbox stop --id <tbx_id>
```
## If Crabbox Fails
Keep the fallback narrow. First decide whether the failure is Crabbox itself,
Blacksmith/Testbox, repo hydration, sync, or the test command.
Fast checks:
```sh
command -v crabbox
../crabbox/bin/crabbox --version
crabbox run --provider blacksmith-testbox --help | sed -n '1,140p'
command -v blacksmith
blacksmith --version
blacksmith testbox list
```
Common Crabbox-only failures:
- Provider missing or old CLI: use `../crabbox/bin/crabbox` from the sibling
repo, or update/install Crabbox before retrying.
- Bad local config: pass `--provider blacksmith-testbox` plus explicit
`--blacksmith-*` flags instead of relying on `.crabbox.yaml`.
- Slug/claim confusion: use the raw `tbx_...` id, or run one-shot without
`--id`.
- Sync/timing bug: add `--debug --timing-json`; capture the final JSON and the
printed Actions URL.
- Cleanup uncertainty: run `blacksmith testbox list` and stop only boxes you
created.
If Crabbox cannot dispatch, sync, attach, or stop but Blacksmith itself works,
use direct Blacksmith from the repo root:
```sh
blacksmith testbox warmup ci-check-testbox.yml --ref main --idle-timeout 90
blacksmith testbox run --id <tbx_id> "env CI=1 NODE_OPTIONS=--max-old-space-size=4096 OPENCLAW_TEST_PROJECTS_PARALLEL=6 OPENCLAW_VITEST_MAX_WORKERS=1 OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=900000 pnpm test:changed"
blacksmith testbox stop --id <tbx_id>
```
Direct full suite:
```sh
blacksmith testbox run --id <tbx_id> "env CI=1 NODE_OPTIONS=--max-old-space-size=4096 OPENCLAW_TEST_PROJECTS_PARALLEL=6 OPENCLAW_VITEST_MAX_WORKERS=1 OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=900000 pnpm test"
```
Auth fallback, only when `blacksmith` says auth is missing:
```sh
blacksmith auth login --non-interactive --organization openclaw
```
Raw Blacksmith footguns:
- Run from repo root. The CLI syncs the current directory.
- Save the returned `tbx_...` id in the session.
- Reuse that id for focused reruns; stop it before handoff.
- Raw commit SHAs are not reliable `warmup --ref` refs; use a branch or tag.
- Treat `blacksmith testbox list` as cleanup diagnostics, not a shared reusable
queue.
Escalate to owned AWS/Hetzner only when Blacksmith is down, quota-limited,
missing the needed environment, or owned capacity is the explicit goal. Use the
Owned Cloud Fallback section below.
## Blacksmith Backend Notes
Crabbox Blacksmith backend delegates setup to:
- org: `openclaw`
- workflow: `.github/workflows/ci-check-testbox.yml`
- job: `check`
- ref: `main` unless testing a branch/tag intentionally
The hydration workflow owns checkout, Node/pnpm setup, dependency install,
secrets, ready marker, and keepalive. Crabbox owns dispatch, sync, SSH command
execution, timing, logs/results, and cleanup.
Minimal direct Blacksmith fallback, from repo root:
```sh
blacksmith testbox warmup ci-check-testbox.yml --ref main --idle-timeout 90
blacksmith testbox run --id <tbx_id> "env CI=1 NODE_OPTIONS=--max-old-space-size=4096 OPENCLAW_TEST_PROJECTS_PARALLEL=6 OPENCLAW_VITEST_MAX_WORKERS=1 pnpm test:changed"
blacksmith testbox stop --id <tbx_id>
```
Use direct Blacksmith only when Crabbox is the broken layer and Blacksmith
itself still works. Prefer direct `blacksmith testbox list` for cleanup
diagnostics, not as a reusable work queue.
Important Blacksmith footguns:
- Always run from repo root. The CLI syncs the current directory.
- Raw commit SHAs are not reliable `warmup --ref` refs; use a branch or tag.
- If auth is missing and browser auth is acceptable:
```sh
blacksmith auth login --non-interactive --organization openclaw
```
## Owned Cloud Fallback
Use AWS/Hetzner only when Blacksmith is down, quota-limited, missing the needed
environment, or owned capacity is explicitly the goal.
```sh
pnpm crabbox:warmup -- --provider aws --class beast --market on-demand --idle-timeout 90m
pnpm crabbox:hydrate -- --id <cbx_id-or-slug>
pnpm crabbox:run -- --id <cbx_id-or-slug> --timing-json --shell -- "env NODE_OPTIONS=--max-old-space-size=4096 OPENCLAW_TEST_PROJECTS_PARALLEL=6 OPENCLAW_VITEST_MAX_WORKERS=1 OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=900000 pnpm test:changed"
pnpm crabbox:stop -- <cbx_id-or-slug>
```
## Useful Commands
Install/auth for owned Crabbox if needed:
```sh
brew install openclaw/tap/crabbox
printf '%s' "$CRABBOX_COORDINATOR_TOKEN" | crabbox login --url https://crabbox.openclaw.ai --provider aws --token-stdin
```
macOS config lives at:
```text
~/Library/Application Support/crabbox/config.yaml
```
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`.
## Diagnostics
```sh
crabbox status --id <id-or-slug> --wait
@@ -59,29 +277,30 @@ crabbox logs <run_id>
crabbox results <run_id>
crabbox cache stats --id <id-or-slug>
crabbox ssh --id <id-or-slug>
blacksmith testbox list
```
Use `--debug` on `run` when measuring sync timing.
Use `--timing-json` on warmup, hydrate, and run when comparing AWS and
blacksmith-testbox timings.
Use `--market spot|on-demand` on AWS warmup or one-shot run when testing quota
or capacity behavior without changing `.crabbox.yaml`.
Use `--timing-json` on warmup, hydrate, and run when comparing backends.
Use `--market spot|on-demand` only on AWS warmup/one-shot runs.
## Hydration Boundary
## Failure Triage
`.github/workflows/crabbox-hydrate.yml` is repo-specific on purpose. It owns
OpenClaw checkout, setup-node, pnpm setup, provider env hydration, ready marker,
and keepalive. Crabbox owns runner registration, workflow dispatch, SSH sync,
command execution, logs/results, local lease claims, and idle cleanup.
- Crabbox cannot find provider: verify `../crabbox/bin/crabbox --help` lists
`blacksmith-testbox`; update Crabbox before falling back.
- Hydration stuck or failed: open the printed GitHub Actions run URL and inspect
the hydration step.
- Sync failed: rerun with `--debug`; check changed-file count and whether the
checkout is dirty.
- Command failed: rerun only the failing shard/file first. Do not rerun a full
suite until the focused failure is understood.
- Cleanup uncertain: `blacksmith testbox list`; stop owned `tbx_...` leases you
created.
- Crabbox broken but Blacksmith works: use the direct Blacksmith fallback above,
then file/fix the Crabbox issue.
Do not add OpenClaw-specific setup to Crabbox. Put repo setup in the hydration
workflow and generic lease/sync behavior in Crabbox.
## Boundary
## Cleanup
Crabbox has coordinator-owned idle expiry and local lease claims, so OpenClaw
does not need a custom ledger. Default idle timeout is 30 minutes unless config
or flags set a different value. Still stop boxes you created when done.
If `crabbox list` prints `orphan=no-active-lease`, treat it as an operator
review hint; do not delete `keep=true` machines without checking provider and
coordinator state.
Do not add OpenClaw-specific setup to Crabbox itself. Put repo setup in the
hydration workflow and keep Crabbox generic around lease, sync, command
execution, logs/results, timing, and cleanup.

View File

@@ -14,7 +14,7 @@ Use this skill for Parallels guest workflows and smoke interpretation. Do not lo
- Stable `2026.3.12` pre-upgrade diagnostics may require a plain `gateway status --deep` fallback.
- Treat `precheck=latest-ref-fail` on that stable pre-upgrade lane as baseline, not automatically a regression.
- Pass `--json` for machine-readable summaries.
- Per-phase logs land under `/tmp/openclaw-parallels-*`.
- Per-phase logs land under `.artifacts/parallels/openclaw-parallels-*` by default. Override with `OPENCLAW_PARALLELS_ARTIFACT_ROOT` when a run needs another artifact volume.
- Do not run local and gateway agent turns in parallel on the same fresh workspace or session.
- Hard-cap every top-level Parallels lane with host `timeout --foreground` (or `gtimeout --foreground` if that is the available binary) so a stalled install, snapshot switch, or `prlctl exec` transport cannot consume the rest of the testing window. Defaults:
- macOS: `75m`
@@ -68,8 +68,16 @@ Use this skill for Parallels guest workflows and smoke interpretation. Do not lo
- The Windows same-guest update helper should write stage markers to its log before long steps like tgz download and `npm install -g` so the outer progress monitor does not sit on `waiting for first log line` during healthy but quiet installs.
- Linux same-guest update verification should also export `HOME=/root`, pass `OPENAI_API_KEY` via `prlctl exec ... /usr/bin/env`, and use `openclaw agent --local`; the fresh Linux baseline does not rely on persisted gateway credentials.
- The npm-update wrapper now prints per-lane progress from the nested log files. If a lane still looks stuck, inspect the nested logs in `runDir` first (`macos-fresh.log`, `windows-fresh.log`, `linux-fresh.log`, `macos-update.log`, `windows-update.log`, `linux-update.log`) instead of assuming the outer wrapper hung.
- If the wrapper fails a lane, read the auto-dumped tail first, then the full nested lane log under `/tmp/openclaw-parallels-npm-update.*`.
- Each run writes both `summary.json` and `summary.md`; read the markdown first for quick human triage, then the JSON/timings for automation.
- For full beta validation after a tag is published, prefer one command:
- `timeout --foreground 150m pnpm test:parallels:npm-update -- --beta-validation beta3 --json`
This resolves `beta3` to the latest `*-beta.3` version, runs latest->that-version same-guest update coverage, and then runs fresh install smoke for that exact published target on the same selected OS matrix. Use `--platform macos|windows|linux` to narrow reruns.
- For beta 4 npm validation with agent turns, the known-good shape is:
- `gtimeout --foreground 150m pnpm test:parallels:npm-update -- --beta-validation beta4 --model openai/gpt-5.4 --json`
Prefer the explicit `beta4` alias over `openclaw@beta` when validating a specific prerelease number; npm tags can move.
- If the wrapper fails a lane, read the auto-dumped tail first, then the full nested lane log under `.artifacts/parallels/openclaw-parallels-npm-update.*`.
- Current known macOS update-lane transport signature when the fallback is missing or bypassed: `Unable to authenticate the user. Make sure that the specified credentials are correct and try again.` Treat that as Parallels current-user authentication before blaming npm or OpenClaw.
- A macOS packaged fresh install with global package directories or bundled files mode `0777` usually means the harness used the root `prlctl exec` fallback under a permissive umask. The POSIX guest transports should prepend `umask 022`; verify the phase preflight line before blaming npm.
## CLI invocation footgun

View File

@@ -28,6 +28,7 @@ gitcrawl cluster-detail openclaw/openclaw --id <cluster-id> --member-limit 20 --
- 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.
- Do not manually close plus manually comment for these reasons.
- If an issue/PR is already fixed on current `main` or solved by a new release, comment with proof plus the canonical commit/PR/release, then close it.
- `r:*` labels can be used on both issues and PRs.
- Current reasons:
- `r: skill`

View File

@@ -139,6 +139,20 @@ pnpm test:docker:npm-telegram-live
- `OPENCLAW_QA_CONVEX_SITE_URL`
- `OPENCLAW_QA_CONVEX_SECRET_MAINTAINER`
- `OPENCLAW_NPM_TELEGRAM_PROVIDER_MODE=mock-openai`
- If direct Telegram env is missing locally and `op signin` blocks, prefer dispatching the manual GitHub lane because the `qa-live-shared` environment already has Convex CI credentials:
```bash
gh workflow run "NPM Telegram Beta E2E" --repo openclaw/openclaw --ref main \
-f package_spec=openclaw@YYYY.M.D-beta.N \
-f package_label=openclaw@YYYY.M.D-beta.N \
-f provider_mode=mock-openai
```
- Poll the exact run id from the dispatch URL. `gh run view --json artifacts` is not supported; list artifacts with:
```bash
gh api repos/openclaw/openclaw/actions/runs/<run-id>/artifacts
```
## Character evals

3
.github/labeler.yml vendored
View File

@@ -217,9 +217,8 @@
- "Dockerfile"
- "Dockerfile.*"
- "docker-compose.yml"
- "docker-setup.sh"
- "setup-podman.sh"
- ".dockerignore"
- "deploy/fly.private.toml"
- "scripts/docker/setup.sh"
- "scripts/docker/sandbox/Dockerfile*"
- "scripts/podman/setup.sh"

View File

@@ -1467,8 +1467,18 @@ jobs:
fail-fast: false
matrix:
include:
- check_name: check-additional-boundaries
- check_name: check-additional-boundaries-a
group: boundaries
boundary_shard: 1/4
- check_name: check-additional-boundaries-b
group: boundaries
boundary_shard: 2/4
- check_name: check-additional-boundaries-c
group: boundaries
boundary_shard: 3/4
- check_name: check-additional-boundaries-d
group: boundaries
boundary_shard: 4/4
- check_name: check-additional-extension-channels
group: extension-channels
- check_name: check-additional-extension-bundled
@@ -1573,6 +1583,7 @@ jobs:
- name: Run additional check shard
env:
ADDITIONAL_CHECK_GROUP: ${{ matrix.group }}
OPENCLAW_ADDITIONAL_BOUNDARY_SHARD: ${{ matrix.boundary_shard || '' }}
RUN_CONTROL_UI_I18N: ${{ needs.preflight.outputs.run_control_ui_i18n }}
OPENCLAW_ADDITIONAL_BOUNDARY_CONCURRENCY: 4
OPENCLAW_EXTENSION_BOUNDARY_CONCURRENCY: 6
@@ -1758,10 +1769,10 @@ jobs:
python -m pip install pytest ruff pyyaml
- name: Lint Python skill scripts
run: python -m ruff check skills
run: python -m ruff check --config skills/pyproject.toml skills
- name: Test skill Python scripts
run: python -m pytest -q skills
run: python -m pytest -q -c skills/pyproject.toml skills
checks-windows:
permissions:
@@ -1987,8 +1998,8 @@ jobs:
- name: Swift lint
run: |
swiftlint --config .swiftlint.yml
swiftformat --lint apps/macos/Sources --config .swiftformat
swiftlint lint --config config/swiftlint.yml
swiftformat --lint apps/macos/Sources --config config/swiftformat --exclude '**/OpenClawProtocol,**/HostEnvSecurityPolicy.generated.swift'
- name: Swift build (release)
run: |

View File

@@ -54,7 +54,7 @@ on:
- qa-live
- npm-telegram
live_suite_filter:
description: Optional exact live suite id for focused live/E2E reruns; blank runs all selected live suites
description: Optional exact live/E2E suite id, or comma-separated QA live lanes such as qa-live-matrix,qa-live-telegram; blank runs all selected live suites
required: false
default: ""
type: string

View File

@@ -274,7 +274,7 @@ jobs:
const activePrLimitLabel = "r: too-many-prs";
const activePrLimitOverrideLabel = "r: too-many-prs-override";
const activePrLimit = 10;
const activePrLimit = 20;
const labelColor = "B60205";
const labelDescription = `Author has more than ${activePrLimit} active PRs in this repo`;
const authorLogin = pullRequest.user?.login;

View File

@@ -0,0 +1,169 @@
name: Mantis Discord Smoke
on:
workflow_dispatch:
inputs:
ref:
description: Ref, tag, or SHA to run
required: true
default: main
type: string
post_message:
description: Post a smoke message and reaction to the configured Discord channel
required: true
default: true
type: boolean
permissions:
contents: read
pull-requests: read
concurrency:
group: mantis-discord-smoke-${{ inputs.ref }}-${{ github.run_attempt }}
cancel-in-progress: false
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
NODE_VERSION: "24.x"
PNPM_VERSION: "10.33.0"
OPENCLAW_BUILD_PRIVATE_QA: "1"
OPENCLAW_ENABLE_PRIVATE_QA_CLI: "1"
jobs:
authorize_actor:
name: Authorize workflow actor
runs-on: blacksmith-8vcpu-ubuntu-2404
steps:
- name: Require maintainer-level repository access
uses: actions/github-script@v8
with:
script: |
const allowed = new Set(["admin", "maintain", "write"]);
const { owner, repo } = context.repo;
const { data } = await github.rest.repos.getCollaboratorPermissionLevel({
owner,
repo,
username: context.actor,
});
const permission = data.permission;
core.info(`Actor ${context.actor} permission: ${permission}`);
if (!allowed.has(permission)) {
core.setFailed(
`Workflow requires write/maintain/admin access. Actor "${context.actor}" has "${permission}".`,
);
}
validate_selected_ref:
name: Validate selected ref
needs: authorize_actor
runs-on: blacksmith-8vcpu-ubuntu-2404
outputs:
selected_revision: ${{ steps.validate.outputs.selected_revision }}
trusted_reason: ${{ steps.validate.outputs.trusted_reason }}
steps:
- name: Checkout selected ref
uses: actions/checkout@v6
with:
persist-credentials: false
ref: ${{ inputs.ref }}
fetch-depth: 0
- name: Validate selected ref
id: validate
env:
GH_TOKEN: ${{ github.token }}
INPUT_REF: ${{ inputs.ref }}
shell: bash
run: |
set -euo pipefail
selected_revision="$(git rev-parse HEAD)"
trusted_reason=""
git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main
if git merge-base --is-ancestor "$selected_revision" refs/remotes/origin/main; then
trusted_reason="main-ancestor"
elif git tag --points-at "$selected_revision" | grep -Eq '^v'; then
trusted_reason="release-tag"
elif [[ "$INPUT_REF" =~ ^release/[0-9]{4}\.[0-9]+\.[0-9]+$ ]]; then
git fetch --no-tags origin "+refs/heads/${INPUT_REF}:refs/remotes/origin/${INPUT_REF}"
release_branch_sha="$(git rev-parse "refs/remotes/origin/${INPUT_REF}")"
if [[ "$selected_revision" == "$release_branch_sha" ]]; then
trusted_reason="release-branch-head"
fi
else
pr_head_count="$(
gh api \
-H "Accept: application/vnd.github+json" \
"repos/${GITHUB_REPOSITORY}/commits/${selected_revision}/pulls" \
--jq '[.[] | select(.state == "open" and .head.repo.full_name == "'"${GITHUB_REPOSITORY}"'" and .head.sha == "'"${selected_revision}"'")] | length'
)"
if [[ "$pr_head_count" != "0" ]]; then
trusted_reason="open-pr-head"
fi
fi
if [[ -z "$trusted_reason" ]]; then
echo "Ref '${INPUT_REF}' resolved to $selected_revision, which is not trusted for this secret-bearing Mantis run." >&2
echo "Allowed refs must be on main, point to a release tag, match a release branch head, or match an open PR head in ${GITHUB_REPOSITORY}." >&2
exit 1
fi
echo "selected_revision=$selected_revision" >> "$GITHUB_OUTPUT"
echo "trusted_reason=$trusted_reason" >> "$GITHUB_OUTPUT"
{
echo "Validated ref: \`${INPUT_REF}\`"
echo "Resolved SHA: \`$selected_revision\`"
echo "Trust reason: \`$trusted_reason\`"
} >> "$GITHUB_STEP_SUMMARY"
run_discord_smoke:
name: Run Mantis Discord smoke
needs: validate_selected_ref
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 20
environment: qa-live-shared
steps:
- name: Checkout selected ref
uses: actions/checkout@v6
with:
persist-credentials: false
ref: ${{ needs.validate_selected_ref.outputs.selected_revision }}
fetch-depth: 1
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "true"
- name: Build private QA runtime
run: pnpm build
- name: Run Mantis Discord smoke
shell: bash
env:
OPENCLAW_QA_DISCORD_MANTIS_BOT_TOKEN: ${{ secrets.OPENCLAW_QA_DISCORD_MANTIS_BOT_TOKEN }}
OPENCLAW_QA_DISCORD_GUILD_ID: ${{ secrets.OPENCLAW_QA_DISCORD_GUILD_ID }}
OPENCLAW_QA_DISCORD_CHANNEL_ID: ${{ secrets.OPENCLAW_QA_DISCORD_CHANNEL_ID }}
OPENCLAW_QA_REDACT_PUBLIC_METADATA: "1"
run: |
set -euo pipefail
args=()
if [[ "${{ inputs.post_message }}" != "true" ]]; then
args+=(--skip-post)
fi
pnpm openclaw qa mantis discord-smoke \
--repo-root . \
--output-dir .artifacts/qa-e2e/mantis/discord-smoke \
"${args[@]}"
- name: Upload Mantis artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: mantis-discord-smoke-${{ github.run_id }}-${{ github.run_attempt }}
path: .artifacts/qa-e2e/mantis/
retention-days: 14
if-no-files-found: warn

View File

@@ -0,0 +1,583 @@
name: Mantis Discord Status Reactions
on:
issue_comment:
types: [created]
workflow_dispatch:
inputs:
baseline_ref:
description: Ref, tag, or SHA expected to reproduce queued-only behavior
required: true
default: 0bf06e953fdda290799fc9fb9244a8f67fdae593
type: string
candidate_ref:
description: Ref, tag, or SHA expected to show queued -> thinking -> done
required: true
default: main
type: string
pr_number:
description: Optional bug or fix PR number to receive the QA evidence comment
required: false
type: string
permissions:
contents: write
issues: write
pull-requests: write
concurrency:
group: mantis-discord-status-reactions-${{ github.event.issue.number || inputs.pr_number || inputs.candidate_ref || github.run_id }}-${{ github.run_attempt }}
cancel-in-progress: false
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
NODE_VERSION: "24.x"
PNPM_VERSION: "10.33.0"
OPENCLAW_BUILD_PRIVATE_QA: "1"
OPENCLAW_ENABLE_PRIVATE_QA_CLI: "1"
jobs:
authorize_actor:
name: Authorize workflow actor
if: >-
${{
github.event_name == 'workflow_dispatch' ||
(
github.event_name == 'issue_comment' &&
github.event.issue.pull_request &&
(
contains(github.event.comment.body, '@Mantis') ||
contains(github.event.comment.body, '@mantis') ||
contains(github.event.comment.body, '/mantis')
)
)
}}
runs-on: blacksmith-8vcpu-ubuntu-2404
steps:
- name: Require maintainer-level repository access
uses: actions/github-script@v8
with:
script: |
const allowed = new Set(["admin", "maintain", "write"]);
const { owner, repo } = context.repo;
const { data } = await github.rest.repos.getCollaboratorPermissionLevel({
owner,
repo,
username: context.actor,
});
const permission = data.permission;
core.info(`Actor ${context.actor} permission: ${permission}`);
if (!allowed.has(permission)) {
core.setFailed(
`Workflow requires write/maintain/admin access. Actor "${context.actor}" has "${permission}".`,
);
}
resolve_request:
name: Resolve Mantis request
needs: authorize_actor
runs-on: blacksmith-8vcpu-ubuntu-2404
outputs:
baseline_ref: ${{ steps.resolve.outputs.baseline_ref }}
candidate_ref: ${{ steps.resolve.outputs.candidate_ref }}
pr_number: ${{ steps.resolve.outputs.pr_number }}
request_source: ${{ steps.resolve.outputs.request_source }}
should_run: ${{ steps.resolve.outputs.should_run }}
steps:
- name: Resolve refs and target PR
id: resolve
uses: actions/github-script@v8
with:
script: |
const defaultBaseline = "0bf06e953fdda290799fc9fb9244a8f67fdae593";
const eventName = context.eventName;
function setOutput(name, value) {
core.setOutput(name, value ?? "");
core.info(`${name}=${value ?? ""}`);
}
if (eventName === "workflow_dispatch") {
const inputs = context.payload.inputs ?? {};
setOutput("should_run", "true");
setOutput("baseline_ref", inputs.baseline_ref || defaultBaseline);
setOutput("candidate_ref", inputs.candidate_ref || "main");
setOutput("pr_number", inputs.pr_number || "");
setOutput("request_source", "workflow_dispatch");
return;
}
if (eventName !== "issue_comment") {
core.setFailed(`Unsupported event: ${eventName}`);
return;
}
const issue = context.payload.issue;
const body = context.payload.comment?.body ?? "";
if (!issue?.pull_request) {
core.setFailed("Mantis issue_comment trigger requires a pull request comment.");
return;
}
const normalized = body.toLowerCase();
const requested =
(normalized.includes("@mantis") || normalized.includes("/mantis")) &&
normalized.includes("discord") &&
normalized.includes("status") &&
normalized.includes("reaction");
if (!requested) {
core.notice("Comment mentioned Mantis but did not request the Discord status-reactions scenario.");
setOutput("should_run", "false");
setOutput("baseline_ref", "");
setOutput("candidate_ref", "");
setOutput("pr_number", "");
setOutput("request_source", "unsupported_issue_comment");
return;
}
const { owner, repo } = context.repo;
const { data: pr } = await github.rest.pulls.get({
owner,
repo,
pull_number: issue.number,
});
const baselineMatch = body.match(/(?:baseline|base)[\s:=]+([^\s`]+)/i);
const candidateMatch = body.match(/(?:candidate|head)[\s:=]+([^\s`]+)/i);
const baseline = baselineMatch?.[1] ?? defaultBaseline;
const rawCandidate = candidateMatch?.[1];
const candidate =
rawCandidate && !["head", "pr", "pr-head"].includes(rawCandidate.toLowerCase())
? rawCandidate
: pr.head.sha;
setOutput("should_run", "true");
setOutput("baseline_ref", baseline);
setOutput("candidate_ref", candidate);
setOutput("pr_number", String(issue.number));
setOutput("request_source", "issue_comment");
await github.rest.reactions.createForIssueComment({
owner,
repo,
comment_id: context.payload.comment.id,
content: "eyes",
}).catch((error) => core.warning(`Could not add eyes reaction: ${error.message}`));
validate_refs:
name: Validate selected refs
needs: resolve_request
if: ${{ needs.resolve_request.outputs.should_run == 'true' }}
runs-on: blacksmith-8vcpu-ubuntu-2404
outputs:
baseline_revision: ${{ steps.validate.outputs.baseline_revision }}
candidate_revision: ${{ steps.validate.outputs.candidate_revision }}
steps:
- name: Checkout harness ref
uses: actions/checkout@v6
with:
persist-credentials: false
fetch-depth: 0
- name: Validate refs are trusted
id: validate
env:
GH_TOKEN: ${{ github.token }}
BASELINE_REF: ${{ needs.resolve_request.outputs.baseline_ref }}
CANDIDATE_REF: ${{ needs.resolve_request.outputs.candidate_ref }}
shell: bash
run: |
set -euo pipefail
git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main
validate_ref() {
local label="$1"
local input_ref="$2"
local revision=""
local reason=""
revision="$(git rev-parse "${input_ref}^{commit}")"
if git merge-base --is-ancestor "$revision" refs/remotes/origin/main; then
reason="main-ancestor"
elif git tag --points-at "$revision" | grep -Eq '^v'; then
reason="release-tag"
else
local pr_head_count
pr_head_count="$(
gh api \
-H "Accept: application/vnd.github+json" \
"repos/${GITHUB_REPOSITORY}/commits/${revision}/pulls" \
--jq '[.[] | select(.state == "open" and .head.repo.full_name == "'"${GITHUB_REPOSITORY}"'" and .head.sha == "'"${revision}"'")] | length'
)"
if [[ "$pr_head_count" != "0" ]]; then
reason="open-pr-head"
fi
fi
if [[ -z "$reason" ]]; then
echo "${label} ref '${input_ref}' resolved to ${revision}, which is not trusted for this secret-bearing Mantis run." >&2
exit 1
fi
echo "${label}_revision=${revision}" >> "$GITHUB_OUTPUT"
{
echo "${label}: \`${input_ref}\`"
echo "${label} SHA: \`${revision}\`"
echo "${label} trust reason: \`${reason}\`"
} >> "$GITHUB_STEP_SUMMARY"
}
validate_ref baseline "$BASELINE_REF"
validate_ref candidate "$CANDIDATE_REF"
run_status_reactions:
name: Run Discord status reaction before/after
needs: [resolve_request, validate_refs]
if: ${{ needs.resolve_request.outputs.should_run == 'true' }}
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 180
environment: qa-live-shared
steps:
- name: Checkout harness ref
uses: actions/checkout@v6
with:
persist-credentials: false
fetch-depth: 0
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "true"
- name: Build Mantis harness
run: pnpm build
- name: Setup Go for Crabbox CLI
uses: actions/setup-go@v6
with:
go-version: "1.26.x"
cache: false
- name: Install Crabbox CLI
shell: bash
run: |
set -euo pipefail
install_dir="${RUNNER_TEMP}/crabbox"
mkdir -p "$install_dir" "$HOME/.local/bin"
git clone --depth 1 https://github.com/openclaw/crabbox.git "$install_dir/src"
go build -C "$install_dir/src" -o "$HOME/.local/bin/crabbox" ./cmd/crabbox
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
"$HOME/.local/bin/crabbox" --version
"$HOME/.local/bin/crabbox" warmup --help 2>&1 | grep -q -- "-desktop"
- name: Prepare baseline and candidate worktrees
shell: bash
env:
BASELINE_SHA: ${{ needs.validate_refs.outputs.baseline_revision }}
CANDIDATE_SHA: ${{ needs.validate_refs.outputs.candidate_revision }}
run: |
set -euo pipefail
worktree_root=".artifacts/qa-e2e/mantis/discord-status-reactions-worktrees"
mkdir -p "$worktree_root"
git worktree add --detach "$worktree_root/baseline" "$BASELINE_SHA"
git worktree add --detach "$worktree_root/candidate" "$CANDIDATE_SHA"
for lane in baseline candidate; do
lane_dir="$worktree_root/${lane}"
echo "Installing ${lane} worktree dependencies"
pnpm --dir "$lane_dir" install --frozen-lockfile
echo "Building ${lane} worktree"
pnpm --dir "$lane_dir" build
done
- name: Run baseline and candidate
id: run_mantis
shell: bash
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENCLAW_QA_CONVEX_SITE_URL: ${{ secrets.OPENCLAW_QA_CONVEX_SITE_URL }}
OPENCLAW_QA_CONVEX_SECRET_CI: ${{ secrets.OPENCLAW_QA_CONVEX_SECRET_CI }}
OPENCLAW_QA_REDACT_PUBLIC_METADATA: "1"
OPENCLAW_QA_DISCORD_CAPTURE_CONTENT: "1"
CRABBOX_COORDINATOR: ${{ secrets.CRABBOX_COORDINATOR }}
CRABBOX_COORDINATOR_TOKEN: ${{ secrets.CRABBOX_COORDINATOR_TOKEN }}
OPENCLAW_QA_MANTIS_CRABBOX_COORDINATOR: ${{ secrets.OPENCLAW_QA_MANTIS_CRABBOX_COORDINATOR }}
OPENCLAW_QA_MANTIS_CRABBOX_COORDINATOR_TOKEN: ${{ secrets.OPENCLAW_QA_MANTIS_CRABBOX_COORDINATOR_TOKEN }}
CRABBOX_ACCESS_CLIENT_ID: ${{ secrets.CRABBOX_ACCESS_CLIENT_ID }}
CRABBOX_ACCESS_CLIENT_SECRET: ${{ secrets.CRABBOX_ACCESS_CLIENT_SECRET }}
run: |
set -euo pipefail
require_var() {
local key="$1"
if [[ -z "${!key:-}" ]]; then
echo "Missing required ${key}." >&2
exit 1
fi
}
CRABBOX_COORDINATOR="${CRABBOX_COORDINATOR:-${OPENCLAW_QA_MANTIS_CRABBOX_COORDINATOR:-}}"
CRABBOX_COORDINATOR_TOKEN="${CRABBOX_COORDINATOR_TOKEN:-${OPENCLAW_QA_MANTIS_CRABBOX_COORDINATOR_TOKEN:-}}"
export CRABBOX_COORDINATOR CRABBOX_COORDINATOR_TOKEN
require_var OPENAI_API_KEY
require_var OPENCLAW_QA_CONVEX_SITE_URL
require_var OPENCLAW_QA_CONVEX_SECRET_CI
require_var CRABBOX_COORDINATOR_TOKEN
root=".artifacts/qa-e2e/mantis/discord-status-reactions"
worktree_root=".artifacts/qa-e2e/mantis/discord-status-reactions-worktrees"
mkdir -p "$root"
echo "output_dir=${root}" >> "$GITHUB_OUTPUT"
run_lane() {
local lane="$1"
local repo_root="$worktree_root/$lane"
local output_dir=".artifacts/qa-e2e/mantis/discord-status-reactions/$lane"
pnpm openclaw qa discord \
--repo-root "$repo_root" \
--output-dir "$output_dir" \
--provider-mode live-frontier \
--model openai/gpt-5.4 \
--alt-model openai/gpt-5.4 \
--fast \
--credential-source convex \
--credential-role ci \
--scenario discord-status-reactions-tool-only \
--allow-failures
rm -rf "$root/$lane"
mkdir -p "$root/$lane"
cp -a "$repo_root/$output_dir/." "$root/$lane/"
}
run_lane baseline
run_lane candidate
desktop_lease_id=""
warmup_output="$(
crabbox warmup \
--provider hetzner \
--desktop \
--browser \
--class standard \
--idle-timeout 30m \
--ttl 90m
)"
printf '%s\n' "$warmup_output" | tee "$root/crabbox-desktop-warmup.log"
desktop_lease_id="$(printf '%s\n' "$warmup_output" | grep -Eo 'cbx_[a-f0-9]+' | head -n 1 || true)"
if [[ ! "$desktop_lease_id" =~ ^cbx_[a-f0-9]+$ ]]; then
echo "Crabbox desktop warmup did not return a lease id." >&2
exit 1
fi
cleanup_desktop_lease() {
if [[ -n "$desktop_lease_id" ]]; then
crabbox stop --provider hetzner "$desktop_lease_id" || true
fi
}
trap cleanup_desktop_lease EXIT
capture_desktop_lane() {
local lane="$1"
local html_file="$root/$lane/discord-status-reactions-tool-only-timeline.html"
local desktop_dir="$root/$lane/desktop-browser"
if [[ ! -f "$html_file" ]]; then
echo "Missing desktop source HTML for ${lane}: ${html_file}" >&2
exit 1
fi
local args=(
openclaw qa mantis desktop-browser-smoke
--html-file "$html_file"
--output-dir "$desktop_dir"
--provider hetzner
--class standard
--idle-timeout 30m
--ttl 90m
--lease-id "$desktop_lease_id"
)
pnpm "${args[@]}"
cp "$desktop_dir/desktop-browser-smoke.png" "$root/$lane/discord-status-reactions-tool-only-desktop.png"
}
capture_desktop_lane baseline
capture_desktop_lane candidate
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")"
jq -n \
--arg baseline_status "$baseline_status" \
--arg candidate_status "$candidate_status" \
--arg baseline_sha "${{ needs.validate_refs.outputs.baseline_revision }}" \
--arg candidate_sha "${{ needs.validate_refs.outputs.candidate_revision }}" \
'{
scenario: "discord-status-reactions-tool-only",
baseline: { sha: $baseline_sha, expected: "queued-only", status: $baseline_status, reproduced: ($baseline_status == "fail") },
candidate: { sha: $candidate_sha, expected: "queued -> thinking -> done", status: $candidate_status, fixed: ($candidate_status == "pass") },
pass: (($baseline_status == "fail") and ($candidate_status == "pass"))
}' > "$root/comparison.json"
{
echo "# Mantis Discord Status Reactions"
echo
echo "- Scenario: \`discord-status-reactions-tool-only\`"
echo "- Baseline status: \`${baseline_status}\`"
echo "- Candidate status: \`${candidate_status}\`"
echo "- Baseline screenshot: \`baseline/discord-status-reactions-tool-only-timeline.png\`"
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\`"
} > "$root/mantis-report.md"
cat "$root/mantis-report.md" >> "$GITHUB_STEP_SUMMARY"
if [[ "$baseline_status" != "fail" ]]; then
echo "Baseline did not reproduce queued-only behavior." >&2
exit 1
fi
if [[ "$candidate_status" != "pass" ]]; then
echo "Candidate did not show queued -> thinking -> done." >&2
exit 1
fi
- name: Upload Mantis status reaction artifacts
id: upload_artifact
if: ${{ always() && steps.run_mantis.outputs.output_dir != '' }}
uses: actions/upload-artifact@v4
with:
name: mantis-discord-status-reactions-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ steps.run_mantis.outputs.output_dir }}
retention-days: 14
if-no-files-found: warn
- name: Create Mantis GitHub App token
id: mantis_app_token
if: ${{ always() && needs.resolve_request.outputs.pr_number != '' }}
uses: actions/create-github-app-token@v3
with:
app-id: ${{ secrets.MANTIS_GITHUB_APP_ID }}
private-key: ${{ secrets.MANTIS_GITHUB_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
repositories: ${{ github.event.repository.name }}
permission-contents: write
permission-issues: write
permission-pull-requests: write
- name: Comment PR with inline QA screenshots
if: ${{ always() && needs.resolve_request.outputs.pr_number != '' && steps.run_mantis.outputs.output_dir != '' }}
env:
GH_TOKEN: ${{ steps.mantis_app_token.outputs.token }}
TARGET_PR: ${{ needs.resolve_request.outputs.pr_number }}
ARTIFACT_URL: ${{ steps.upload_artifact.outputs.artifact-url }}
BASELINE_SHA: ${{ needs.validate_refs.outputs.baseline_revision }}
CANDIDATE_SHA: ${{ needs.validate_refs.outputs.candidate_revision }}
REQUEST_SOURCE: ${{ needs.resolve_request.outputs.request_source }}
shell: bash
run: |
set -euo pipefail
if [[ ! "$TARGET_PR" =~ ^[0-9]+$ ]]; then
echo "pr_number must be numeric, got '${TARGET_PR}'." >&2
exit 1
fi
root=".artifacts/qa-e2e/mantis/discord-status-reactions"
for required in \
"$root/comparison.json" \
"$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"
do
if [[ ! -f "$required" ]]; then
echo "Missing required QA evidence file: $required" >&2
exit 1
fi
done
gh api "repos/${GITHUB_REPOSITORY}/pulls/${TARGET_PR}" --jq '.number' >/dev/null
artifact_root="mantis/discord-status-reactions/pr-${TARGET_PR}/run-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
artifacts_worktree="$(mktemp -d)"
git init --quiet "$artifacts_worktree"
git -C "$artifacts_worktree" config user.name "github-actions[bot]"
git -C "$artifacts_worktree" config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git -C "$artifacts_worktree" remote add origin "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git"
if git -C "$artifacts_worktree" fetch --quiet origin qa-artifacts; then
git -C "$artifacts_worktree" checkout --quiet -B qa-artifacts FETCH_HEAD
else
git -C "$artifacts_worktree" checkout --quiet --orphan qa-artifacts
fi
mkdir -p "$artifacts_worktree/$artifact_root"
cp "$root/baseline/discord-status-reactions-tool-only-timeline.png" "$artifacts_worktree/$artifact_root/baseline.png"
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"
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."
else
git -C "$artifacts_worktree" commit --quiet -m "qa: publish Mantis Discord screenshots for PR ${TARGET_PR}"
git -C "$artifacts_worktree" push --quiet origin HEAD:qa-artifacts
fi
encoded_artifact_root="${artifact_root// /%20}"
raw_base="https://raw.githubusercontent.com/${GITHUB_REPOSITORY}/qa-artifacts/${encoded_artifact_root}"
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")"
comment_file="$(mktemp)"
cat > "$comment_file" <<EOF
<!-- mantis-discord-status-reactions -->
## Mantis Discord Status Reactions QA
Summary: Mantis reran Discord status reactions against the known queued-only baseline and the candidate ref. The baseline reproduced the bug, while the candidate showed the expected queued -> thinking -> done reaction sequence.
- Scenario: \`discord-status-reactions-tool-only\`
- Trigger: \`${REQUEST_SOURCE}\`
- Run: https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}
- Artifact: ${ARTIFACT_URL}
- Baseline: \`${baseline_status}\` at \`${BASELINE_SHA}\`
- Candidate: \`${candidate_status}\` at \`${CANDIDATE_SHA}\`
- Overall: \`${pass}\`
| Baseline queued-only | Candidate queued -> thinking -> done |
| --- | --- |
| <img src="${raw_base}/baseline.png" width="420" alt="Baseline Discord status reaction timeline"> | <img src="${raw_base}/candidate.png" width="420" alt="Candidate Discord status reaction timeline"> |
| 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"> |
Raw QA files: https://github.com/${GITHUB_REPOSITORY}/tree/qa-artifacts/${artifact_root}
EOF
comment_id="$(
gh api --paginate "repos/${GITHUB_REPOSITORY}/issues/${TARGET_PR}/comments" \
--jq '.[] | select(.body | contains("<!-- mantis-discord-status-reactions -->")) | .id' \
| tail -n 1
)"
if [[ -n "$comment_id" ]]; then
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}."
else
echo "::warning::Could not update existing Mantis QA screenshot 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}."
fi
else
gh pr comment "$TARGET_PR" --body-file "$comment_file"
echo "Created Mantis QA screenshot comment on PR #${TARGET_PR}."
fi

View File

@@ -220,6 +220,23 @@ jobs:
echo "output_dir=${output_dir}" >> "$GITHUB_OUTPUT"
export OPENCLAW_NPM_TELEGRAM_OUTPUT_DIR="${output_dir}"
append_telegram_summary() {
local status=$?
local report="${output_dir}/telegram-qa-report.md"
if [[ -n "${GITHUB_STEP_SUMMARY:-}" && -f "${report}" ]]; then
{
echo "## Package Telegram E2E"
echo
echo "- Package: ${OPENCLAW_NPM_TELEGRAM_PACKAGE_LABEL:-${OPENCLAW_NPM_TELEGRAM_PACKAGE_SPEC}}"
echo "- Provider mode: ${OPENCLAW_NPM_TELEGRAM_PROVIDER_MODE}"
echo
cat "${report}"
} >> "${GITHUB_STEP_SUMMARY}"
fi
return "${status}"
}
trap append_telegram_summary EXIT
if [[ -n "${PACKAGE_ARTIFACT_NAME// }" ]]; then
mapfile -t package_tgzs < <(find .artifacts/telegram-package-under-test -type f -name "*.tgz" | sort)
if [[ "${#package_tgzs[@]}" -ne 1 ]]; then

View File

@@ -409,6 +409,7 @@ jobs:
add_profile_suite native-live-src-gateway-profiles-xai "full"
add_profile_suite native-live-src-gateway-profiles-zai "full"
add_profile_suite native-live-src-gateway-backends "stable full"
add_profile_suite native-live-src-infra "stable full"
add_profile_suite native-live-test "stable full"
add_profile_suite native-live-extensions-l-n "full"
add_profile_suite native-live-extensions-moonshot "full"
@@ -817,6 +818,9 @@ jobs:
export OPENCLAW_DOCKER_ALL_LOG_DIR=".artifacts/docker-tests/release-${DOCKER_E2E_CHUNK}"
export OPENCLAW_DOCKER_ALL_TIMINGS_FILE=".artifacts/docker-tests/release-${DOCKER_E2E_CHUNK}-timings.json"
export OPENCLAW_DOCKER_ALL_PNPM_COMMAND="$(command -v pnpm)"
if [[ "${{ steps.plan.outputs.needs_live_image }}" == "1" ]]; then
OPENCLAW_DOCKER_BUILD_ON_MISSING=1 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" bash .release-harness/scripts/test-live-build-docker.sh
fi
node .release-harness/scripts/test-docker-all.mjs
@@ -1060,7 +1064,7 @@ jobs:
export OPENCLAW_DOCKER_ALL_TIMINGS_FILE=".artifacts/docker-tests/targeted-${{ steps.plan.outputs.artifact_suffix }}-timings.json"
export OPENCLAW_DOCKER_ALL_PNPM_COMMAND="$(command -v pnpm)"
if [[ "${{ steps.plan.outputs.needs_live_image }}" == "1" ]]; then
OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" bash .release-harness/scripts/test-live-build-docker.sh
OPENCLAW_DOCKER_BUILD_ON_MISSING=1 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" bash .release-harness/scripts/test-live-build-docker.sh
fi
export OPENCLAW_DOCKER_ALL_BUILD=0
@@ -1188,6 +1192,9 @@ jobs:
export OPENCLAW_DOCKER_ALL_LOG_DIR=".artifacts/docker-tests/release-openwebui"
export OPENCLAW_DOCKER_ALL_TIMINGS_FILE=".artifacts/docker-tests/release-openwebui-timings.json"
export OPENCLAW_DOCKER_ALL_PNPM_COMMAND="$(command -v pnpm)"
if [[ "${{ steps.plan.outputs.needs_live_image }}" == "1" ]]; then
OPENCLAW_DOCKER_BUILD_ON_MISSING=1 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" bash .release-harness/scripts/test-live-build-docker.sh
fi
node .release-harness/scripts/test-docker-all.mjs
@@ -1983,6 +1990,12 @@ jobs:
timeout_minutes: 90
profile_env_only: false
profiles: stable full
- suite_id: native-live-src-infra
label: Native live infra
command: OPENCLAW_LIVE_APNS_REACHABILITY=1 node .release-harness/scripts/test-live-shard.mjs native-live-src-infra
timeout_minutes: 45
profile_env_only: false
profiles: stable full
- suite_id: native-live-test
label: Native live test harnesses
command: node .release-harness/scripts/test-live-shard.mjs native-live-test

View File

@@ -92,6 +92,7 @@ jobs:
env:
KOVA_REF: ${{ inputs.kova_ref || 'b63b6f9e20efb23641df00487e982230d81a90ac' }}
KOVA_HOME: ${{ github.workspace }}/.artifacts/kova/home/${{ matrix.lane }}
PERFORMANCE_HELPER_DIR: ${{ github.workspace }}/.artifacts/performance-workflow
REPORT_DIR: ${{ github.workspace }}/.artifacts/kova/reports/${{ matrix.lane }}
BUNDLE_DIR: ${{ github.workspace }}/.artifacts/kova/bundles/${{ matrix.lane }}
SUMMARY_DIR: ${{ github.workspace }}/.artifacts/kova/summaries
@@ -149,6 +150,15 @@ jobs:
fetch-depth: 1
persist-credentials: false
- name: Checkout performance workflow helpers
if: steps.lane.outputs.run == 'true'
uses: actions/checkout@v6
with:
ref: ${{ github.sha }}
path: .artifacts/performance-workflow
fetch-depth: 1
persist-credentials: false
- name: Record tested revision
if: steps.lane.outputs.run == 'true'
shell: bash
@@ -310,7 +320,7 @@ jobs:
report_url="https://github.com/openclaw/clawgrit-reports/tree/main/openclaw-performance/${ref_slug}/${run_slug}/${LANE_ID}"
fi
summary_path="$SUMMARY_DIR/${LANE_ID}.md"
summary_args=(node scripts/kova-ci-summary.mjs --report "$report_json" --output "$summary_path" --lane "$LANE_ID")
summary_args=(node "$PERFORMANCE_HELPER_DIR/scripts/kova-ci-summary.mjs" --report "$report_json" --output "$summary_path" --lane "$LANE_ID")
if [[ -n "$report_url" ]]; then
summary_args+=(--report-url "$report_url")
fi
@@ -349,6 +359,24 @@ jobs:
fi
mkdir -p "$SOURCE_PERF_DIR/mock-hello"
if ! node -e "const fs=require('node:fs'); const scripts=require('./package.json').scripts||{}; process.exit(scripts['test:gateway:cpu-scenarios'] && scripts.openclaw && fs.existsSync('scripts/bench-cli-startup.ts') ? 0 : 1)"; then
cat > "$SOURCE_PERF_DIR/index.md" <<EOF
# OpenClaw Source Performance
Generated: $(date -u +%Y-%m-%dT%H:%M:%SZ)
Source probes skipped for this tested ref because one or more probe entry points are not present in the checked-out source tree.
## Test scope
- Tested ref: ${TESTED_REF}
- Tested SHA: ${TESTED_SHA}
- Required scripts: test:gateway:cpu-scenarios, openclaw, scripts/bench-cli-startup.ts
EOF
cat "$SOURCE_PERF_DIR/index.md" >> "$GITHUB_STEP_SUMMARY"
exit 0
fi
pnpm build
pnpm test:gateway:cpu-scenarios \
@@ -432,7 +460,7 @@ jobs:
cleanup_gateway
trap - EXIT
pnpm perf:source:summary \
node "$PERFORMANCE_HELPER_DIR/scripts/openclaw-performance-source-summary.mjs" \
--source-dir "$SOURCE_PERF_DIR" \
--output "$SOURCE_PERF_DIR/index.md"
@@ -527,4 +555,14 @@ jobs:
exit 0
fi
git -C "$reports_root" commit -m "perf: add OpenClaw ${LANE_ID} report ${TESTED_SHA::12}"
git -C "$reports_root" push origin HEAD:main
for attempt in 1 2 3 4 5; do
if git -C "$reports_root" push origin HEAD:main; then
exit 0
fi
if [[ "$attempt" == "5" ]]; then
exit 1
fi
sleep $((attempt * 2))
git -C "$reports_root" fetch --depth=1 origin main
git -C "$reports_root" rebase FETCH_HEAD
done

View File

@@ -54,7 +54,7 @@ on:
- qa-parity
- qa-live
live_suite_filter:
description: Optional exact live suite id for focused live/E2E reruns; blank runs all selected live suites
description: Optional exact live/E2E suite id, or comma-separated QA live lanes such as qa-live-matrix,qa-live-telegram; blank runs all selected live suites
required: false
default: ""
type: string
@@ -88,6 +88,9 @@ jobs:
release_profile: ${{ steps.inputs.outputs.release_profile }}
rerun_group: ${{ steps.inputs.outputs.rerun_group }}
live_suite_filter: ${{ steps.inputs.outputs.live_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 }}
package_acceptance_package_spec: ${{ steps.inputs.outputs.package_acceptance_package_spec }}
steps:
- name: Require main or release workflow ref for release checks
@@ -205,9 +208,66 @@ jobs:
RELEASE_PROFILE_INPUT: ${{ inputs.release_profile }}
RELEASE_RERUN_GROUP_INPUT: ${{ inputs.rerun_group }}
RELEASE_LIVE_SUITE_FILTER_INPUT: ${{ inputs.live_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
filter="$(printf '%s' "$RELEASE_LIVE_SUITE_FILTER_INPUT" | tr '[:upper:]' '[:lower:]')"
if [[ -n "${filter// }" ]]; then
qa_filter_seen=false
matrix_selected=false
telegram_selected=false
slack_selected=false
IFS=', ' read -r -a filter_tokens <<< "$filter"
for token in "${filter_tokens[@]}"; do
token="${token//$'\t'/}"
token="${token//$'\r'/}"
token="${token//$'\n'/}"
[[ -z "$token" ]] && continue
case "$token" in
qa-live|qa-live-all|qa-all)
qa_filter_seen=true
matrix_selected=true
telegram_selected=true
;;
qa-live-non-slack|qa-non-slack|non-slack|no-slack|without-slack)
qa_filter_seen=true
matrix_selected=true
telegram_selected=true
;;
qa-live-matrix|qa-matrix|matrix)
qa_filter_seen=true
matrix_selected=true
;;
qa-live-telegram|qa-telegram|telegram)
qa_filter_seen=true
telegram_selected=true
;;
qa-live-slack|qa-slack|slack)
qa_filter_seen=true
slack_selected="$qa_live_slack_ci_enabled"
;;
esac
done
if [[ "$qa_filter_seen" == "true" ]]; then
qa_live_matrix_enabled="$matrix_selected"
qa_live_telegram_enabled="$telegram_selected"
qa_live_slack_enabled="$slack_selected"
fi
fi
{
printf 'ref=%s\n' "$RELEASE_REF_INPUT"
printf 'provider=%s\n' "$RELEASE_PROVIDER_INPUT"
@@ -215,6 +275,9 @@ jobs:
printf 'release_profile=%s\n' "$RELEASE_PROFILE_INPUT"
printf 'rerun_group=%s\n' "$RELEASE_RERUN_GROUP_INPUT"
printf 'live_suite_filter=%s\n' "$RELEASE_LIVE_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"
printf 'package_acceptance_package_spec=%s\n' "$RELEASE_PACKAGE_ACCEPTANCE_PACKAGE_SPEC_INPUT"
} >> "$GITHUB_OUTPUT"
@@ -243,12 +306,13 @@ jobs:
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 }}\`"
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, and Telegram lanes, and the non-Parallels Docker/live/openwebui coverage from the CI migration plan."
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."
} >> "$GITHUB_STEP_SUMMARY"
prepare_release_package:
@@ -462,7 +526,7 @@ jobs:
published_upgrade_survivor_baselines: all-since-2026.4.23
published_upgrade_survivor_scenarios: 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-mention-gating
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:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}
@@ -655,7 +719,7 @@ jobs:
qa_live_matrix_release_checks:
name: Run QA Lab live Matrix lane
needs: [resolve_target]
if: contains(fromJSON('["all","qa","qa-live"]'), needs.resolve_target.outputs.rerun_group)
if: contains(fromJSON('["all","qa","qa-live"]'), needs.resolve_target.outputs.rerun_group) && needs.resolve_target.outputs.qa_live_matrix_enabled == 'true'
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 60
permissions:
@@ -732,7 +796,7 @@ jobs:
qa_live_telegram_release_checks:
name: Run QA Lab live Telegram lane
needs: [resolve_target]
if: contains(fromJSON('["all","qa","qa-live"]'), needs.resolve_target.outputs.rerun_group)
if: contains(fromJSON('["all","qa","qa-live"]'), needs.resolve_target.outputs.rerun_group) && needs.resolve_target.outputs.qa_live_telegram_enabled == 'true'
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 60
permissions:
@@ -822,6 +886,99 @@ jobs:
retention-days: 14
if-no-files-found: warn
qa_live_slack_release_checks:
name: Run QA Lab live Slack lane
needs: [resolve_target]
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'
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 60
permissions:
contents: read
pull-requests: read
environment: qa-live-shared
env:
OPENCLAW_BUILD_PRIVATE_QA: "1"
OPENCLAW_ENABLE_PRIVATE_QA_CLI: "1"
steps:
- name: Checkout selected ref
uses: actions/checkout@v6
with:
persist-credentials: false
ref: ${{ needs.resolve_target.outputs.revision }}
fetch-depth: 1
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "true"
- name: Validate required QA credential env
env:
OPENCLAW_QA_CONVEX_SITE_URL: ${{ secrets.OPENCLAW_QA_CONVEX_SITE_URL }}
OPENCLAW_QA_CONVEX_SECRET_CI: ${{ secrets.OPENCLAW_QA_CONVEX_SECRET_CI }}
shell: bash
run: |
set -euo pipefail
require_var() {
local key="$1"
if [[ -z "${!key:-}" ]]; then
echo "Missing required ${key}." >&2
exit 1
fi
}
require_var OPENCLAW_QA_CONVEX_SITE_URL
require_var OPENCLAW_QA_CONVEX_SECRET_CI
- name: Build private QA runtime
run: pnpm build
- name: Run Slack live lane
id: run_lane
shell: bash
env:
OPENCLAW_QA_CONVEX_SITE_URL: ${{ secrets.OPENCLAW_QA_CONVEX_SITE_URL }}
OPENCLAW_QA_CONVEX_SECRET_CI: ${{ secrets.OPENCLAW_QA_CONVEX_SECRET_CI }}
OPENCLAW_QA_REDACT_PUBLIC_METADATA: "1"
OPENCLAW_QA_SLACK_CAPTURE_CONTENT: "1"
run: |
set -euo pipefail
output_dir=".artifacts/qa-e2e/slack-live-release-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
echo "output_dir=${output_dir}" >> "$GITHUB_OUTPUT"
for attempt in 1 2; do
attempt_output_dir="${output_dir}/attempt-${attempt}"
if pnpm openclaw qa slack \
--repo-root . \
--output-dir "${attempt_output_dir}" \
--provider-mode mock-openai \
--model mock-openai/gpt-5.5 \
--alt-model mock-openai/gpt-5.5-alt \
--fast \
--credential-source convex \
--credential-role ci; then
exit 0
fi
if [[ "${attempt}" == "2" ]]; then
exit 1
fi
echo "Slack live lane failed on attempt ${attempt}; retrying once..." >&2
sleep 10
done
- name: Upload Slack QA artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: release-qa-live-slack-${{ needs.resolve_target.outputs.revision }}
path: .artifacts/qa-e2e/
retention-days: 14
if-no-files-found: warn
summary:
name: Verify release checks
needs:
@@ -835,6 +992,7 @@ jobs:
- qa_lab_parity_report_release_checks
- qa_live_matrix_release_checks
- qa_live_telegram_release_checks
- qa_live_slack_release_checks
if: always()
runs-on: ubuntu-24.04
permissions: {}
@@ -855,7 +1013,8 @@ jobs:
"qa_lab_parity_lane_release_checks=${{ needs.qa_lab_parity_lane_release_checks.result }}" \
"qa_lab_parity_report_release_checks=${{ needs.qa_lab_parity_report_release_checks.result }}" \
"qa_live_matrix_release_checks=${{ needs.qa_live_matrix_release_checks.result }}" \
"qa_live_telegram_release_checks=${{ needs.qa_live_telegram_release_checks.result }}"
"qa_live_telegram_release_checks=${{ needs.qa_live_telegram_release_checks.result }}" \
"qa_live_slack_release_checks=${{ needs.qa_live_slack_release_checks.result }}"
do
name="${item%%=*}"
result="${item#*=}"

View File

@@ -174,9 +174,9 @@ jobs:
shift
local before_json dispatch_output run_id status conclusion url
before_json="$(gh run list --workflow "$workflow" --event workflow_dispatch --limit 100 --json databaseId --jq '[.[].databaseId]')"
before_json="$(gh run list --repo "$GITHUB_REPOSITORY" --workflow "$workflow" --event workflow_dispatch --limit 100 --json databaseId --jq '[.[].databaseId]')"
dispatch_output="$(gh workflow run "$workflow" --ref "$CHILD_WORKFLOW_REF" "$@" 2>&1)"
dispatch_output="$(gh workflow run --repo "$GITHUB_REPOSITORY" "$workflow" --ref "$CHILD_WORKFLOW_REF" "$@" 2>&1)"
printf '%s\n' "$dispatch_output"
run_id="$(
printf '%s\n' "$dispatch_output" |
@@ -187,7 +187,7 @@ jobs:
if [[ -z "$run_id" ]]; then
for _ in $(seq 1 60); do
run_id="$(
BEFORE_IDS="$before_json" gh run list --workflow "$workflow" --event workflow_dispatch --limit 50 --json databaseId,createdAt \
BEFORE_IDS="$before_json" gh run list --repo "$GITHUB_REPOSITORY" --workflow "$workflow" --event workflow_dispatch --limit 50 --json databaseId,createdAt \
--jq 'map(select(.databaseId as $id | (env.BEFORE_IDS | fromjson | index($id) | not))) | sort_by(.createdAt) | reverse | .[0].databaseId // empty'
)"
if [[ -n "$run_id" ]]; then
@@ -207,13 +207,13 @@ jobs:
cancel_child() {
if [[ -n "${run_id:-}" ]]; then
echo "Cancelling child workflow ${workflow}: ${run_id}" >&2
gh run cancel "$run_id" >/dev/null 2>&1 || true
gh run cancel --repo "$GITHUB_REPOSITORY" "$run_id" >/dev/null 2>&1 || true
fi
}
trap cancel_child EXIT INT TERM
while true; do
status="$(gh run view "$run_id" --json status --jq '.status')"
status="$(gh run view --repo "$GITHUB_REPOSITORY" "$run_id" --json status --jq '.status')"
if [[ "$status" == "completed" ]]; then
break
fi
@@ -221,14 +221,14 @@ jobs:
done
trap - EXIT INT TERM
conclusion="$(gh run view "$run_id" --json conclusion --jq '.conclusion')"
url="$(gh run view "$run_id" --json url --jq '.url')"
conclusion="$(gh run view --repo "$GITHUB_REPOSITORY" "$run_id" --json conclusion --jq '.conclusion')"
url="$(gh run view --repo "$GITHUB_REPOSITORY" "$run_id" --json url --jq '.url')"
echo "${workflow} finished with ${conclusion}: ${url}"
{
echo "- ${workflow}: ${conclusion} (${url})"
} >> "$GITHUB_STEP_SUMMARY"
if [[ "$conclusion" != "success" ]]; then
gh run view "$run_id" --json jobs --jq '.jobs[] | select(.conclusion != "success" and .conclusion != "skipped") | {name, conclusion, url}' || true
gh run view --repo "$GITHUB_REPOSITORY" "$run_id" --json jobs --jq '.jobs[] | select(.conclusion != "success" and .conclusion != "skipped") | {name, conclusion, url}' || true
exit 1
fi
}

View File

@@ -32,7 +32,7 @@ env:
CLAWHUB_REGISTRY: "https://clawhub.ai"
CLAWHUB_REPOSITORY: "openclaw/clawhub"
# Pinned to a reviewed ClawHub commit so release behavior stays reproducible.
CLAWHUB_REF: "48e66714ac2352d52b193a90ae911cd92463c20a"
CLAWHUB_REF: "facf20ceb6cc459e2872d941e71335a784bbc55c"
jobs:
preview_plugins_clawhub:
@@ -50,7 +50,7 @@ jobs:
uses: actions/checkout@v6
with:
persist-credentials: false
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || github.sha }}
ref: ${{ github.ref }}
fetch-depth: 0
- name: Setup Node environment
@@ -62,14 +62,29 @@ jobs:
- name: Resolve checked-out ref
id: ref
run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
- name: Validate ref is on main or a release branch
env:
TARGET_REF: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || '' }}
run: |
set -euo pipefail
git fetch --no-tags origin \
+refs/heads/main:refs/remotes/origin/main \
'+refs/heads/release/*:refs/remotes/origin/release/*'
if [[ -n "${TARGET_REF}" ]]; then
if git rev-parse --verify --quiet "${TARGET_REF}^{commit}" >/dev/null; then
target_sha="$(git rev-parse "${TARGET_REF}^{commit}")"
elif git rev-parse --verify --quiet "origin/${TARGET_REF}^{commit}" >/dev/null; then
target_sha="$(git rev-parse "origin/${TARGET_REF}^{commit}")"
else
echo "Unable to resolve requested publish ref: ${TARGET_REF}" >&2
exit 1
fi
git checkout --detach "${target_sha}"
fi
echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
- name: Validate ref is on main or a release branch
run: |
set -euo pipefail
if git merge-base --is-ancestor HEAD origin/main; then
exit 0
fi
@@ -153,6 +168,12 @@ jobs:
echo "::error::One or more selected plugin versions already exist on ClawHub. Bump the version before running a real publish."
exit 1
- name: Verify OpenClaw ClawHub package ownership
if: steps.plan.outputs.has_candidates == 'true'
env:
CLAWHUB_REGISTRY: ${{ env.CLAWHUB_REGISTRY }}
run: node --import tsx scripts/plugin-clawhub-owner-preflight.ts .local/plugin-clawhub-release-plan.json
preview_plugin_pack:
needs: preview_plugins_clawhub
if: needs.preview_plugins_clawhub.outputs.has_candidates == 'true'
@@ -161,7 +182,7 @@ jobs:
contents: read
strategy:
fail-fast: false
max-parallel: 1
max-parallel: 6
matrix:
plugin: ${{ fromJson(needs.preview_plugins_clawhub.outputs.matrix) }}
steps:
@@ -169,8 +190,18 @@ jobs:
uses: actions/checkout@v6
with:
persist-credentials: false
ref: ${{ needs.preview_plugins_clawhub.outputs.ref_revision }}
fetch-depth: 1
ref: ${{ github.ref }}
fetch-depth: 0
- name: Checkout target revision
env:
TARGET_SHA: ${{ needs.preview_plugins_clawhub.outputs.ref_revision }}
run: |
set -euo pipefail
git fetch --no-tags origin \
+refs/heads/main:refs/remotes/origin/main \
'+refs/heads/release/*:refs/remotes/origin/release/*'
git checkout --detach "${TARGET_SHA}"
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
@@ -178,16 +209,22 @@ jobs:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "true"
install-deps: "false"
install-deps: "true"
- name: Checkout ClawHub CLI source
uses: actions/checkout@v6
with:
persist-credentials: false
repository: ${{ env.CLAWHUB_REPOSITORY }}
ref: ${{ env.CLAWHUB_REF }}
ref: main
path: clawhub-source
fetch-depth: 1
fetch-depth: 0
- name: Checkout pinned ClawHub CLI revision
working-directory: clawhub-source
env:
CLAWHUB_REF: ${{ env.CLAWHUB_REF }}
run: git checkout --detach "${CLAWHUB_REF}"
- name: Install ClawHub CLI dependencies
working-directory: clawhub-source
@@ -203,6 +240,9 @@ jobs:
chmod +x "$RUNNER_TEMP/clawhub"
echo "$RUNNER_TEMP" >> "$GITHUB_PATH"
- name: Verify package-local runtime build
run: node scripts/check-plugin-npm-runtime-builds.mjs --package "${{ matrix.plugin.packageDir }}"
- name: Preview publish command
env:
CLAWHUB_REGISTRY: ${{ env.CLAWHUB_REGISTRY }}
@@ -223,6 +263,7 @@ jobs:
id-token: write
strategy:
fail-fast: false
max-parallel: 6
matrix:
plugin: ${{ fromJson(needs.preview_plugins_clawhub.outputs.matrix) }}
steps:
@@ -230,8 +271,18 @@ jobs:
uses: actions/checkout@v6
with:
persist-credentials: false
ref: ${{ needs.preview_plugins_clawhub.outputs.ref_revision }}
fetch-depth: 1
ref: ${{ github.ref }}
fetch-depth: 0
- name: Checkout target revision
env:
TARGET_SHA: ${{ needs.preview_plugins_clawhub.outputs.ref_revision }}
run: |
set -euo pipefail
git fetch --no-tags origin \
+refs/heads/main:refs/remotes/origin/main \
'+refs/heads/release/*:refs/remotes/origin/release/*'
git checkout --detach "${TARGET_SHA}"
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
@@ -239,16 +290,22 @@ jobs:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "true"
install-deps: "false"
install-deps: "true"
- name: Checkout ClawHub CLI source
uses: actions/checkout@v6
with:
persist-credentials: false
repository: ${{ env.CLAWHUB_REPOSITORY }}
ref: ${{ env.CLAWHUB_REF }}
ref: main
path: clawhub-source
fetch-depth: 1
fetch-depth: 0
- name: Checkout pinned ClawHub CLI revision
working-directory: clawhub-source
env:
CLAWHUB_REF: ${{ env.CLAWHUB_REF }}
run: git checkout --detach "${CLAWHUB_REF}"
- name: Install ClawHub CLI dependencies
working-directory: clawhub-source
@@ -304,7 +361,19 @@ jobs:
encoded_name="$(node -e 'console.log(encodeURIComponent(process.env.PACKAGE_NAME ?? ""))')"
encoded_version="$(node -e 'console.log(encodeURIComponent(process.env.PACKAGE_VERSION ?? ""))')"
url="${CLAWHUB_REGISTRY%/}/api/v1/packages/${encoded_name}/versions/${encoded_version}"
status="$(curl --silent --show-error --output /dev/null --write-out '%{http_code}' "${url}")"
status=""
for attempt in $(seq 1 8); do
status="$(curl --silent --show-error --output /dev/null --write-out '%{http_code}' "${url}")"
if [[ "${status}" == "404" || "${status}" =~ ^2 ]]; then
break
fi
if [[ "${status}" == "429" || "${status}" =~ ^5 ]]; then
echo "ClawHub availability check returned ${status} for ${PACKAGE_NAME}@${PACKAGE_VERSION}; retrying (${attempt}/8)."
sleep 60
continue
fi
break
done
if [[ "${status}" =~ ^2 ]]; then
echo "${PACKAGE_NAME}@${PACKAGE_VERSION} is already published on ClawHub."
exit 1

View File

@@ -13,6 +13,7 @@ on:
- "scripts/plugin-npm-publish.sh"
- "scripts/plugin-npm-release-check.ts"
- "scripts/plugin-npm-release-plan.ts"
- "scripts/verify-plugin-npm-published-runtime.mjs"
workflow_dispatch:
inputs:
publish_scope:
@@ -224,3 +225,9 @@ jobs:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
run: bash scripts/plugin-npm-publish.sh --publish "${{ matrix.plugin.packageDir }}"
- name: Verify published runtime
env:
PACKAGE_NAME: ${{ matrix.plugin.packageName }}
PACKAGE_VERSION: ${{ matrix.plugin.version }}
run: node scripts/verify-plugin-npm-published-runtime.mjs "${PACKAGE_NAME}@${PACKAGE_VERSION}"

View File

@@ -18,6 +18,10 @@ on:
description: Optional comma-separated Discord scenario ids
required: false
type: string
slack_scenario:
description: Optional comma-separated Slack scenario ids
required: false
type: string
matrix_profile:
description: Matrix QA profile for the live Matrix lane
required: false
@@ -554,3 +558,97 @@ jobs:
path: ${{ steps.run_lane.outputs.output_dir }}
retention-days: 14
if-no-files-found: warn
run_live_slack:
name: Run Slack live QA lane with Convex leases
needs: [authorize_actor, validate_selected_ref]
if: vars.OPENCLAW_QA_SLACK_LIVE_CI_ENABLED == 'true'
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 60
environment: qa-live-shared
steps:
- name: Checkout selected ref
uses: actions/checkout@v6
with:
persist-credentials: false
ref: ${{ needs.validate_selected_ref.outputs.selected_revision }}
fetch-depth: 1
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "true"
- name: Validate required QA credential env
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENCLAW_QA_CONVEX_SITE_URL: ${{ secrets.OPENCLAW_QA_CONVEX_SITE_URL }}
OPENCLAW_QA_CONVEX_SECRET_CI: ${{ secrets.OPENCLAW_QA_CONVEX_SECRET_CI }}
shell: bash
run: |
set -euo pipefail
require_var() {
local key="$1"
if [[ -z "${!key:-}" ]]; then
echo "Missing required ${key}." >&2
exit 1
fi
}
require_var OPENAI_API_KEY
require_var OPENCLAW_QA_CONVEX_SITE_URL
require_var OPENCLAW_QA_CONVEX_SECRET_CI
- name: Build private QA runtime
run: pnpm build
- name: Run Slack live lane
id: run_lane
shell: bash
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENCLAW_QA_CONVEX_SITE_URL: ${{ secrets.OPENCLAW_QA_CONVEX_SITE_URL }}
OPENCLAW_QA_CONVEX_SECRET_CI: ${{ secrets.OPENCLAW_QA_CONVEX_SECRET_CI }}
OPENCLAW_QA_REDACT_PUBLIC_METADATA: "1"
OPENCLAW_QA_SLACK_CAPTURE_CONTENT: "1"
INPUT_SCENARIO: ${{ github.event_name == 'workflow_dispatch' && inputs.slack_scenario || '' }}
run: |
set -euo pipefail
output_dir=".artifacts/qa-e2e/slack-live-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
scenario_args=()
if [[ -n "${INPUT_SCENARIO// }" ]]; then
IFS=',' read -r -a raw_scenarios <<<"${INPUT_SCENARIO}"
for raw in "${raw_scenarios[@]}"; do
scenario="$(printf '%s' "${raw}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
if [[ -n "${scenario}" ]]; then
scenario_args+=(--scenario "${scenario}")
fi
done
fi
echo "output_dir=${output_dir}" >> "$GITHUB_OUTPUT"
pnpm openclaw qa slack \
--repo-root . \
--output-dir "${output_dir}" \
--provider-mode live-frontier \
--model "${OPENCLAW_CI_OPENAI_MODEL}" \
--alt-model "${OPENCLAW_CI_OPENAI_MODEL}" \
--fast \
--credential-source convex \
--credential-role ci \
"${scenario_args[@]}"
- name: Upload Slack QA artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: qa-live-slack-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ steps.run_lane.outputs.output_dir }}
retention-days: 14
if-no-files-found: warn

View File

@@ -0,0 +1,200 @@
name: Windows Blacksmith Testbox
on:
workflow_dispatch:
inputs:
testbox_id:
type: string
description: "Testbox session ID"
required: true
runner_label:
type: string
description: "Windows runner label"
required: false
default: "blacksmith-16vcpu-windows-2025"
permissions:
contents: read
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
jobs:
windows:
name: windows
runs-on: ${{ inputs.runner_label }}
timeout-minutes: 75
defaults:
run:
shell: pwsh
steps:
- name: Begin Testbox
shell: bash
env:
TESTBOX_ID: ${{ inputs.testbox_id }}
run: |
set -euo pipefail
metadata_port="${METADATA_PORT:-}"
if [ -z "$metadata_port" ]; then
metadata_port="$(cat /proc/cmdline | tr ' ' '\n' | grep '^metadata_port=' | cut -d= -f2)"
fi
if [ -z "$metadata_port" ]; then
echo "metadata_port not found in kernel cmdline" >&2
exit 1
fi
metadata_addr="192.168.127.1:${metadata_port}"
state=/tmp/.testbox
mkdir -p "$state"
chmod 700 "$state"
installation_model_id="$(curl -s --connect-timeout 2 --max-time 5 "http://${metadata_addr}/installationModelID")"
api_url="$(curl -s --connect-timeout 2 --max-time 5 "http://${metadata_addr}/backendURL")"
auth_token="$(curl -s --connect-timeout 2 --max-time 5 "http://${metadata_addr}/stickyDiskToken")"
if [ -z "$api_url" ] || [ -z "$installation_model_id" ] || [ -z "$auth_token" ]; then
echo "could not read required Blacksmith metadata" >&2
exit 1
fi
if [ -n "${BLACKSMITH_HOSTNAME:-}" ]; then
runner_host="$BLACKSMITH_HOSTNAME"
else
runner_host="${BLACKSMITH_HOST_PUBLIC_IP:-}"
fi
runner_ssh_port="${BLACKSMITH_SSH_PORT:-22}"
response="$(curl -s -f -L --post302 --post303 -X POST "${api_url}/api/testbox/phone-home" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${auth_token}" \
-d "{
\"testbox_id\": \"${TESTBOX_ID}\",
\"installation_model_id\": ${installation_model_id},
\"status\": \"hydrating\",
\"ip_address\": \"${runner_host}\",
\"ssh_port\": \"${runner_ssh_port}\",
\"working_directory\": \"${GITHUB_WORKSPACE}\",
\"adopted_run_id\": \"${GITHUB_RUN_ID}\",
\"metadata\": {}
}" 2>/dev/null || true)"
echo "$TESTBOX_ID" > "$state/testbox_id"
echo "$installation_model_id" > "$state/installation_model_id"
echo "$auth_token" > "$state/auth_token"
echo "$api_url" > "$state/api_url"
echo "$runner_host" > "$state/runner_host"
echo "$runner_ssh_port" > "$state/runner_ssh_port"
echo "$GITHUB_WORKSPACE" > "$state/working_directory"
echo "$GITHUB_RUN_ID" > "$state/adopted_run_id"
if [ -n "$response" ] && echo "$response" | jq -e . >/dev/null 2>&1; then
echo "$response" | jq -r '.ssh_public_key // empty' > "$state/ssh_public_key"
idle_timeout="$(echo "$response" | jq -r '.idle_timeout // empty')"
echo "${idle_timeout:-10}" > "$state/idle_timeout"
echo "phone-home response=json"
else
printf '%s\n' "$response" > "$state/ssh_public_key"
echo "10" > "$state/idle_timeout"
echo "phone-home response=raw"
fi
ssh_public_key="$(cat "$state/ssh_public_key" 2>/dev/null || true)"
if [ -n "$ssh_public_key" ]; then
mkdir -p ~/.ssh
printf '%s\n' "$ssh_public_key" >> ~/.ssh/authorized_keys
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
fi
- name: Checkout
uses: actions/checkout@v6
with:
persist-credentials: false
submodules: false
- name: Prepare Windows shell
run: |
$ErrorActionPreference = "Stop"
Write-Host "runner=$env:RUNNER_NAME"
Write-Host "machine=$env:COMPUTERNAME"
Write-Host ("os=" + [System.Environment]::OSVersion.VersionString)
Write-Host ("powershell=" + $PSVersionTable.PSVersion.ToString())
git --version
- name: Run Testbox
shell: bash
run: |
set -euo pipefail
state=/tmp/.testbox
test -d "$state"
testbox_id="$(cat "$state/testbox_id")"
installation_model_id="$(cat "$state/installation_model_id")"
auth_token="$(cat "$state/auth_token")"
idle_timeout="$(cat "$state/idle_timeout" 2>/dev/null || true)"
idle_timeout="${idle_timeout:-10}"
api_url="$(cat "$state/api_url")"
runner_host="$(cat "$state/runner_host")"
runner_ssh_port="$(cat "$state/runner_ssh_port")"
working_directory="$(cat "$state/working_directory")"
adopted_run_id="$(cat "$state/adopted_run_id")"
ready_body="$RUNNER_TEMP/testbox-ready.json"
cat > "$ready_body" <<JSON
{
"testbox_id": "${testbox_id}",
"installation_model_id": ${installation_model_id},
"status": "ready",
"ip_address": "${runner_host}",
"ssh_port": "${runner_ssh_port}",
"working_directory": "${working_directory}",
"adopted_run_id": "${adopted_run_id}",
"metadata": {}
}
JSON
http_code="$(curl -sS -L --post302 --post303 -o "$RUNNER_TEMP/testbox-ready.response" -w '%{http_code}' \
-X POST "${api_url}/api/testbox/phone-home" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${auth_token}" \
--data-binary @"$ready_body" || true)"
echo "phone_home_ready_http=${http_code}"
echo "============================================"
echo "Testbox ready!"
echo " Testbox ID: ${testbox_id}"
echo " Runner host: ${runner_host}"
echo " SSH port: ${runner_ssh_port}"
echo " Working directory: ${working_directory}"
echo " Run ID: ${adopted_run_id}"
echo " SSH: ssh -p ${runner_ssh_port} runner@${runner_host}"
echo "============================================"
last_activity="$(date +%s)"
idle_timeout_seconds=$(( idle_timeout * 60 ))
while true; do
sleep 30
now="$(date +%s)"
if netstat -na 2>/dev/null | grep ":${runner_ssh_port}" | grep -q ESTABLISHED; then
last_activity="$now"
elif [ -f ~/.testbox-last-activity ]; then
file_mtime="$(stat -c %Y ~/.testbox-last-activity 2>/dev/null || stat -f %m ~/.testbox-last-activity)"
if [ "$file_mtime" -gt "$last_activity" ]; then
last_activity="$file_mtime"
fi
fi
idle_seconds=$(( now - last_activity ))
if [ "$idle_seconds" -ge "$idle_timeout_seconds" ]; then
echo "Idle timeout reached (${idle_timeout} minutes). Shutting down."
exit 0
fi
done
- name: Testbox action marker
if: ${{ false }}
uses: useblacksmith/run-testbox@5ca05834db1d3813554d1dd109e5f2087a8d7cbc

View File

@@ -0,0 +1,189 @@
name: Windows Testbox Probe
on:
workflow_dispatch:
inputs:
target_ref:
description: "Git ref or SHA to check out"
required: false
default: "main"
type: string
runner_label:
description: "Windows runner label"
required: false
default: "blacksmith-16vcpu-windows-2025"
type: choice
options:
- blacksmith-16vcpu-windows-2025
- blacksmith-32vcpu-windows-2025
- windows-2025
keepalive_minutes:
description: "Minutes to keep the Windows runner alive for SSH inspection"
required: false
default: "20"
type: string
require_wsl2:
description: "Fail the run when WSL2 is unavailable"
required: false
default: false
type: boolean
import_ubuntu_wsl2:
description: "Import a throwaway Ubuntu WSL2 distro when none is installed"
required: false
default: false
type: boolean
enable_wsl2_features:
description: "Try enabling Windows WSL2/VM optional features before probing"
required: false
default: false
type: boolean
permissions:
contents: read
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
jobs:
probe:
name: Windows probe
runs-on: ${{ inputs.runner_label }}
timeout-minutes: 75
defaults:
run:
shell: pwsh
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: ${{ inputs.target_ref || github.ref }}
persist-credentials: false
submodules: false
- name: Probe native Windows
run: |
$ErrorActionPreference = "Stop"
Write-Host "runner=$env:RUNNER_NAME"
Write-Host "machine=$env:COMPUTERNAME"
Write-Host "workspace=$env:GITHUB_WORKSPACE"
Write-Host "target_ref=${{ inputs.target_ref || github.ref }}"
Write-Host ("os=" + [System.Environment]::OSVersion.VersionString)
Write-Host ("arch=" + [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture)
Write-Host ("powershell=" + $PSVersionTable.PSVersion.ToString())
cmd.exe /c ver
git --version
- name: Probe WSL2
id: wsl2
env:
ENABLE_WSL2_FEATURES: ${{ inputs.enable_wsl2_features }}
IMPORT_UBUNTU_WSL2: ${{ inputs.import_ubuntu_wsl2 }}
UBUNTU_WSL_ROOTFS_URL: https://cloud-images.ubuntu.com/wsl/releases/24.04/current/ubuntu-noble-wsl-amd64-wsl.rootfs.tar.gz
run: |
$ErrorActionPreference = "Continue"
$ok = $false
function Invoke-WslText {
param([string[]] $Arguments)
$output = & wsl.exe @Arguments 2>&1
$code = $LASTEXITCODE
$text = (($output | ForEach-Object { "$_" }) -join "`n") -replace "`0", ""
[pscustomobject]@{ Code = $code; Text = $text }
}
function Get-WslDistros {
$result = Invoke-WslText -Arguments @("--list", "--quiet")
$result.Text -split "\r?\n" |
ForEach-Object { $_.Trim() } |
Where-Object {
$_ -and
$_ -notmatch "Windows Subsystem for Linux has no installed distributions" -and
$_ -notmatch "^Use 'wsl\.exe" -and
$_ -notmatch "^and 'wsl\.exe"
}
}
$wsl = Get-Command wsl.exe -ErrorAction SilentlyContinue
if (-not $wsl) {
Write-Warning "wsl.exe is not available on this runner."
} else {
Write-Host "wsl.exe=$($wsl.Source)"
if ($env:ENABLE_WSL2_FEATURES -eq "true") {
Write-Host "enable_wsl2_features=true"
foreach ($feature in @("Microsoft-Windows-Subsystem-Linux", "VirtualMachinePlatform", "HypervisorPlatform", "Microsoft-Hyper-V-All")) {
dism.exe /online /enable-feature /featurename:$feature /all /norestart
Write-Host "enable_feature_${feature}_exit=$LASTEXITCODE"
}
}
$status = Invoke-WslText -Arguments @("--status")
Write-Host $status.Text
Write-Host "wsl_status_exit=$($status.Code)"
$list = Invoke-WslText -Arguments @("--list", "--verbose")
Write-Host $list.Text
Write-Host "wsl_list_exit=$($list.Code)"
$distros = @(Get-WslDistros)
if ($distros.Count -eq 0 -and $env:IMPORT_UBUNTU_WSL2 -eq "true") {
Write-Host "import_ubuntu_wsl2=true"
$wslRoot = "C:\wsl\UbuntuProbe"
$rootfs = "C:\wsl\ubuntu-noble-wsl.rootfs.tar.gz"
New-Item -ItemType Directory -Force -Path @((Split-Path -Parent $rootfs), $wslRoot) | Out-Null
Invoke-WebRequest -Uri $env:UBUNTU_WSL_ROOTFS_URL -OutFile $rootfs -UseBasicParsing
wsl.exe --import UbuntuProbe $wslRoot $rootfs --version 2
Write-Host "wsl_import_exit=$LASTEXITCODE"
$list = Invoke-WslText -Arguments @("--list", "--verbose")
Write-Host $list.Text
Write-Host "wsl_list_after_import_exit=$($list.Code)"
$distros = @(Get-WslDistros)
}
if ($distros.Count -gt 0) {
$distro = $distros[0]
Write-Host "wsl_probe_distro=$distro"
wsl.exe -d $distro --exec bash -lc 'set -euo pipefail; uname -a; if [ -f /etc/os-release ]; then sed -n "1,8p" /etc/os-release; fi'
} else {
wsl.exe --exec bash -lc 'set -euo pipefail; uname -a; if [ -f /etc/os-release ]; then sed -n "1,8p" /etc/os-release; fi'
}
if ($LASTEXITCODE -eq 0) {
$ok = $true
}
Write-Host "wsl_exec_exit=$LASTEXITCODE"
}
if ($ok) {
"wsl2_ok=true" >> $env:GITHUB_OUTPUT
"OPENCLAW_WSL2_PROBE_OK=true" >> $env:GITHUB_ENV
Write-Host "wsl2_ok=true"
} else {
"wsl2_ok=false" >> $env:GITHUB_OUTPUT
"OPENCLAW_WSL2_PROBE_OK=false" >> $env:GITHUB_ENV
Write-Warning "wsl2_ok=false"
}
exit 0
- name: Keep runner alive for SSH inspection
env:
KEEPALIVE_MINUTES: ${{ inputs.keepalive_minutes }}
run: |
$ErrorActionPreference = "Stop"
$minutes = 20
if ($env:KEEPALIVE_MINUTES -match '^\d+$') {
$minutes = [int]$env:KEEPALIVE_MINUTES
}
$minutes = [Math]::Max(0, [Math]::Min($minutes, 60))
Write-Host "keepalive_minutes=$minutes"
for ($i = 1; $i -le $minutes; $i++) {
Write-Host "keepalive minute $i/$minutes"
Start-Sleep -Seconds 60
}
- name: Enforce WSL2 requirement
if: ${{ inputs.require_wsl2 }}
run: |
if ($env:OPENCLAW_WSL2_PROBE_OK -ne "true") {
Write-Error "WSL2 probe failed or WSL2 is unavailable on this Windows runner."
exit 1
}

View File

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

@@ -25,7 +25,7 @@ repos:
rev: v0.11.0
hooks:
- id: shellcheck
args: [--severity=error] # Only fail on errors, not warnings/info
args: [--rcfile=config/shellcheckrc, --severity=error] # Only fail on errors, not warnings/info
# Exclude vendor and scripts with embedded code or known issues
exclude: "^(vendor/|scripts/e2e/)"
@@ -40,7 +40,14 @@ repos:
rev: v1.22.0
hooks:
- id: zizmor
args: [--persona=regular, --min-severity=medium, --min-confidence=medium]
args:
[
--config,
.github/zizmor.yml,
--persona=regular,
--min-severity=medium,
--min-confidence=medium,
]
exclude: "^(vendor/|apps/swabble/)"
# Python checks for skills scripts
@@ -49,13 +56,13 @@ repos:
hooks:
- id: ruff
files: "^skills/.*\\.py$"
args: [--config, pyproject.toml]
args: [--config, skills/pyproject.toml]
- repo: local
hooks:
- id: skills-python-tests
name: skills python tests
entry: pytest -q skills
entry: pytest -q -c skills/pyproject.toml skills
language: python
additional_dependencies: [pytest>=8, <9]
pass_filenames: false
@@ -90,7 +97,7 @@ repos:
# swiftlint (same as CI)
- id: swiftlint
name: swiftlint
entry: swiftlint --config .swiftlint.yml
entry: swiftlint lint --config config/swiftlint.yml
language: system
pass_filenames: false
types: [swift]
@@ -98,7 +105,7 @@ repos:
# swiftformat --lint (same as CI)
- id: swiftformat
name: swiftformat
entry: swiftformat --lint apps/macos/Sources --config .swiftformat
entry: swiftformat --lint apps/macos/Sources --config config/swiftformat --exclude '**/OpenClawProtocol,**/HostEnvSecurityPolicy.generated.swift'
language: system
pass_filenames: false
types: [swift]

View File

@@ -30,6 +30,7 @@ Telegraph style. Root rules only. Read scoped `AGENTS.md` before subtree work.
- Core/tests: no deep plugin internals (`extensions/*/src/**`, `onboard.js`). Use `api.ts`, SDK facade, generic contracts.
- Extension-owned behavior stays extension-owned: repair, detection, onboarding, auth/provider defaults, provider tools/settings.
- Owner boundary: fix owner-specific behavior in the owner module. Shared/core gets generic seams only; no owner ids, dependency strings, defaults, migrations, or recovery policy. If a bug names an extension or its dependency, start in that extension and add a generic core seam only when multiple owners need it.
- Dependency ownership follows runtime ownership: extension-only deps stay plugin-local; root deps only for core imports or intentionally internalized bundled plugin runtime.
- Legacy config repair: doctor/fix paths, not startup/load-time core migrations.
- Core test asserting extension-specific behavior: move to owner extension or generic contract test.
- New seams: backwards-compatible, documented, versioned. Third-party plugins exist.
@@ -69,7 +70,9 @@ Telegraph style. Root rules only. Read scoped `AGENTS.md` before subtree work.
- GitHub search boolean text is fussy. If `OR` queries return empty, split exact terms and search title/body/comments separately before concluding no hits.
- PR shortlist: `gh pr list ...`; then `gh pr view <n> --json number,title,body,closingIssuesReferences,files,statusCheckRollup,reviewDecision`.
- After landing PR: search duplicate open issues/PRs. Before closing: comment why + canonical link.
- If an issue/PR is already fixed on current `main` or solved by a new release: comment with proof + canonical commit/PR/release, then close.
- GH comments with markdown backticks, `$`, or shell snippets: avoid inline double-quoted `--body`; use single quotes or `--body-file`.
- PR create: description/body always required. Include concise Summary + Verification sections; mention issue/PR refs, behavior changed, and exact local/Testbox/CI proof. Never open an empty-description, empty-body, or placeholder-body PR.
- PR execution artifacts/screenshots: attach them to the PR, comment, or an external artifact store. Do not add `.github/pr-assets` or other PR-only assets to the repo.
- PR review answer must explicitly cover: what bug/behavior we are trying to fix; PR/issue URL(s) and affected endpoint/surface; whether this is the best possible fix, with high-certainty evidence from code, tests, CI, and shipped/current behavior.
- When working on an issue or PR, always end the user-facing final answer with the full GitHub URL.
@@ -160,6 +163,7 @@ Telegraph style. Root rules only. Read scoped `AGENTS.md` before subtree work.
keep chasing `main` with repeated full gates after one green run plus a clean
rebase sanity pass.
- User says `commit`: your changes only. `commit all`: all changes in grouped chunks. `push`: may `git pull --rebase` first.
- User says `ship it`: changelog if needed, commit intended changes, pull --rebase, push.
- Do not delete/rename unexpected files; ask if blocking, else ignore.
- Bulk PR close/reopen >5: ask with count/scope.
- PR/issue workflows: `$openclaw-pr-maintainer`. `/landpr`: `~/.codex/prompts/landpr.md`.

View File

@@ -6,32 +6,399 @@ Docs: https://docs.openclaw.ai
### Highlights
- Plugins/file-transfer: add bundled file-transfer plugin with `file_fetch`, `dir_list`, `dir_fetch`, and `file_write` agent tools for binary file ops on paired nodes; default-deny per-node path policy under `plugins.entries.file-transfer.config.nodes` with operator approval, symlink traversal refused by default (opt-in `followSymlinks`), and a 16 MB byte ceiling per round-trip. (#74742) Thanks @omarshahine.
- Google Meet/Voice Call: make Twilio dial-in joins speak through the realtime Gemini voice bridge with paced audio streaming, backpressure-aware buffering, barge-in queue clearing, and no TwiML fallback during realtime speech, giving Meet participants a much snappier OpenClaw voice agent. (#77064) Thanks @scoootscooob.
### Changes
- Gateway/performance: lazy-load early runtime discovery and shutdown-hook helpers, defer maintenance timers until after readiness, and trim duplicate plugin auto-enable work during Gateway startup.
- Gateway/performance: lazy-load the heavy cron runtime after the rest of Gateway startup, defer restart-sentinel refresh after readiness, and let the Gateway startup benchmark write per-run V8 CPU profiles with `--cpu-prof-dir`.
- Gateway/performance: keep raw channel-config schema parsing from discovering bundled plugin runtime metadata, and add `pnpm gateway:watch --benchmark-no-force` for profiling startup without the default port cleanup.
- Plugins/onboarding: let Manual setup install optional official plugins, including ClawHub-backed diagnostics with npm fallback, and expose the external Codex plugin as a selectable provider setup choice. Thanks @vincentkoc.
- Plugins/CLI: include package dependency install state in `openclaw plugins list --json` so scripts can spot missing plugin dependencies without runtime-loading plugins.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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: on the beta OpenClaw update channel, default-line npm and ClawHub plugin updates try `@beta` first and fall back to default/latest when no plugin beta release exists.
- 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.
- 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.
- 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.
- Exec approvals: add a tree-sitter-backed shell command explainer for future approval and command-review surfaces. (#75004) Thanks @jesse-merhi.
- 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/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.
- 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.
- Plugin SDK: add plugin-owned `SessionEntry` slot projection and scoped trusted-policy session extension reads. (#75609; replaces part of #73384/#74483) Thanks @100yenadmin.
### Fixes
- 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.
- Doctor/config: report missing catalog-backed channel plugin entries as repairable installs, so upgrades with Discord or WhatsApp config point to `openclaw doctor --fix` instead of telling users to remove valid plugin config. Fixes #77483.
- 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.
- 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.
- Browser: enforce strict SSRF current-URL checks before existing-session screenshots, matching existing-session snapshot handling. Thanks @vincentkoc.
- Active Memory: give timeout partial transcript recovery enough abort-settle headroom so temporary recall summaries are returned before cleanup. Thanks @vincentkoc.
- 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.
- Agents/media: avoid sending generated image, video, and music attachments twice when streamed reply text arrives before the final `MEDIA:` directive.
- 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.
- 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.
- 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.
- 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.
- 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.
- fix(gateway): clamp unbound websocket auth scopes [AI]. (#77413) Thanks @pgondhi987.
- Gate zalouser startup name matching [AI]. (#77411) Thanks @pgondhi987.
- 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.
- fix(device-pair): require pairing scope for pair command [AI]. (#76377) Thanks @pgondhi987.
- 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.
- fix(qqbot): keep private commands off framework surface [AI]. (#77212) Thanks @pgondhi987.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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/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/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.
- 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.
- Google Meet: keep waiting for the Meet microphone to unmute during join intro readiness instead of permanently skipping talk-back when Meet briefly reports the local mic as muted.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
## 2026.5.3-1
### Fixes
- Plugins/security: stop the install scanner from blocking official bundled plugin packages when `process.env` access and normal API sends only appear in distant parts of the same compiled bundle. Thanks @vincentkoc.
## 2026.5.3
### Highlights
- Plugins/file-transfer: add bundled file-transfer plugin with `file_fetch`, `dir_list`, `dir_fetch`, and `file_write` agent tools for binary file ops on paired nodes; default-deny per-node path policy under `plugins.entries.file-transfer.config.nodes` with operator approval, symlink traversal refused by default (opt-in `followSymlinks`), and a 16 MB byte ceiling per round-trip. (#74742) Thanks @omarshahine.
- Plugins/install: harden official plugin install, uninstall, update, onboarding, ClawHub fallback, npm dependency-state reporting, and beta-channel update paths so externalized plugins behave like first-class package installs.
- Gateway/performance: trim startup and Control UI hot paths by lazy-loading plugin/runtime discovery, cron, schema, shutdown, sessions, and model metadata work only when needed.
- Channels/replies: improve Discord status reactions and degraded transport reporting, add WhatsApp Channel/Newsletter targets, and tighten Telegram, Feishu, Matrix, Microsoft Teams, and Slack delivery/recovery behavior.
- Install/update: recover broken macOS LaunchAgent upgrades, reject source-only plugin packages before runtime load, and repair stale Gateway/plugin state during updates and doctor runs.
- Agent/runtime reliability: preserve streamed provider replies, delayed A2A session replies, prompt/tool delivery, memory recall, web search provider discovery, and provider-specific thinking/model metadata across common edge cases.
### Changes
- Channels/streaming: add unified `streaming.mode: "progress"` drafts with auto single-word status labels and shared progress configuration across Discord, Telegram, Matrix, Slack, and Microsoft Teams.
- Agents/commands: add `/steer <message>` for queue-independent steering of the active current-session run without starting a new turn when the session is idle. (#76934)
- Tools/BTW: add `/side` as a text and native slash-command alias for `/btw` side questions.
- Doctor/config: `doctor --fix` now commits safe legacy migrations even when unrelated validation issues (e.g. a missing plugin) prevent full validation from passing, so `agents.defaults.llm` and other known-legacy keys are always cleaned up by `doctor --fix` regardless of other config problems. Fixes #76798. (#76800) Thanks @hclsys.
- Agents/tools: skip optional media and PDF tool factories when the effective tool denylist already blocks them, avoiding unnecessary hot-path setup for tools that will be filtered out before model use. (#76773) Thanks @dorukardahan.
- Discord/status: let explicit reaction tool calls opt into tracking subsequent tool progress on the reacted message with `trackToolCalls: true`, and use the shared tool display emoji table for status reactions.
- Gateway/config: stop Gateway startup and hot reload from auto-restoring invalid config; invalid config now fails closed and `openclaw doctor --fix` owns last-known-good repair.
- Gateway/performance: lazy-load early runtime discovery and shutdown-hook helpers, defer maintenance timers until after readiness, and trim duplicate plugin auto-enable work during Gateway startup.
- QA/Mantis: add a `pnpm openclaw qa mantis discord-smoke` runner and manual GitHub workflow that verify the Mantis Discord bot can see the configured guild/channel, post a smoke message, add a reaction, and upload artifacts.
- QA/Slack: add a Slack live transport QA runner with canary and mention-gating coverage for the private bot-to-bot harness. Thanks @vincentkoc.
- Plugins/onboarding: let Manual setup install optional official plugins, including ClawHub-backed diagnostics with npm fallback, and expose the external Codex plugin as a selectable provider setup choice. Thanks @vincentkoc.
- Plugins/CLI/update: include package dependency install state in `openclaw plugins list --json`, trust official externalized npm migrations, clean stale bundled load paths for externalized installs, try plugin `@beta` updates first on the beta OpenClaw channel, and fall back to default/latest when no plugin beta release exists.
- Plugins/ClawHub: annotate 429 errors with reset windows and unauthenticated higher-rate-limit hints, so operators can tell when downloads recover and when signing in helps. Thanks @romneyda.
- Gateway/performance: lazy-load early runtime discovery, shutdown hooks, cron, channel-config schema metadata, restart sentinels, and maintenance timers after readiness; trim duplicate plugin auto-enable work and add startup CPU/profile controls.
- Gateway/config: stop Gateway startup and hot reload from auto-restoring invalid config; invalid config now fails closed and `openclaw doctor --fix` owns last-known-good repair.
- Discord/status: let explicit reaction tool calls opt into tracking later tool progress with `trackToolCalls: true`, share tool display emoji mapping, and surface degraded Discord transport or gateway event-loop starvation in status output. (#76327) Thanks @joshavant.
- 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.
- Agents/tools: skip optional media and PDF tool factories when the effective tool denylist already blocks them, avoiding unnecessary hot-path setup for tools that will be filtered out before model use. (#76773) Thanks @dorukardahan.
- 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.
- Tools/BTW: add `/side` as a text and native slash-command alias for `/btw` side questions.
- Exec approvals: add a tree-sitter-backed shell command explainer for future approval and command-review surfaces. (#75004) Thanks @jesse-merhi.
- QA/Mantis: add a `pnpm openclaw qa mantis discord-smoke` runner and manual GitHub workflow that verify the Mantis Discord bot can see the configured guild/channel, post a smoke message, add a reaction, and upload artifacts.
### Fixes
- 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.
- 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.
- Channels/WhatsApp: allow `@whiskeysockets/libsignal-node` in `onlyBuiltDependencies` so pnpm v9+ `blockExoticSubdeps` no longer rejects the baileys git-tarball subdep and silences all inbound agent replies. Fixes #76539. Thanks @ottodeng and @vincentkoc.
- Gateway/systemd: preserve operator-added secrets in the Gateway env file across re-stage while clearing OpenClaw-managed keys (such as `OPENCLAW_GATEWAY_TOKEN`) so a fresh staging value is never shadowed by a stale env-file copy; operator secrets are also retained when the state-dir `.env` is empty. Fixes #76860. Thanks @hclsys.
- 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 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.
- QA/cache: require the full `CACHE-OK <suffix>` marker before live cache probes stop retrying, so suffix-only prose cannot hide a broken probe response. Thanks @vincentkoc.
- Slack/Matrix: avoid creating blank progress-draft messages when `streaming.progress.label=false` and progress tool lines are disabled. Thanks @vincentkoc.
- QA/Matrix: keep the mock OpenAI tool-progress provider aligned with exact-marker Matrix prompts so the hardened live preview scenario still forces a deterministic read before final delivery. Thanks @vincentkoc.
- OpenAI/Google Meet: wait for realtime voice `session.updated` before treating the bridge as connected, so Meet joins do not return with audio queued behind an unconfigured realtime session. Thanks @vincentkoc.
- Plugins/catalog: merge official external catalog descriptors into partial package channel config metadata, so lagging WeCom/Yuanbao manifests keep their own schema while still exposing host-supplied labels and setup text. Thanks @vincentkoc.
- Plugins/catalog: supplement lagging official external WeCom and Yuanbao npm manifests with channel config descriptors and declared tool contracts from the OpenClaw catalog, so trusted package sweeps no longer fail because external package metadata trails the host contract. Thanks @vincentkoc.
- Plugins/install: let trusted official `@openclaw/*` catalog installs recover when npm `latest` points at a prerelease by falling back to the newest stable version, or by selecting the newest exact prerelease for prerelease-only launch packages with a warning instead of making beta/development plugin sweeps fail at install time. Thanks @vincentkoc.
- Google Meet: grant Chrome media permissions against the actual Meet tab, start the local realtime audio bridge only after Meet joins, expose realtime transcripts in status/logs, and force explicit audio responses with current OpenAI realtime output-audio events so BlackHole capture does not keep the OpenClaw participant muted or silent.
- Memory/LanceDB: declare `apache-arrow` in the bundled memory plugin package so LanceDB installs include its runtime peer. Fixes #76910. Thanks @afiqfiles-max.
- CLI/devices: retry explicit device-pair approval with `operator.admin` after a pairing-scope ownership denial, so existing admin-capable paired-device tokens can recover new Control UI/browser pairing after upgrades instead of requiring manual JSON edits. Fixes #76956. Thanks @neo19482.
- Google Meet: use the local call-control microphone button instead of disabled remote participant mute buttons, and block realtime speech when the OpenClaw Meet microphone remains muted.
- Google Meet: refresh realtime browser state during status and retry delayed speech after Meet finishes joining, so a just-opened in-call tab no longer leaves speech stuck behind stale `not-in-call` health.
- Plugins/install: recover the install ledger from the managed npm root when `plugins/installs.json` is empty or partial, so reinstalling Discord and Codex no longer makes the other installed plugin disappear.
- Google Meet: grant Meet media permissions through the Playwright browser context when CDP grants do not affect the attached Chrome page, and report in-call microphone/speaker permission problems instead of marking realtime speech ready.
- QA/Slack: fail the live mention-gating scenario on any unexpected SUT reply, even when the reply does not echo the expected marker. Thanks @vincentkoc.
- QA/Matrix: steer the live tool-progress preview check away from `HEARTBEAT.md` and report final preview candidates when the live marker reply misses the exact token. Thanks @vincentkoc.
- QA/Matrix: let the live tool-progress preview check verify progress replacement events without depending on the preview saying `Working`. Thanks @vincentkoc.
- Tlon: expose `groupInviteAllowlist` in the channel config schema and clarify that group invite auto-accept fails closed without an invite allowlist. Thanks @vincentkoc.
- Control UI/WebChat: collapse duplicate in-flight internal text sends onto the active Gateway run so rapid repeat submits do not start fresh `agent:main:main` dispatches. Fixes #75737. Thanks @dsdsddd1 and @BunsDev.
- Mattermost: accept the documented `channels.mattermost.streaming` config and honor `streaming: "off"` by disabling draft preview posts. Thanks @vincentkoc.
- Mattermost: expose streaming progress config labels and help text in generated channel config metadata so Control UI/docs can explain the new `channels.mattermost.streaming.progress.*` fields. Thanks @vincentkoc.
- Mattermost: honor `channels.mattermost.streaming.progress.toolProgress=false` in progress draft mode so compact tool status lines stay hidden until final delivery. Thanks @vincentkoc.
- Microsoft Teams: honor progress draft tool lines in native Teams progress streams and suppress standalone tool messages when `channels.msteams.streaming.progress.toolProgress=false`. Thanks @vincentkoc.
- Discord: keep progress draft boundary callbacks bound during streaming replies, so extension lint stays green while progress previews transition between assistant and reasoning blocks. Thanks @vincentkoc.
- Discord: resolve SecretRef-backed bot tokens from the active runtime snapshot for named accounts and keep unresolved configured tokens from crashing status or health checks. (#76987) Thanks @joshavant.
- Channels/streaming: expose `streaming.progress.label`, `labels`, `maxLines`, and `toolProgress` in bundled channel config metadata so progress draft settings appear in config, docs, and control surfaces. Thanks @vincentkoc.
- Channels/streaming: normalize whitespace and case for `streaming.progress.label: "auto"` so progress draft labels keep using the built-in label pool instead of rendering a literal `auto` title. Thanks @vincentkoc.
- 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.
- 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.
- Cron/status: render explicit `delivery.mode: "none"` jobs as no-delivery previews and label cron session history distinctly instead of showing fallback delivery or direct-session rows. Fixes #76945.
- Gateway/usage: serve `usage.cost` and `sessions.usage` from a durable transcript aggregate cache with lock-safe background refreshes and localized stale-cache status, so large usage views avoid repeated full scans. (#76650) Thanks @Marvinthebored.
- Plugins/hooks: let `plugins.entries.<id>.hooks.timeoutMs` and `plugins.entries.<id>.hooks.timeouts` bound plugin typed hooks from operator config, so slow hooks can be tuned without patching installed plugin code. Fixes #76778. Thanks @vincentkoc.
- Telegram: add `channels.telegram.mediaGroupFlushMs` at the top level and per account so operators can tune album buffering instead of being stuck with the hard-coded 500ms media-group flush window. Fixes #76149. Thanks @vincentkoc.
- Config/messages: coerce boolean `messages.visibleReplies` and `messages.groupChat.visibleReplies` values to the documented enum modes so an intuitive toggle no longer invalidates config and drops channel startup. Fixes #75390. Thanks @scottgl9.
- Agents/network: allow trusted web-search providers and configured model-provider hosts to work behind Surge/Clash/sing-box fake-IP DNS by accepting RFC 2544 and IPv6 ULA synthetic answers only for the request's scoped hostname, without broad private-network access. Refs #76530 and #76549. Thanks @zqchris.
- Providers: honor env-proxy settings for guarded provider model fetches when no explicit dispatcher policy is configured, preserving explicit transport overrides. Fixes #70453. (#72480) Thanks @mjamiv.
- Web fetch: add a default-off `tools.web.fetch.useTrustedEnvProxy` opt-in for proxy-only environments so `web_fetch` can let an operator-controlled HTTP(S) proxy resolve DNS while preserving default strict DNS pinning and hostname policy checks. Refs #58034 and #62560. Thanks @cosmicnet and @mjamiv.
- Feishu: accept and honor `channels.feishu.blockStreaming` at the top level and per account, while keeping the legacy default off so Feishu cards no longer reject documented config or silently drop block replies. Fixes #75555. Thanks @vincentkoc.
- Gateway/update: avoid `launchctl kickstart -k` immediately after fresh macOS update bootstraps, and unlink dangling global plugin-runtime symlinks during packaged postinstall and `doctor --fix` so upgrades no longer SIGTERM the newly booted Gateway or leave bundled plugin imports pointed at pruned `plugin-runtime-deps` trees. Completes #76261 and fixes #76466. (#76929)
- Google Chat: normalize custom Google auth transport headers before google-auth/gaxios interceptors run, restoring webhook token verification when certificate retrieval expects Fetch `Headers`. Fixes #76742. Thanks @donbowman.
- Doctor/plugins: reset stale `plugins.slots.memory` and `plugins.slots.contextEngine` references during `doctor --fix`, so cleanup of missing plugin config does not leave unrecoverable slot owners behind. Fixes #76550 and #76551. Thanks @vincentkoc.
- Docs/WhatsApp: merge the duplicate top-level `web` objects in the gateway channel config example so copy-pasted WhatsApp config keeps both `web.whatsapp` and reconnect settings. Fixes #76619. Thanks @WadydX.
- Plugins/Anthropic: expose Claude thinking profiles from the bundled provider-policy artifact so non-runtime callers keep Opus 4.7 `adaptive`, `xhigh`, and `max` instead of downgrading to `high`. Fixes #76779. Thanks @tomascupr and @iAbhi001.
- Plugins/tools: honor `tools.alsoAllow` as an optional plugin tool discovery hint without treating its internal allow-all default as permission to load every manifest-marked optional plugin tool. Fixes #76616.
- Discord/native commands: skip slash-command registration and cleanup REST calls when `channels.discord.commands.native=false`, letting low-power gateways start without waiting on disabled native-command lifecycle requests. Fixes #76202. Thanks @vincentkoc.
- CLI/plugins: reject unowned command roots such as `openclaw foo` before managed proxy startup and full plugin CLI runtime loading while preserving manifest-owned and CLI-metadata-owned plugin commands. Fixes #75287. Thanks @neilofneils404.
- CLI/message: skip local configured-channel plugin preload for explicit gateway-owned message actions, letting normalized CLI delivery delegate to the gateway without initializing channel runtime in the short-lived CLI process. Fixes #75477.
- Plugins/commands: normalize empty plugin command handler results and let Telegram native plugin commands send the empty-response fallback instead of throwing when a handler returns `undefined`. Fixes #74800. Thanks @vincentkoc.
- Plugins/tools: cold-load selected plugin tool registries when the active registry only has partial tool coverage, so wildcard-expanded allowlists no longer hide installed plugin tools from `tools.effective`. Fixes #76780. Thanks @lilesjtu.
- Plugins/tools: compare cached and runtime plugin tool name conflicts with normalized core tool names, so case variants of core tools are blocked instead of leaking duplicate tool registrations. Thanks @vincentkoc.
- Plugins/OpenRouter: advertise DeepSeek V4 thinking levels, including `xhigh` and `max`, through the runtime and lightweight provider policy surfaces so `/think` validation no longer rejects OpenRouter-routed DeepSeek V4 models. Fixes #74788. Thanks @vincentkoc.
- Status/sessions: ignore malformed non-string persisted session provider/model metadata instead of throwing while rendering status summaries. Fixes #76206. Thanks @vincentkoc.
- CLI/config: remove only the targeted array element for `openclaw config unset array[index]` instead of replaying the unset during config write and deleting the shifted next element. Fixes #76290. Thanks @SymbolStar and @vincentkoc.
- Plugins/voice-call: treat abnormal local Gateway close code 1006 as a standalone CLI fallback case, so `voicecall smoke` and related commands can still run the provider check path when the Gateway socket closes before returning a response.
- CLI/doctor: migrate legacy per-channel `streaming.progress` config into `streaming.preview.toolProgress`, so upgrades with stale Discord or Telegram streaming keys validate again instead of blocking plugin commands.
- Plugins/release: reject ClawHub code-plugin packages that contain TypeScript runtime entries without compiled `dist/*.js` output, and run package-local runtime-build checks during npm and ClawHub plugin release previews.
- Plugins/update: keep beta-installed OpenClaw package updates on the beta plugin channel even when config still says stable, so Discord and other externalized plugins update from compiled `@beta` packages instead of stale source-only `latest` artifacts.
- Agents/tools: stop treating `tools.deny: ["write"]` as an implicit `apply_patch` deny; operators who want to block patch writes should deny `apply_patch` or `group:fs` explicitly. Fixes #76749. (#76795) Thanks @Nek-12 and @hclsys.
- Plugins/release: verify published plugin npm tarballs expose compiled runtime entries after publish, catching TS-only package artifacts before release closeout. Thanks @vincentkoc.
- 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.
- Google Meet: route stateful CLI session commands through the gateway-owned runtime so joined realtime sessions survive after the starting CLI process exits. Fixes #76344. Thanks @coltonharris-wq.
- Memory/status: split builtin sqlite-vec store readiness from embedding-provider readiness in `memory status --deep` and `openclaw status`, so local vector-store failures no longer look like provider failures and provider failures no longer hide a healthy local vector store.
- CLI/doctor: trust a ready gateway memory probe when CLI-side active memory backend resolution is unavailable, preventing false "No active memory plugin is registered" warnings for healthy runtime setups. Fixes #76792. Thanks @som-686.
- Memory/status: keep plain `openclaw memory status` and `openclaw memory status --json` on the cheap read-only path by reserving vector and embedding provider probes for `--deep` or `--index`. Fixes #76769. Thanks @daruire.
- Telegram: suppress stale same-session replies when a newer accepted message arrives before an older in-flight Telegram dispatch finalizes. Fixes #76642. Thanks @chinar-amrutkar.
- Gateway/diagnostics: throttle repeated long-running active-work session warnings so healthy cron or subagent runs no longer print the same `recovery=none` line every heartbeat.
- Gateway/diagnostics: keep non-blocking active-work and transient event-loop max-spike liveness diagnostics out of the default gateway console while preserving structured diagnostic events and warnings for queued, stalled, and recovery-eligible work.
- Slack: collapse routine Socket Mode pong-timeout reconnects into one OpenClaw reconnect line and suppress the duplicate Slack SDK pong warning.
- Gateway/diagnostics: abort-drain embedded runs after an extended no-progress stall so a single dead session no longer leaves queued Discord/channel turns blocked behind repeated `recovery=none` liveness warnings.
- Plugins/ClawHub: accept the live artifact resolver `kind`/`sha256` field names alongside the typed `artifactKind`/`artifactSha256` form so `clawhub:` installs of npm-pack and legacy ZIP packages no longer miss downloadable artifacts. Thanks @romneyda.
- Control UI/Sessions: avoid full `sessions.list` reloads for chat-turn `sessions.changed` payloads, so large session stores no longer add multi-second delays while chat responses are being delivered. (#76676) Thanks @VACInc.
- Gateway/watch: run `doctor --fix --non-interactive` once and retry when the dev Gateway child exits during startup, so stale local plugin install/config state does not leave the tmux watch session disappearing without a repair attempt.
- Doctor/Telegram: warn when selected Telegram quote replies can suppress `streaming.preview.toolProgress`, and document the `replyToMode` trade-off without changing runtime delivery. Fixes #73487. Thanks @GodsBoy.
- Channels/Discord: send a best-effort native typing cue immediately after an inbound DM is accepted, so slow pre-dispatch turns show Discord liveness before queueing, context assembly, model, or tool work starts. Fixes #76417. Thanks @mlopez14.
- Plugins/install: reject source-only TypeScript package installs and installed plugin packages that are missing compiled runtime output, so broken npm artifacts fail at install/discovery time instead of falling through jiti and surfacing later as unavailable providers. Fixes #76720.
- 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: 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.
- 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.
- Agents/Telegram: preserve explicit reply and quote context in embedded model prompts without letting quoted text drive prompt-local image loading. Fixes #76419. (#76659) Thanks @cheechnd.
- Active Memory: apply `setupGraceTimeoutMs` to the embedded recall runner as well as the outer prompt-build watchdog, so very-cold first recalls keep the configured setup grace end-to-end. (#74480) Thanks @volcano303.
- Agents/infer: let intentional no-tool `modelRun` and `llm-task` turns ignore inherited config allowlists while preserving explicit runtime `toolsAllow` failures, so gateway model probes and JSON LLM tasks no longer fail before reaching the provider. Fixes #74810. Thanks @amknight.
- Channels/Feishu: cap how long the per-chat sequential queue blocks subsequent same-key tasks behind a single in-flight task (5 min default), so a single hung dispatch no longer leaves later same-chat messages in `queued` state until gateway restart; the stuck task continues running but is evicted from the blocking chain and a warning is logged. Fixes #70133. (#76687) Thanks @martingarramon and @bek91.
- Active Memory: skip scoped Telegram forum-topic conversation ids (containing `:`) when resolving the embedded recall run channel, falling back to `messageProvider` instead, so Active Memory no longer throws a bundled-plugin dirName validation error in forum-topic sessions. Fixes #76704.
- Agents/tools: defer automatic PDF model/auth resolution until the PDF tool is used, keeping agent-turn tool prep from probing auth profiles on messages without PDFs while preserving explicit PDF model registration. Fixes #76644. Thanks @hclsys.
- CLI/config: keep JSON dry-run patches validating touched channel configuration against bundled channel schemas even when the patch only contains SecretRef objects.
- Plugins/tools: keep disabled bundled tool plugins out of explicit runtime allowlist ownership and fall back from loaded-but-empty channel registries to tool-bearing plugin registries, so Active Memory can use bundled `memory-core` search/get tools even when `memory-lancedb` is disabled. Fixes #76603. Thanks @jwong-art.
- Plugins/install: run `npm install` from the managed npm-root manifest so installing one `@openclaw/*` plugin preserves already installed sibling plugins instead of pruning them. Fixes #76571. (#76602) Thanks @byungskers and @crpol.
- Plugins/context-engine: include the selected `plugins.slots.contextEngine` plugin in the gateway startup load plan so external context-engine plugins without `activation.onStartup` in their manifest are loaded before any agent turn resolves the active engine; prevents the "Context engine X is not registered; falling back to default engine legacy" warning after gateway startup. Fixes #76576. Thanks @hclsys.
- Plugins/tools: restore on-demand registry load for path-based plugins (origin "config") so tool factories registered via `plugins.load.paths` are resolved at agent request time when no pre-warmed channel registry is present; prevents "unknown method" errors after gateway startup. Fixes #76598. Thanks @hclsys.
- Plugins/hooks: include explicitly enabled hook-capable plugins in the Gateway startup runtime scope so embedded PI runs can see their `before_prompt_build` and `agent_end` hooks. Fixes #76649. Thanks @wwf3045 and @MkDev11.
- Plugins/OpenCode: expose Claude thinking profiles through the lightweight provider policy surface so directive and session validation keep `xhigh`, `adaptive`, and `max` for `opencode/claude-opus-4-7` instead of remapping `xhigh` to `high`. Fixes #76648. Thanks @aaajiao.
- Channels/QQ Bot: resolve structured `clientSecret` SecretRefs before QQ token exchange, expose the QQ Bot secret contract to secrets tooling, and reject legacy `secretref:/...` marker strings. (#74772) Thanks @xialonglee.
- Agents: keep active streamed provider replies alive by refreshing guarded fetch timeouts on raw body chunks and surface true prompt stream timeouts as explicit errors instead of partial assistant fragments. Fixes #76307. (#76633) Thanks @MkDev11.
- Plugins/externalization: keep official ACPX, Google Chat, and LINE install specs on production package names, leaving beta-tag probing to the explicit OpenClaw beta update channel. Thanks @vincentkoc.
@@ -41,10 +408,14 @@ Docs: https://docs.openclaw.ai
- Plugins/catalog: pin bare npm specs from prerelease external channel catalog entries to the catalog entry version, so beta catalogs do not silently install the latest stable package.
- Plugins/update: treat catalog-matched official npm updates and OpenClaw-authored externalized-bundled npm bridges as trusted official installs so launch-code plugins can update or migrate out of the bundled tree without scanner false positives. Thanks @vincentkoc.
- Plugins/onboarding: fall back from ClawHub to npm only for missing package/version errors, keeping integrity and verification failures fail-closed during storepack rollout. Thanks @vincentkoc.
- CLI/onboarding: mask credential inputs (model-auth provider API keys, gateway tokens and passwords, web-search provider keys, and skill env-var values) in the interactive `openclaw onboard` wizard so pasted secrets no longer echo into terminal scrollback, `Start-Transcript` logs, or screenshots; existing tokens/passwords are preserved through a masked-preview confirm step before the sensitive prompt. Thanks @anurag-bg-neu.
- Control UI/Talk: fix Talk (OpenAI Realtime WebRTC) CORS failure by stripping server-side-only attribution headers (`originator`, `version`, `User-Agent`) from browser offer headers; `api.openai.com/v1/realtime/calls` only allows `authorization` and `content-type` in its CORS preflight, so forwarding these headers caused the browser SDP exchange to fail. Fixes #76435. Thanks @hclsys.
- CLI/logs: auto-reconnect `openclaw logs --follow` on transient gateway disconnects (WebSocket close, timeout, connection drop) with bounded exponential backoff (up to 8 retries, capped at 30 s) and stderr retry warnings, while still exiting immediately on non-recoverable auth or configuration errors. Fixes #74782. (#75059) Thanks @shashank-poola.
- CLI/logs: announce `--follow` recovery with a `[logs] gateway reconnected` notice once a poll succeeds after a transient outage, and emit JSON `notice` records in `--json` mode for both the retry warning and the reconnect transition, so live monitoring scripts can react to the recovery. Carries forward #75059. (#75372) Thanks @romneyda.
- Chat delivery: make `/verbose on|full|off` changes affect subsequent tool-use chat bubbles again, including channels with draft preview tool progress enabled, while preserving one-shot verbose directives.
- CLI/logs: auto-reconnect `openclaw logs --follow` on transient gateway disconnects with bounded backoff, stderr retry warnings, `[logs] gateway reconnected` recovery notices, and JSON `notice` records while still exiting immediately on non-recoverable auth or configuration errors. Fixes #74782. (#75059, #75372) Thanks @shashank-poola and @romneyda.
- Codex/WhatsApp: keep the `message` dynamic tool available when Codex source replies are configured for message-tool delivery, so coding-profile chat agents do not complete turns privately without a visible channel reply. Fixes #76660. (#76663) Thanks @VishalJ99.
- Codex/heartbeat: send heartbeat-specific initiative guidance through Codex turn-scoped collaboration-mode instructions, keeping ordinary message-tool chat turns in Default mode without heartbeat prompt leakage. Thanks @pashpashpash.
- Plugins/onboarding: trust optional official plugin and web-search installs selected from the official catalog so npm security scanning treats them like other source-linked official install paths. Thanks @vincentkoc.
- Agents/web_search: keep installed runtime provider discovery enabled when web-search metadata is missing, so externally installed official providers such as Brave remain visible to agent and cron turns instead of falling back to bundled-only lookup. Fixes #76626. Thanks @amknight.
- Tests/plugins: expose the Discord npm onboarding Docker lane as a package script and assert planned Docker lanes point at real scripts, so external-channel onboarding coverage can actually run. Thanks @vincentkoc.
- Plugins/ClawHub: explain unreleased ClawHub plugin artifacts as a rollout-state fallback to `npm:` installs instead of leaking raw archive metadata fields. Thanks @vincentkoc.
- Tests/onboarding: assert packaged channel onboarding leaves `openclaw channels status --json` and plain `openclaw status` showing the configured channel, covering the empty Channels table regression path. Thanks @vincentkoc.
@@ -60,15 +431,14 @@ Docs: https://docs.openclaw.ai
- TUI/Control UI: fix `/think` command showing only base thinking levels when the active session uses a different model from the default, so provider-specific levels like DeepSeek V4 Pro's `xhigh` and `max` are now visible and selectable. Fixes #76482. Thanks @amknight.
- CLI/sessions: keep intentional empty agent replies silent after tool-delivered channel output, instead of surfacing a misleading "No reply from agent." fallback. Thanks @vincentkoc.
- Config/doctor: cap `.clobbered.*` forensic snapshots per config path and serialize snapshot writes so repeated `doctor --fix` recovery loops cannot flood the config directory. Fixes #76454; carries forward #65649. Thanks @JUSTICEESSIELP, @rsnow, and @vincentkoc.
- Feishu: suppress duplicate text when replies send native voice media while preserving captions for ordinary audio files and falling back to text plus attachment links when voice uploads fail.
- Feishu: send the skipped reply text when `audioAsVoice` falls back to a generic file attachment after transcode failure, so voice-intent replies do not lose their caption.
- TTS/plugins: activate the configured speech provider plugin during Gateway startup, so Microsoft and Local CLI voice replies work immediately after selecting them instead of staying invisible in the startup plugin set. Fixes #76481. Thanks @amknight.
- TTS/plugins: include speech providers selected through inherited agent, channel, and account TTS personas during Gateway startup, matching the runtime TTS config merge. Carries forward #76481. Thanks @amknight.
- Feishu: suppress duplicate text when replies send native voice media, preserve captions for ordinary audio files, and send fallback text plus attachment links when `audioAsVoice` transcode/upload fallback produces a generic file.
- TTS/plugins: activate configured and inherited speech provider plugins during Gateway startup, so Microsoft and Local CLI voice replies work immediately after persona selection instead of staying invisible in the startup plugin set. Fixes #76481. Thanks @amknight.
- Feishu: keep packaged Feishu startup from bundling the Lark SDK's ESM `__dirname` path by loading the SDK as a plugin-local runtime dependency. Fixes #76291 and #76494. (#76392) Thanks @zqchris.
- Plugins/npm: build package-local runtime dist files for publishable plugins and stop listing root-package-excluded plugin sidecars in the core package metadata, so npm plugin installs such as `@openclaw/diffs` and `@openclaw/discord` no longer publish source-only runtime payloads. Fixes #76426. Thanks @PrinceOfEgypt.
- Channels/secrets: resolve SecretRef-backed channel credentials through external plugin secret contracts after the plugin split, covering runtime startup, target discovery, webhook auth, disabled-account enumeration, and late-bound web_search config. Fixes #76371. (#76449) Thanks @joshavant and @neeravmakwana.
- Docker/Gateway: pass Docker setup `.env` values into gateway and CLI containers and preserve exec SecretRef `passEnv` keys in managed service plans, so 1Password Connect-backed Discord tokens keep resolving after doctor or plugin repair. Thanks @vincentkoc.
- Control UI/WebChat: explain compaction boundaries in chat history and link directly to session checkpoint controls so pre-compaction turns no longer look silently lost after refresh. Fixes #76415. Thanks @BunsDev.
- Agents/compaction: add an optional bundled compaction notifier hook and retry once from the compacted transcript when automatic compaction leaves a turn without a final visible reply. (#76651) Thanks @simplyclever914.
- Agents/incomplete-turn: detect and surface a warning when the agent's final text after a tool-call chain is silently dropped because the post-tool assistant response was never produced, instead of completing the turn with only the pre-tool analysis text. Fixes #76477. Thanks @amknight.
- Channels/WhatsApp: attach native outbound mention metadata for group text and media captions by resolving `@+<digits>` and `@<digits>` tokens against WhatsApp participant data, including LID groups. Fixes #39879; carries forward #56863. Thanks @kengi1437, @joe2643, and @fridayck.
- Channels/WhatsApp: require outbound mention tokens to end at a word boundary so phone-number prefixes inside longer strings no longer trigger hidden native mentions.
@@ -76,19 +446,17 @@ Docs: https://docs.openclaw.ai
- Plugins/install: resolve bare official external plugin IDs such as `brave` through the official catalog when no bundled source is available, so packaged installs fetch the intended scoped npm package instead of an unrelated unscoped package. Fixes #76373. Thanks @bek91 and @vincentkoc.
- Plugins/install: require OpenClaw-owned install provenance before granting official npm plugin scanner trust, so direct npm package names no longer bypass launch-code scanning while catalog, onboarding, and doctor installs stay trusted. Thanks @fede-kamel and @vincentkoc.
- Network proxy: preserve target TLS hostname validation for Node HTTPS requests routed through the managed HTTP proxy, so Discord-style CONNECT traffic no longer validates certificates against the local proxy host. Fixes #74809. (#76442) Thanks @jesse-merhi and @abnershang.
- Gateway/sessions: keep async `sessions.list` title and preview hydration bounded to transcript head/tail reads so Control UI polling cannot full-scan large session transcripts every refresh. Thanks @vincentkoc.
- Gateway/sessions: keep `sessions.list` rows lightweight by bounding title/preview hydration to transcript head/tail reads and caching manifest model-id normalization plus setup fallback metadata against the active plugin snapshot. Thanks @vincentkoc and @rolandrscheel.
- Gateway/performance: cache per-run verbose-level session reads, skip a redundant `lsof` scan in `gateway --force` when no listener was killed, and make the Gateway startup benchmark print usage for `--help`.
- Gateway/sessions: keep agent runtime metadata on lightweight `sessions.list` rows so model-only session patches do not make Control UI lose runtime identity. Thanks @vincentkoc.
- Gateway/sessions: keep bulk `sessions.list` rows lightweight by skipping per-row transcript usage fallback, display model inference, and plugin projection, avoiding event-loop stalls in large session stores. Thanks @Marvinthebored and @vincentkoc.
- Gateway/models: keep read-only `models.list` fallbacks on persisted/current metadata and configured rows while using static auth checks, so missing `models.json` files no longer runtime-load provider discovery or stall gateway after restart. Fixes #76382; refs #76360 and #75707. Thanks @trojy13, @RayWoo, @AnathemaOfficial, and @vincentkoc.
- Gateway/models: keep agent image attachment capability checks on the full catalog while preserving the read-only `models.list` path, so image sends are not rejected after static catalog fallback.
- Gateway/sessions: keep agent runtime metadata on lightweight `sessions.list` rows and skip per-row transcript usage fallback, display model inference, and plugin projection, avoiding identity loss and event-loop stalls in large session stores. Thanks @Marvinthebored and @vincentkoc.
- Gateway/models: keep read-only `models.list` fallbacks on persisted/current metadata, configured rows, registry-compatible fallbacks, and static auth checks while preserving full-catalog image attachment capability checks. Fixes #76382; refs #76360 and #75707. Thanks @trojy13, @RayWoo, @AnathemaOfficial, @Marvinthebored, and @vincentkoc.
- CLI/plugins: reject missing plugin ids before config writes in `plugins enable` and `plugins disable` so a typo no longer persists a stale config entry. (#73554) Thanks @ai-hpc.
- Agents/sessions: preserve delivered trailing assistant replies during session-file repair so Telegram/WebChat history is not rewritten to drop already-delivered responses. Fixes #76329. Thanks @obviyus.
- Gateway/chat history: preserve oversized transcript turns as explicit omitted-message placeholders while avoiding large JSONL parse stalls. Thanks @Marvinthebored and @vincentkoc.
- Gateway/models: keep read-only model-list responses on registry-compatible fallbacks and metadata defaults, so empty or minimal persisted model files do not hide built-ins or custom model capabilities. Thanks @Marvinthebored.
- CLI/doctor: load the configured memory-slot plugin when resolving memory diagnostics so bundled `memory-core` no longer triggers a false “no active memory plugin” warning on standalone `doctor` / `status` runs. Fixes #76367. Thanks @neeravmakwana.
- Gateway: preserve stack diagnostics when `chat.send` or agent attachment parsing/staging fails, improving image-send failure triage. Refs #63432. (#75135) Thanks @keen0206.
- Heartbeats/Codex: stop sending the legacy `HEARTBEAT_OK` prompt instruction when heartbeat turns have the structured `heartbeat_respond` tool, while keeping the text sentinel for legacy automatic heartbeat replies. Thanks @pashpashpash.
- Agents/idle-timeout: add a cost-runaway breaker to the outer embedded-run retry loop that halts further attempts after 5 consecutive idle timeouts without completed model progress, so a wedged provider can no longer fan paid model calls out across the same run; completed text or tool-call progress resets the breaker, but partial tool-argument token dribbles do not. Fixes #76293. Thanks @ThePuma312.
- Heartbeats/Codex: align structured heartbeat prompts with actual `heartbeat_respond` tool availability, stop sending legacy `HEARTBEAT_OK` when the tool exists, and keep tool-disabled commitment check-ins on the legacy ack path. Thanks @pashpashpash and @vincentkoc.
- Agent runtimes: fail explicit plugin runtime selections honestly when the requested harness is unavailable instead of silently falling back to the embedded PI runtime. Thanks @pashpashpash.
- Maintainer workflow: push prepared PR heads through GitHub's verified commit API by default and require an explicit override before git-protocol pushes can publish unsigned commits. Thanks @BunsDev.
- Feishu: resolve setup/status probes through the selected/default account so multi-account configs with account-scoped app credentials show as configured and probeable. Fixes #72930. Thanks @brokemac79.
@@ -103,8 +471,7 @@ Docs: https://docs.openclaw.ai
- Cron: preserve manual `cron.run` IDs in `cron.runs` history so manual run acknowledgements can be correlated with finished run records. Fixes #76276.
- CLI/devices: request `operator.admin` for `openclaw devices approve <requestId>` only when the exact pending device request would mint or inherit admin-scoped operator access, while keeping lower-scope approvals on the pairing scope.
- Memory/embedding: broaden the embedding reindex retry classifier to include transient socket-layer errors (`fetch failed`, `ECONNRESET`, `socket hang up`, `UND_ERR_*`, `closed`) so memory reindex survives provider network hiccups instead of aborting mid-run. Related #56815, #44166. (#76311) Thanks @buyitsydney.
- Memory/sessions: keep rotated and deleted session transcripts (`.jsonl.reset.<iso>` / `.jsonl.deleted.<iso>`) searchable end-to-end by indexing their real content in `buildSessionEntry` instead of short-circuiting to empty entries, and by mapping archive hit paths back to their live transcript stem during `memory_search` visibility filtering so hits are no longer dropped at the guard. `.jsonl.bak.<iso>` backups and compaction checkpoints remain opaque. Refs #56131. Thanks @buyitsydney.
- Memory/sessions: emit a `sessionTranscriptUpdate` event when `archiveFileOnDisk` rotates a live session transcript into `.jsonl.reset.<iso>` / `.jsonl.deleted.<iso>` / `.jsonl.bak.<iso>`, and bypass the delta-bytes / delta-messages threshold gate in `processSessionDeltaBatch` for usage-counted archive paths (`.jsonl.reset.<iso>` and `.jsonl.deleted.<iso>`). Without the bypass the archive event was forwarded to the listener but dropped at the threshold check, because an archive is a one-shot file-rename mutation rather than an incremental append and would typically land below the default `deltaBytes: 100000` / `deltaMessages: 50` reindex thresholds. Archives now feed the memory sync incremental path the same way `appendMessage` / compaction / tool-result rewrite / chat inject / command execution events already do. Refs #56131. Thanks @buyitsydney.
- Memory/sessions: keep rotated and deleted transcripts (`.jsonl.reset.<iso>` / `.jsonl.deleted.<iso>`) searchable by indexing archive content, mapping archive hits back to live transcript stems, emitting transcript update events on archive rotation, and bypassing incremental delta thresholds for one-shot archive mutations while keeping backups and compaction checkpoints opaque. Refs #56131. Thanks @buyitsydney.
- Memory/search: keep sqlite-vec optional in packaged installs and point missing-extension recovery at the valid `agents.defaults.memorySearch.store.vector.extensionPath` setting. Thanks @willemsej and @vincentkoc.
- Gateway: keep directly requested plugin tools invokable under restrictive tool profiles while preserving explicit deny lists and the HTTP safety deny list, preventing catalog/invoke mismatches that surface as "Tool not available". Thanks @BunsDev.
- Gateway/update: allow beta binaries to refresh gateway services when the config was last written by the matching stable release version, avoiding false newer-config downgrade blocks during beta channel updates.
@@ -119,12 +486,14 @@ Docs: https://docs.openclaw.ai
- Active Memory: preserve the target agent context when building embedded recall plugin tools so `memory_search` and `memory_get` stay available for explicit recall sessions. Fixes #76343. Thanks @Countermarch.
- Plugins/externalization: repair missing configured plugin installs from npm by default, reserve ClawHub downloads for explicit `clawhubSpec` metadata, and cover agent-runtime/env-selected plugin repair. Thanks @vincentkoc.
- Plugins/install: allow official catalog-matched npm channel plugins such as Feishu to pass the trusted install scanner path while keeping spoofed package names blocked. Thanks @vincentkoc.
- Tools/llm-task: keep JSON-only embedded model runs from tripping inherited tool allowlists when tools are intentionally disabled, while preserving runtime `toolsAllow` failures. Fixes #74019. Thanks @amknight.
- Tools/profiles: make `tools.profile: "full"` grant all tools including optional plugin tools such as browser, so the full profile no longer silently drops plugin-provided tools that require an explicit allowlist entry. Fixes #76507. Thanks @amknight.
- Feishu: keep timeout env parsing separate from the HTTP client wrapper so package security scans no longer report a false env-harvesting hit during install. Thanks @vincentkoc.
- Upgrade/config: validate configured web-search providers and statically suppressed model/provider pairs against the active plugin set at config load, so stale plugin state fails loud before runtime fallback.
- 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.
@@ -132,6 +501,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.
@@ -145,6 +515,9 @@ Docs: https://docs.openclaw.ai
- Gateway/CLI: make `openclaw gateway start` repair stale managed service definitions that point at old OpenClaw versions, missing binaries, or temporary installer paths before starting.
- Heartbeat/scheduler: make heartbeat phase scheduling active-hours-aware so the scheduler seeks forward to the first in-window phase slot instead of arming timers for quiet-hours slots and relying solely on the runtime guard. Non-UTC `activeHours.timezone` values (e.g. `Asia/Shanghai`) now correctly influence when the next heartbeat timer fires, avoiding wasted quiet-hours ticks and long dormant gaps after gateway restarts. Fixes #75487. Thanks @amknight.
- Providers/Arcee AI: mark Trinity Large Thinking as tool-incompatible so main-session runs use the same text-only request shape that made subagent runs recover, avoiding the remaining main-session response-shape mismatch after the #62848 transport failover fix. Fixes #62851 and #62847; carries forward #62848. Thanks @Adam-Researchh.
- Plugins/SDK: harden run-scoped plugin context cleanup so finalized workflow runs do not leak per-run state. Thanks @100yenadmin.
- Plugins/SDK: keep stale async registry cleanup from clearing restored plugin run context and scheduler state after a plugin registry is reactivated. (#75600) Thanks @100yenadmin.
- Plugins/SDK: preserve restored plugin scheduler state when earlier delayed replacement cleanup finishes after reactivation. Thanks @100yenadmin.
- Status: show the `openai-codex` OAuth profile for `openai/gpt-*` sessions running through the native Codex runtime instead of reporting auth as unknown. (#76197) Thanks @mbelinky.
- Gateway: avoid repeated plugin tool descriptor config hashing so large runtime configs do not block reply startup and trigger reconnect/timeouts. (#75944) Thanks @joshavant.
- Plugins/externalization: keep diagnostics ClawHub packages and persisted bundled-plugin relocation on npm-first install metadata for launch, and omit Discord from the core package now that its external package is published. Thanks @vincentkoc.
@@ -158,9 +531,16 @@ Docs: https://docs.openclaw.ai
- Control UI/chat: remove the delete-confirm popover outside-click listener on every dismiss path, so Cancel, Delete, outside clicks, and same-button toggles no longer leave stale document listeners behind. Refs #75590 and #69982. Thanks @Ricardo-M-L.
- Memory-core: treat exhausted file watcher limits as non-fatal for builtin memory auto-sync while preserving fatal handling for unrelated disk-full errors. (#73357) Thanks @solodmd.
- Providers/Ollama: restore catalog context-window forwarding as `num_ctx` for native `/api/chat` requests; fixes tool selection and context truncation regressions on models with catalog entries (qwen3, llama3, gemma3, …) when no explicit `params.num_ctx` was configured. Fixes #76117. (#76181) Thanks @openperf.
- Plugins/install: pin npm plugin installs to the verified resolved version and reject package-lock version or integrity drift, so mutable tags cannot race integrity checks into accepting a different artifact. Thanks @Lucenx9.
- Plugins/providers: preserve scoped cold-load fallback for enabled external manifest-contract capability providers missing from the startup registry, so providers such as Fish Audio can resolve on request without requiring `activation.onStartup` for correctness. (#76536) Thanks @Conan-Scott.
- Gateway/update: carry `continuationMessage` from `update.run` into successful restart sentinels so session-scoped self-updates can resume one follow-up turn after the Gateway restarts. Refs #71178. (#74362) Thanks @100menotu001, @HeilbronAILabs, and @artnking.
- Agents/fallback: suppress duplicate current-turn user-message transcript writes after embedded fallback retries while still sending the retry prompt to the model. (#63696) Thanks @dashhuang.
- Channels/Telegram: force a fresh final message when a visible non-preview bubble (tool/block/error) was delivered after the active answer preview, so multi-step assistant replies no longer end up with the final answer above intermediate output. Fixes #76529. Thanks @jack-stormentswe.
- Channels/Telegram: require an observed Telegram send, edit, or fallback before treating a forum-topic final as delivered, so final replies generated in transcript no longer disappear from Telegram topics. Fixes #76554. (#76764) Thanks @bubucilo and @obviyus.
- Plugins/update: keep externalized bundled npm bridge updates on the normal plugin security scanner path instead of granting source-linked official trust without artifact provenance. (#76765) Thanks @Lucenx9.
- Agents/reply context: label replied-to messages as the current user message target in model-visible metadata, so short replies are grounded to their explicit reply target instead of nearby chat history. (#76817) Thanks @obviyus.
- Doctor/plugins: install configured missing official plugins such as Discord and Brave during doctor/update repair, auto-enable repaired provider plugins, preserve config when a download fails, and stop auto-enable from inventing plugin entries when no manifest declares a configured channel. Fixes #76872. Thanks @jack-stormentswe.
## 2026.5.2
@@ -215,6 +595,12 @@ Docs: https://docs.openclaw.ai
- Control UI: allow deployments to configure grouped chat message max-width with a validated `gateway.controlUi.chatMessageMaxWidth` setting instead of patching bundled CSS after upgrades. Fixes #67935. Thanks @xiew4589-lang.
- Control UI/Cron: ignore malformed persisted cron rows without valid payloads before they enter UI state and guard stale cron render paths, preventing blank Control UI sections after a bad cron snapshot. Fixes #55047 and #54439; supersedes #54550 and #54552.
- Control UI/sessions: bound the default Sessions tab query to recent activity and fewer rows, avoiding expensive full-history loads while keeping filters editable. Fixes #76050. (#76051) Thanks @Neomail2.
- Control UI/sessions: apply reliable `sessions.changed` snapshots in-place and refetch only for partial events, avoiding redundant `sessions.list` regeneration during active session updates.
- Control UI/sessions: explain the Sessions filter controls with hover tooltips and raise the default list limit to 200 rows.
- Control UI/sessions: expand compaction checkpoint details from checkpoint-bearing rows and keep token totals on one line.
- Control UI/sessions: group Active and Limit filters together, streamline source toggles, and make the filter section collapsible.
- Control UI/sessions: shorten filter tooltips and remove duplicate browser-native tooltip popovers.
- Control UI/sessions: keep the expanded filter controls on one row on large screens.
- Gateway/channels: cap startup fanout at four channel/account handoffs and recover from Bonjour ciao self-probe races, reducing Windows startup stalls with many Telegram accounts. Fixes #75687.
- Gateway/sessions: keep `sessions.list` polling responsive on large session stores by reusing list-safe session cache/indexes and returning a lightweight compaction checkpoint preview instead of heavyweight summaries. Thanks @rolandrscheel.
- Control UI/Gateway: keep long-running dashboard WebSocket sessions alive with protocol pings and keep Stop available after reconnect or reload by recovering session-scoped active-run abort state. Fixes #70991. Thanks @alexandre-leng.
@@ -250,6 +636,7 @@ Docs: https://docs.openclaw.ai
- QA Lab: stop gateway children when the suite parent disappears, so interrupted local QA runs cannot leave hot orphaned gateways behind.
- Codex/app-server: tolerate a second connection close during startup recovery and include retry counts plus stringified errors in the restart warning, so concurrent lanes do not fail after one shared-client race.
- Plugins/CLI: cache plugin CLI registration entries per command program so completion state generation does not repeat the full plugin sweep in one invocation. Thanks @ScientificProgrammer.
- Voice Call: summarize restored-call verification logs during startup while preserving expired-call cleanup, reducing duplicate per-call skip messages. Thanks @jckm14.
- Plugins: reuse gateway-bindable plugin loader cache entries for later default-mode loads without serving default-built registries to gateway-bound requests, reducing repeated plugin registration during dispatch. Refs #61756. Thanks @DmitryPogodaev.
- Gateway/secrets: include the caught error message in `secrets.reload` and `secrets.resolve` warning logs while keeping RPC errors generic, so operators can diagnose reload and permission failures. Thanks @davidangularme.
- Providers/OpenRouter: fill DeepSeek V4 `reasoning_content` replay placeholders for `openrouter/deepseek/deepseek-v4-flash` and `openrouter/deepseek/deepseek-v4-pro`, so thinking/tool follow-up turns do not fail with DeepSeek's replay-shape error. Fixes #76018. Thanks @cloph-dsp.
@@ -462,7 +849,16 @@ Docs: https://docs.openclaw.ai
- Providers/Google: keep Gemini thinking-signature-only stream chunks active during reasoning, so Gemini 3.1 Pro Preview replies no longer hit idle timeouts before visible text. Fixes #76071. (#76080) Thanks @marcoschierhorn and @zhangguiping-xydt.
- CLI/skills: show per-agent model and command visibility in `openclaw skills check --agent`, and let doctor report or disable unavailable skills allowed for the default agent. (#75983) Thanks @mbelinky.
## 2026.4.30
## 2026.4.29
### Highlights
- Messaging and automation get active-run steering by default, visible-reply enforcement, spawned subagent routing metadata, and opt-in follow-up commitments for heartbeat-delivered reminders. Thanks @vincentkoc, @scoootscooob, @samzong, and @vignesh07.
- Memory grows into a people-aware wiki with provenance views, per-conversation Active Memory filters, partial recall on timeout, and bounded REM preview diagnostics. Thanks @vincentkoc, @quengh, @joeykrug, and @samzong.
- Provider/model coverage expands with NVIDIA onboarding/catalogs plus faster manifest-backed model/auth paths, Bedrock Opus 4.7 thinking parity, and safer Codex/OpenAI-compatible replay and streaming behavior. Thanks @eleqtrizit, @shakkernerd, @prasad-yashdeep, @woodhouse-bot, and @LyHug.
- Gateway and packaged-plugin reliability focuses on slow-host startup, reusable model catalogs, event-loop readiness diagnostics, runtime-dependency repair, stale-session recovery, and version-scoped update caches. Thanks @lpendeavors, @DerFlash, @vincentkoc, @pashpashpash, and @jhsmith409.
- Channel fixes cluster around Slack Block Kit limits, Telegram proxy/webhook/polling/send resilience, Discord startup/rate-limit handling, WhatsApp delivery/liveness, and Microsoft Teams/Matrix/Feishu edge cases. Thanks @slackapi, @SymbolStar, @djgeorg3, @TinyTb, @dseravalli, @nklock, and @alex-xuweilong.
- Security and operations add OpenGrep scanning, sharper GHSA triage policy, safer exec/pairing/owner-scope handling, Docker/onboarding automation, and web-fetch IPv6 ULA opt-in for trusted proxy stacks. Thanks @jesse-merhi, @pgondhi987, @mmaps, @jinjimz, and @jeffrey701.
### Changes
@@ -484,6 +880,31 @@ Docs: https://docs.openclaw.ai
- Agents/Codex: default Codex-harness direct source replies to the OpenClaw `message` tool when visible reply delivery is not explicitly configured, keeping channel-visible output as a deliberate tool call. (#75765) Thanks @pashpashpash.
- Heartbeats/agents: add a structured `heartbeat_respond` tool for tool-capable heartbeat runs so agents can record quiet outcomes or explicit notification text without relying only on `HEARTBEAT_OK` parsing. (#75765) Thanks @pashpashpash.
- Gateway/config: allow `$include` directives to read files from operator-approved `OPENCLAW_INCLUDE_ROOTS` directories while preserving default config-directory confinement. Thanks @ificator.
- Security/tools: configured tool sections (`tools.exec`, `tools.fs`) no longer implicitly widen restrictive profiles (`messaging`, `minimal`). Users who need those tools under a restricted profile must add explicit `alsoAllow` entries; a startup warning identifies affected configs. Fixes #47487. Thanks @amknight.
- Gateway/SDK: add SDK-facing artifact list/get/download RPCs and App SDK helpers with transcript provenance and download-source guardrails. Refs #74706. Thanks @tmimmanuel.
- Agents/commitments: add opt-in inferred follow-up commitments with hidden batched extraction, per-agent/per-channel scoping, heartbeat delivery, CLI management, a simple `commitments.enabled`/`commitments.maxPerDay` config, and heartbeat-interval due-time clamping so magical check-ins do not echo immediately. (#74189) Thanks @vignesh07.
- Messages/queue: make `steer` drain all pending Pi steering messages at the next model boundary, keep legacy one-at-a-time steering as `queue`, and add a dedicated steering queue docs page. Thanks @vincentkoc.
- Messages/queue: default active-run queueing to `steer` with a 500ms followup fallback debounce, and document the queue modes, precedence, and drop policies on the command queue page. Thanks @vincentkoc.
- Messages: add global `messages.visibleReplies` so operators can require visible output to go through `message(action=send)` for any source chat, while `messages.groupChat.visibleReplies` stays available as the group/channel override. Thanks @scoootscooob.
- Gateway/events: surface `spawnedBy` on subagent chat and agent broadcast payloads so clients can route child session events without an extra session lookup. (#63244) Thanks @samzong.
- Memory/wiki: add agent-facing people wiki metadata, canonical aliases, person cards, relationship graphs, privacy/provenance reports, evidence-kind drilldown, and search modes for person lookup, question routing, source evidence, and raw claims. Thanks @vincentkoc.
- Active Memory: add optional per-conversation `allowedChatIds` and `deniedChatIds` filters so operators can enable recall only for selected direct, group, or channel conversations while keeping broad sessions skipped. (#67977) Thanks @quengh.
- Active Memory: return bounded partial recall summaries when the hidden memory sub-agent times out, including the default temporary-transcript path, so useful recovered context is not discarded. (#73219) Thanks @joeykrug.
- Gateway/memory: add a read-only `doctor.memory.remHarness` RPC so operator clients can preview bounded REM dreaming output without running mutation paths. (#66673) Thanks @samzong.
- Providers/NVIDIA: add the NVIDIA provider with API-key onboarding, setup docs, static catalog metadata, and literal model-ref picker support so NVIDIA hosted models can be selected with their provider prefix intact. (#71204) Thanks @eleqtrizit.
- Models: suppress explicitly configured openai-codex/gpt-5.4-mini inline entries so a stale models config written by `openclaw doctor --fix` cannot bypass the manifest capability block and cause repeated assistant-turn failures when the runtime switches to that model on ChatGPT-backed Codex accounts. Conditional suppressions (e.g. qwen Coding Plan endpoint guards) remain bypassable by explicit user configuration. (#74451) Thanks @0xCyda, @hclsys, and @Marvae.
- Added SQLite-backed plugin state store (`api.runtime.state.openKeyedStore`) for restart-safe keyed registries with TTL, eviction, and automatic plugin isolation. Thanks @amknight.
- Plugin SDK: mark remaining legacy alias exports and diffs tool/config aliases with deprecation metadata, and add a guard so future legacy alias comments require `@deprecated` tags. Thanks @vincentkoc.
- CLI/QR/dependencies: internalize small terminal progress and QR wrapper helpers while keeping the real QR encoder dependency direct, reducing the default runtime dependency graph without changing QR output behavior. Thanks @vincentkoc.
- Dependencies: refresh workspace runtime, plugin, and tooling packages, including ACP, Pi, AWS SDK, TypeBox, pnpm, oxlint, oxfmt, jsdom, pdfjs, ciao, and tokenjuice, while keeping patched ACP behavior and lint gates current. Thanks @mariozechner.
- Gateway/dev: run `pnpm gateway:watch` through a named tmux session by default, with `gateway:watch:raw` and `OPENCLAW_GATEWAY_WATCH_TMUX=0` for foreground mode, so repeated starts respawn an inspectable watcher without trapping the invoking agent shell. Thanks @vincentkoc.
- Gateway/diagnostics: emit an opt-in startup diagnostics timeline that records gateway lifecycle and plugin-load phases behind a config flag, so slow-start diagnosis no longer requires bespoke instrumentation. Thanks @shakkernerd.
- Control UI/i18n: extend the locale registry with new Persian (fa), Dutch (nl), Vietnamese (vi), Italian (it), Arabic (ar), and Thai (th) entries and ship `fa`, `nl`, `vi`, and `zh-TW` docs glossaries, so the docs translation pipeline and the Control UI language picker stay aligned across surfaces. Thanks @vincentkoc.
- Channels: add Yuanbao channel docs entrance so the Tencent Yuanbao bot appears in the channel listing and sidebar navigation. (#73443) Thanks @loongfay.
- Channels/Yuanbao: update plugin GitHub location to YuanbaoTeam/yuanbao-openclaw-plugin and add "yuanbao" alias to channel catalog. (#74253) Thanks @loongfay.
- Docker setup: add `OPENCLAW_SKIP_ONBOARDING` so automated Docker installs can skip the interactive onboarding step while still applying gateway defaults. (#55518) Thanks @jinjimz.
- Security policy: classify media/base64 decode and format-conversion overhead after configured acceptance limits as performance-only for GHSA triage unless a report demonstrates a limit bypass, crash, exhaustion, data exposure, or another boundary bypass. (#74311)
- Security/OpenGrep: add a precise OpenGrep rulepack, source-rule compiler, provenance metadata check, and PR/full scan workflows that validate first-party code and rulepack-only changes while uploading SARIF to GitHub Code Scanning. (#69483) Thanks @jesse-merhi.
### Fixes
@@ -636,48 +1057,6 @@ Docs: https://docs.openclaw.ai
- Mattermost: refresh current native slash command registrations before accepting callbacks so stale tokens from deleted or regenerated commands stop being accepted without a gateway restart while failed validations stay briefly cached and lookup starts are rate-limited per command, gate each callback against the resolved command's own startup token so a token leaked for one slash command cannot poison another command's failure cache, redact slash validation lookup errors, and add a body read timeout to the multi-account routing path so slow callback senders cannot tie up the dispatcher. Thanks @feynman-hou and @eleqtrizit.
- Security/dotenv: block `COMSPEC` in workspace `.env` so a malicious repo cannot redirect Windows `cmd.exe` resolution, and lock in case-insensitive workspace-`.env` regression coverage for the full Windows shell trust-root family (`COMSPEC`, `PROGRAMFILES`, `PROGRAMW6432`, `SYSTEMROOT`, `WINDIR`). (#74460) Thanks @mmaps.
- Gateway/install: drop stale version-manager and package-manager PATH entries preserved from old service files during `gateway install --force` and doctor repair, so the repair path no longer recreates `gateway-path-nonminimal` warnings. Fixes #75220. (#75440) Thanks @leonaIee, @renaudcerrato, and @aaajiao.
## 2026.4.29
### Highlights
- Messaging and automation get active-run steering by default, visible-reply enforcement, spawned subagent routing metadata, and opt-in follow-up commitments for heartbeat-delivered reminders. Thanks @vincentkoc, @scoootscooob, @samzong, and @vignesh07.
- Memory grows into a people-aware wiki with provenance views, per-conversation Active Memory filters, partial recall on timeout, and bounded REM preview diagnostics. Thanks @vincentkoc, @quengh, @joeykrug, and @samzong.
- Provider/model coverage expands with NVIDIA onboarding/catalogs plus faster manifest-backed model/auth paths, Bedrock Opus 4.7 thinking parity, and safer Codex/OpenAI-compatible replay and streaming behavior. Thanks @eleqtrizit, @shakkernerd, @prasad-yashdeep, @woodhouse-bot, and @LyHug.
- Gateway and packaged-plugin reliability focuses on slow-host startup, reusable model catalogs, event-loop readiness diagnostics, runtime-dependency repair, stale-session recovery, and version-scoped update caches. Thanks @lpendeavors, @DerFlash, @vincentkoc, @pashpashpash, and @jhsmith409.
- Channel fixes cluster around Slack Block Kit limits, Telegram proxy/webhook/polling/send resilience, Discord startup/rate-limit handling, WhatsApp delivery/liveness, and Microsoft Teams/Matrix/Feishu edge cases. Thanks @slackapi, @SymbolStar, @djgeorg3, @TinyTb, @dseravalli, @nklock, and @alex-xuweilong.
- Security and operations add OpenGrep scanning, sharper GHSA triage policy, safer exec/pairing/owner-scope handling, Docker/onboarding automation, and web-fetch IPv6 ULA opt-in for trusted proxy stacks. Thanks @jesse-merhi, @pgondhi987, @mmaps, @jinjimz, and @jeffrey701.
### Changes
- Security/tools: configured tool sections (`tools.exec`, `tools.fs`) no longer implicitly widen restrictive profiles (`messaging`, `minimal`). Users who need those tools under a restricted profile must add explicit `alsoAllow` entries; a startup warning identifies affected configs. Fixes #47487. Thanks @amknight.
- Gateway/SDK: add SDK-facing artifact list/get/download RPCs and App SDK helpers with transcript provenance and download-source guardrails. Refs #74706. Thanks @tmimmanuel.
- Agents/commitments: add opt-in inferred follow-up commitments with hidden batched extraction, per-agent/per-channel scoping, heartbeat delivery, CLI management, a simple `commitments.enabled`/`commitments.maxPerDay` config, and heartbeat-interval due-time clamping so magical check-ins do not echo immediately. (#74189) Thanks @vignesh07.
- Messages/queue: make `steer` drain all pending Pi steering messages at the next model boundary, keep legacy one-at-a-time steering as `queue`, and add a dedicated steering queue docs page. Thanks @vincentkoc.
- Messages/queue: default active-run queueing to `steer` with a 500ms followup fallback debounce, and document the queue modes, precedence, and drop policies on the command queue page. Thanks @vincentkoc.
- Messages: add global `messages.visibleReplies` so operators can require visible output to go through `message(action=send)` for any source chat, while `messages.groupChat.visibleReplies` stays available as the group/channel override. Thanks @scoootscooob.
- Gateway/events: surface `spawnedBy` on subagent chat and agent broadcast payloads so clients can route child session events without an extra session lookup. (#63244) Thanks @samzong.
- Memory/wiki: add agent-facing people wiki metadata, canonical aliases, person cards, relationship graphs, privacy/provenance reports, evidence-kind drilldown, and search modes for person lookup, question routing, source evidence, and raw claims. Thanks @vincentkoc.
- Active Memory: add optional per-conversation `allowedChatIds` and `deniedChatIds` filters so operators can enable recall only for selected direct, group, or channel conversations while keeping broad sessions skipped. (#67977) Thanks @quengh.
- Active Memory: return bounded partial recall summaries when the hidden memory sub-agent times out, including the default temporary-transcript path, so useful recovered context is not discarded. (#73219) Thanks @joeykrug.
- Gateway/memory: add a read-only `doctor.memory.remHarness` RPC so operator clients can preview bounded REM dreaming output without running mutation paths. (#66673) Thanks @samzong.
- Providers/NVIDIA: add the NVIDIA provider with API-key onboarding, setup docs, static catalog metadata, and literal model-ref picker support so NVIDIA hosted models can be selected with their provider prefix intact. (#71204) Thanks @eleqtrizit.
- Models: suppress explicitly configured openai-codex/gpt-5.4-mini inline entries so a stale models config written by `openclaw doctor --fix` cannot bypass the manifest capability block and cause repeated assistant-turn failures when the runtime switches to that model on ChatGPT-backed Codex accounts. Conditional suppressions (e.g. qwen Coding Plan endpoint guards) remain bypassable by explicit user configuration. (#74451) Thanks @0xCyda, @hclsys, and @Marvae.
- Added SQLite-backed plugin state store (`api.runtime.state.openKeyedStore`) for restart-safe keyed registries with TTL, eviction, and automatic plugin isolation. Thanks @amknight.
- Plugin SDK: mark remaining legacy alias exports and diffs tool/config aliases with deprecation metadata, and add a guard so future legacy alias comments require `@deprecated` tags. Thanks @vincentkoc.
- CLI/QR/dependencies: internalize small terminal progress and QR wrapper helpers while keeping the real QR encoder dependency direct, reducing the default runtime dependency graph without changing QR output behavior. Thanks @vincentkoc.
- Dependencies: refresh workspace runtime, plugin, and tooling packages, including ACP, Pi, AWS SDK, TypeBox, pnpm, oxlint, oxfmt, jsdom, pdfjs, ciao, and tokenjuice, while keeping patched ACP behavior and lint gates current. Thanks @mariozechner.
- Gateway/dev: run `pnpm gateway:watch` through a named tmux session by default, with `gateway:watch:raw` and `OPENCLAW_GATEWAY_WATCH_TMUX=0` for foreground mode, so repeated starts respawn an inspectable watcher without trapping the invoking agent shell. Thanks @vincentkoc.
- Gateway/diagnostics: emit an opt-in startup diagnostics timeline that records gateway lifecycle and plugin-load phases behind a config flag, so slow-start diagnosis no longer requires bespoke instrumentation. Thanks @shakkernerd.
- Control UI/i18n: extend the locale registry with new Persian (fa), Dutch (nl), Vietnamese (vi), Italian (it), Arabic (ar), and Thai (th) entries and ship `fa`, `nl`, `vi`, and `zh-TW` docs glossaries, so the docs translation pipeline and the Control UI language picker stay aligned across surfaces. Thanks @vincentkoc.
- Channels: add Yuanbao channel docs entrance so the Tencent Yuanbao bot appears in the channel listing and sidebar navigation. (#73443) Thanks @loongfay.
- Channels/Yuanbao: update plugin GitHub location to YuanbaoTeam/yuanbao-openclaw-plugin and add "yuanbao" alias to channel catalog. (#74253) Thanks @loongfay.
- Docker setup: add `OPENCLAW_SKIP_ONBOARDING` so automated Docker installs can skip the interactive onboarding step while still applying gateway defaults. (#55518) Thanks @jinjimz.
- Security policy: classify media/base64 decode and format-conversion overhead after configured acceptance limits as performance-only for GHSA triage unless a report demonstrates a limit bypass, crash, exhaustion, data exposure, or another boundary bypass. (#74311)
- Security/OpenGrep: add a precise OpenGrep rulepack, source-rule compiler, provenance metadata check, and PR/full scan workflows that validate first-party code and rulepack-only changes while uploading SARIF to GitHub Code Scanning. (#69483) Thanks @jesse-merhi.
### Fixes
- Voice Call: resolve SecretRef-backed Twilio auth tokens and realtime/streaming provider API keys before initializing call providers, so SecretRef-backed voice-call credentials reach runtime as strings. (#73632) Thanks @VACInc.
- Security/outbound: strip re-formed HTML tags during plain-text sanitization so nested tag fragments cannot leave a CodeQL-detected `<script>` sequence behind. Thanks @vincentkoc.
- Security/secrets: compare credential bytes with padded timing-safe buffers instead of hashing candidate passwords before equality checks. Thanks @vincentkoc.

View File

@@ -93,9 +93,9 @@ Welcome to the lobster tank! 🦞
## PR Limits
We cap at **10 open PRs per author**. If you exceed this, the `r: too-many-prs` label is added and your PR is auto-closed. This is a hard limit.
We cap at **20 open PRs per author**. If you exceed this, the `r: too-many-prs` label is added and your PR is auto-closed. This is a hard limit.
For coordinated change sets that genuinely need more than 10 PRs, join the **#clawtributors** channel in Discord and talk to maintainers first.
For coordinated change sets that genuinely need more than 20 PRs, join the **#clawtributors** channel in Discord and talk to maintainers first.
## Before You PR

View File

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

View File

@@ -1,4 +1,4 @@
parent_config: ../../.swiftlint.yml
parent_config: ../../config/swiftlint.yml
included:
- Sources

View File

@@ -1,5 +1,11 @@
# OpenClaw iOS Changelog
## 2026.5.4 - 2026-05-04
Maintenance update for the current OpenClaw development release.
- Gateway pairing now supports scanning QR codes from Settings and accepts full copied setup-code messages while keeping non-loopback `ws://` setup links blocked.
## 2026.5.3 - 2026-05-03
Maintenance update for the current OpenClaw development release.

View File

@@ -2,8 +2,8 @@
// Source of truth: apps/ios/version.json
// Generated by scripts/ios-sync-versioning.ts.
OPENCLAW_IOS_VERSION = 2026.5.3
OPENCLAW_MARKETING_VERSION = 2026.5.3
OPENCLAW_IOS_VERSION = 2026.5.4
OPENCLAW_MARKETING_VERSION = 2026.5.4
OPENCLAW_BUILD_VERSION = 1
#include? "../build/Version.xcconfig"

View File

@@ -241,7 +241,7 @@ gateway can only send pushes for iOS devices that paired with that gateway.
## What Works Now (Concrete)
- Pairing via setup code flow (`/pair` then `/pair approve` in Telegram).
- Pairing via QR or setup code flow (`/pair qr` or `/pair`, then `/pair approve` in Telegram).
- Gateway connection via discovery or manual host/port with TLS fingerprint trust prompt.
- Chat + Talk surfaces through the operator gateway session.
- iPhone node commands in foreground: camera snap/clip, canvas present/navigate/eval/snapshot, screen record, location, contacts, calendar, reminders, photos, motion, local notifications.

View File

@@ -1,42 +0,0 @@
import Foundation
struct GatewaySetupPayload: Codable {
var url: String?
var host: String?
var port: Int?
var tls: Bool?
var bootstrapToken: String?
var token: String?
var password: String?
}
enum GatewaySetupCode {
static func decode(raw: String) -> GatewaySetupPayload? {
if let payload = decodeFromJSON(raw) {
return payload
}
if let decoded = decodeBase64Payload(raw),
let payload = decodeFromJSON(decoded)
{
return payload
}
return nil
}
private static func decodeFromJSON(_ json: String) -> GatewaySetupPayload? {
guard let data = json.data(using: .utf8) else { return nil }
return try? JSONDecoder().decode(GatewaySetupPayload.self, from: data)
}
private static func decodeBase64Payload(_ raw: String) -> String? {
let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines)
guard !trimmed.isEmpty else { return nil }
let normalized = trimmed
.replacingOccurrences(of: "-", with: "+")
.replacingOccurrences(of: "_", with: "/")
let padding = normalized.count % 4
let padded = padding == 0 ? normalized : normalized + String(repeating: "=", count: 4 - padding)
guard let data = Data(base64Encoded: padded) else { return nil }
return String(data: data, encoding: .utf8)
}
}

View File

@@ -248,38 +248,23 @@ private struct ManualEntryStep: View {
return
}
guard let payload = GatewaySetupCode.decode(raw: raw) else {
self.setupStatusText = "Setup code not recognized."
guard let link = GatewayConnectDeepLink.fromSetupInput(raw) else {
self.setupStatusText = "Setup code not recognized or uses an insecure ws:// gateway URL."
return
}
if let urlString = payload.url, let url = URL(string: urlString) {
self.applyURL(url)
} else if let host = payload.host, !host.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
self.manualHost = host.trimmingCharacters(in: .whitespacesAndNewlines)
if let port = payload.port {
self.manualPortText = String(port)
} else {
self.manualPortText = ""
}
if let tls = payload.tls {
self.manualUseTLS = tls
}
} else if let url = URL(string: raw), url.scheme != nil {
self.applyURL(url)
} else {
self.setupStatusText = "Setup code missing URL or host."
return
}
self.manualHost = link.host
self.manualPortText = String(link.port)
self.manualUseTLS = link.tls
if let token = payload.token, !token.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
if let token = link.token, !token.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
self.manualToken = token.trimmingCharacters(in: .whitespacesAndNewlines)
} else if payload.bootstrapToken?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false {
} else if link.bootstrapToken?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false {
self.manualToken = ""
}
if let password = payload.password, !password.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
if let password = link.password, !password.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
self.manualPassword = password.trimmingCharacters(in: .whitespacesAndNewlines)
} else if payload.bootstrapToken?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false {
} else if link.bootstrapToken?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false {
self.manualPassword = ""
}
@@ -287,30 +272,12 @@ private struct ManualEntryStep: View {
.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
if !trimmedInstanceId.isEmpty {
let trimmedBootstrapToken =
payload.bootstrapToken?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
link.bootstrapToken?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
GatewaySettingsStore.saveGatewayBootstrapToken(trimmedBootstrapToken, instanceId: trimmedInstanceId)
}
self.setupStatusText = "Setup code applied."
}
private func applyURL(_ url: URL) {
guard let host = url.host, !host.isEmpty else { return }
self.manualHost = host
if let port = url.port {
self.manualPortText = String(port)
} else {
self.manualPortText = ""
}
let scheme = (url.scheme ?? "").lowercased()
if scheme == "wss" || scheme == "https" {
self.manualUseTLS = true
} else if scheme == "ws" || scheme == "http" {
self.manualUseTLS = false
}
}
// (GatewaySetupCode) decode raw setup codes.
}
@MainActor

View File

@@ -203,14 +203,7 @@ struct OnboardingWizardView: View {
return
}
if let message = self.detectQRCode(from: data) {
if let link = GatewayConnectDeepLink.fromSetupCode(message) {
self.handleScannedLink(link)
return
}
if let url = URL(string: message),
let route = DeepLinkParser.parse(url),
case let .gateway(link) = route
{
if let link = GatewayConnectDeepLink.fromSetupInput(message) {
self.handleScannedLink(link)
return
}

View File

@@ -65,20 +65,11 @@ struct QRScannerView: UIViewControllerRepresentable {
let payload = barcode.payloadStringValue
else { continue }
// Try setup code format first (base64url JSON from /pair qr).
if let link = GatewayConnectDeepLink.fromSetupCode(payload) {
if let link = GatewayConnectDeepLink.fromSetupInput(payload) {
self.handled = true
self.parent.onGatewayLink(link)
return
}
// Fall back to deep link URL format (openclaw://gateway?...).
if let url = URL(string: payload),
let route = DeepLinkParser.parse(url),
case let .gateway(link) = route
{
self.handled = true
self.parent.onGatewayLink(link)
Task { @MainActor in
self.parent.onGatewayLink(link)
}
return
}
}

View File

@@ -49,6 +49,8 @@ struct SettingsTab: View {
@State private var defaultShareInstruction: String = ""
@AppStorage("gateway.setupCode") private var setupCode: String = ""
@State private var setupStatusText: String?
@State private var showQRScanner: Bool = false
@State private var scannerError: String?
@State private var manualGatewayPortText: String = ""
@State private var gatewayExpanded: Bool = true
@State private var selectedAgentPickerId: String = ""
@@ -98,6 +100,13 @@ struct SettingsTab: View {
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
Button {
self.openGatewayQRScanner()
} label: {
Label("Scan QR Code", systemImage: "qrcode.viewfinder")
}
.disabled(self.connectingGatewayID != nil)
Button {
Task { await self.applySetupCodeAndConnect() }
} label: {
@@ -430,6 +439,30 @@ struct SettingsTab: View {
})
}
}
.sheet(isPresented: self.$showQRScanner) {
NavigationStack {
QRScannerView(
onGatewayLink: { link in
self.handleScannedGatewayLink(link)
},
onError: { error in
self.showQRScanner = false
self.setupStatusText = "Scanner error: \(error)"
self.scannerError = error
},
onDismiss: {
self.showQRScanner = false
})
.ignoresSafeArea()
.navigationTitle("Scan QR Code")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .topBarLeading) {
Button("Cancel") { self.showQRScanner = false }
}
}
}
}
.alert("Reset Onboarding?", isPresented: self.$showResetOnboardingAlert) {
Button("Reset", role: .destructive) {
self.resetOnboarding()
@@ -446,6 +479,14 @@ struct SettingsTab: View {
message: Text(help.message),
dismissButton: .default(Text("OK")))
}
.alert("QR Scanner Unavailable", isPresented: Binding(
get: { self.scannerError != nil },
set: { if !$0 { self.scannerError = nil } }))
{
Button("OK", role: .cancel) {}
} message: {
Text(self.scannerError ?? "")
}
.onAppear {
self.lastLocationModeRaw = self.locationEnabledModeRaw
self.syncManualPortText()
@@ -769,39 +810,28 @@ struct SettingsTab: View {
return false
}
guard let payload = GatewaySetupCode.decode(raw: raw) else {
self.setupStatusText = "Setup code not recognized."
guard let link = GatewayConnectDeepLink.fromSetupInput(raw) else {
self.setupStatusText = "Setup code not recognized or uses an insecure ws:// gateway URL."
return false
}
if let urlString = payload.url, let url = URL(string: urlString) {
self.applySetupURL(url)
} else if let host = payload.host, !host.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
self.manualGatewayHost = host.trimmingCharacters(in: .whitespacesAndNewlines)
if let port = payload.port {
self.manualGatewayPort = port
self.manualGatewayPortText = String(port)
} else {
self.manualGatewayPort = 0
self.manualGatewayPortText = ""
}
if let tls = payload.tls {
self.manualGatewayTLS = tls
}
} else if let url = URL(string: raw), url.scheme != nil {
self.applySetupURL(url)
} else {
self.setupStatusText = "Setup code missing URL or host."
return false
}
self.applyGatewayLink(link)
return true
}
private func applyGatewayLink(_ link: GatewayConnectDeepLink) {
self.manualGatewayHost = link.host
self.manualGatewayPort = link.port
self.manualGatewayPortText = String(link.port)
self.manualGatewayTLS = link.tls
let trimmedInstanceId = self.instanceId.trimmingCharacters(in: .whitespacesAndNewlines)
let trimmedBootstrapToken =
payload.bootstrapToken?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
link.bootstrapToken?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
if !trimmedInstanceId.isEmpty {
GatewaySettingsStore.saveGatewayBootstrapToken(trimmedBootstrapToken, instanceId: trimmedInstanceId)
}
if let token = payload.token, !token.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
if let token = link.token, !token.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
let trimmedToken = token.trimmingCharacters(in: .whitespacesAndNewlines)
self.gatewayToken = trimmedToken
if !trimmedInstanceId.isEmpty {
@@ -813,7 +843,7 @@ struct SettingsTab: View {
GatewaySettingsStore.saveGatewayToken("", instanceId: trimmedInstanceId)
}
}
if let password = payload.password, !password.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
if let password = link.password, !password.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
let trimmedPassword = password.trimmingCharacters(in: .whitespacesAndNewlines)
self.gatewayPassword = trimmedPassword
if !trimmedInstanceId.isEmpty {
@@ -825,26 +855,33 @@ struct SettingsTab: View {
GatewaySettingsStore.saveGatewayPassword("", instanceId: trimmedInstanceId)
}
}
return true
}
private func applySetupURL(_ url: URL) {
guard let host = url.host, !host.isEmpty else { return }
self.manualGatewayHost = host
if let port = url.port {
self.manualGatewayPort = port
self.manualGatewayPortText = String(port)
} else {
self.manualGatewayPort = 0
self.manualGatewayPortText = ""
}
let scheme = (url.scheme ?? "").lowercased()
if scheme == "wss" || scheme == "https" {
self.manualGatewayTLS = true
} else if scheme == "ws" || scheme == "http" {
self.manualGatewayTLS = false
private func openGatewayQRScanner() {
self.appModel.disconnectGateway()
self.connectingGatewayID = nil
self.setupStatusText = "Opening QR scanner…"
self.showQRScanner = true
}
private func handleScannedGatewayLink(_ link: GatewayConnectDeepLink) {
self.showQRScanner = false
self.setupCode = ""
self.applyGatewayLink(link)
self.setupStatusText = "QR loaded. Connecting to \(link.host):\(link.port)"
Task { await self.connectAfterScannedGatewayLink() }
}
private func connectAfterScannedGatewayLink() async {
let host = self.manualGatewayHost.trimmingCharacters(in: .whitespacesAndNewlines)
let resolvedPort = self.resolvedManualPort(host: host)
guard let port = resolvedPort else {
self.setupStatusText = "Failed: invalid port"
return
}
let ok = await self.preflightGateway(host: host, port: port, useTLS: self.manualGatewayTLS)
guard ok else { return }
await self.connectManual()
}
private func resolvedManualPort(host: String) -> Int? {
@@ -892,8 +929,6 @@ struct SettingsTab: View {
queueLabel: "gateway.preflight")
}
// (GatewaySetupCode) decode raw setup codes.
private func connectManual() async {
let host = self.manualGatewayHost.trimmingCharacters(in: .whitespacesAndNewlines)
guard !host.isEmpty else {

View File

@@ -21,7 +21,6 @@ Sources/Gateway/GatewayProblemView.swift
Sources/Gateway/GatewayQuickSetupSheet.swift
Sources/Gateway/GatewayServiceResolver.swift
Sources/Gateway/GatewaySettingsStore.swift
Sources/Gateway/GatewaySetupCode.swift
Sources/Gateway/GatewayTrustPromptAlert.swift
Sources/Gateway/KeychainStore.swift
Sources/Gateway/TCPProbe.swift

View File

@@ -161,4 +161,34 @@ private func agentAction(
token: nil,
password: nil))
}
@Test func parseGatewaySetupInputParsesFullCopiedSetupMessage() {
let payload = #"{"url":"wss://gateway.example.com","bootstrapToken":"tok"}"#
let link = GatewayConnectDeepLink.fromSetupInput("""
Pairing setup code generated.
Setup code:
\(setupCode(from: payload))
""")
#expect(link == .init(
host: "gateway.example.com",
port: 443,
tls: true,
bootstrapToken: "tok",
token: nil,
password: nil))
}
@Test func parseGatewaySetupInputParsesRawGatewayURL() {
let link = GatewayConnectDeepLink.fromSetupInput("wss://gateway.example.com:444")
#expect(link == .init(
host: "gateway.example.com",
port: 444,
tls: true,
bootstrapToken: nil,
token: nil,
password: nil))
}
}

View File

@@ -1 +1,3 @@
Maintenance update for the current OpenClaw development release.
- Gateway pairing now supports scanning QR codes from Settings and accepts full copied setup-code messages while keeping non-loopback `ws://` setup links blocked.

View File

@@ -70,7 +70,7 @@ targets:
echo "error: swiftformat not found (brew install swiftformat)" >&2
exit 1
fi
swiftformat --lint --config "$SRCROOT/../../.swiftformat" \
swiftformat --lint --config "$SRCROOT/../../config/swiftformat" \
--unexclude "$SRCROOT/Sources,$SRCROOT/ShareExtension,$SRCROOT/ActivityWidget,$SRCROOT/WatchExtension,$SRCROOT/../shared/OpenClawKit,$SRCROOT/../swabble" \
--filelist "$SRCROOT/SwiftSources.input.xcfilelist"
- name: SwiftLint

View File

@@ -1,3 +1,3 @@
{
"version": "2026.5.3"
"version": "2026.5.4"
}

View File

@@ -425,6 +425,7 @@ enum HostEnvSecurityPolicy {
"SSL_CERT_DIR",
"SSL_CERT_FILE",
"SUDO_EDITOR",
"SYSTEMROOT",
"TF_CLI_CONFIG_FILE",
"TF_PLUGIN_CACHE_DIR",
"UV_DEFAULT_INDEX",
@@ -435,6 +436,7 @@ enum HostEnvSecurityPolicy {
"VIRTUAL_ENV",
"VISUAL",
"WGETRC",
"WINDIR",
"XDG_CONFIG_DIRS",
"XDG_CONFIG_HOME",
"YARN_RC_FILENAME",

View File

@@ -15,9 +15,9 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>2026.5.3</string>
<string>2026.5.4</string>
<key>CFBundleVersion</key>
<string>2026050300</string>
<string>2026050400</string>
<key>CFBundleIconFile</key>
<string>OpenClaw</string>
<key>CFBundleURLTypes</key>

View File

@@ -103,10 +103,13 @@ struct SessionRow: Identifiable {
}
enum SessionKind {
case direct, group, global, unknown
case cron, direct, group, global, unknown
static func from(key: String) -> SessionKind {
if key == "global" { return .global }
let parts = key.lowercased().split(separator: ":").filter { !$0.isEmpty }
if parts.first == "cron" { return .cron }
if parts.count >= 3, parts[0] == "agent", parts[2] == "cron" { return .cron }
if key.hasPrefix("group:") { return .group }
if key.contains(":group:") { return .group }
if key.contains(":channel:") { return .group }
@@ -116,6 +119,7 @@ enum SessionKind {
var label: String {
switch self {
case .cron: "Cron"
case .direct: "Direct"
case .group: "Group"
case .global: "Global"
@@ -125,6 +129,7 @@ enum SessionKind {
var tint: Color {
switch self {
case .cron: .green
case .direct: .accentColor
case .group: .orange
case .global: .purple

View File

@@ -445,6 +445,12 @@ private func promptAnswer(for step: WizardStep) throws -> Any {
case "text":
let initial = anyCodableString(step.initialvalue)
let prompt = step.placeholder ?? "Value"
if step.sensitive == true {
let sensitivePrompt = initial.isEmpty ? prompt : "\(prompt) (leave blank to keep existing)"
let value = try readSensitiveLineWithPrompt(sensitivePrompt)
let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)
return trimmed.isEmpty ? initial : trimmed
}
let value = try readLineWithPrompt("\(prompt)\(initial.isEmpty ? "" : " [\(initial)]")")
let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)
return trimmed.isEmpty ? initial : trimmed
@@ -524,3 +530,28 @@ private func readLineWithPrompt(_ prompt: String) throws -> String {
}
return line
}
private func readSensitiveLineWithPrompt(_ prompt: String) throws -> String {
print("\(prompt): ", terminator: "")
fflush(stdout)
var original = termios()
guard tcgetattr(STDIN_FILENO, &original) == 0 else {
throw WizardCliError.gatewayError("Could not configure hidden terminal input.")
}
var hidden = original
hidden.c_lflag &= ~tcflag_t(ECHO)
guard tcsetattr(STDIN_FILENO, TCSANOW, &hidden) == 0 else {
throw WizardCliError.gatewayError("Could not configure hidden terminal input.")
}
defer {
_ = tcsetattr(STDIN_FILENO, TCSANOW, &original)
print("")
}
guard let line = readLine() else {
throw WizardCliError.cancelled
}
return line
}

View File

@@ -4323,6 +4323,7 @@ public struct CronRunLogEntry: Codable, Sendable {
public let status: AnyCodable?
public let error: String?
public let summary: String?
public let diagnostics: [String: AnyCodable]?
public let delivered: Bool?
public let deliverystatus: AnyCodable?
public let deliveryerror: String?
@@ -4344,6 +4345,7 @@ public struct CronRunLogEntry: Codable, Sendable {
status: AnyCodable?,
error: String?,
summary: String?,
diagnostics: [String: AnyCodable]?,
delivered: Bool?,
deliverystatus: AnyCodable?,
deliveryerror: String?,
@@ -4364,6 +4366,7 @@ public struct CronRunLogEntry: Codable, Sendable {
self.status = status
self.error = error
self.summary = summary
self.diagnostics = diagnostics
self.delivered = delivered
self.deliverystatus = deliverystatus
self.deliveryerror = deliveryerror
@@ -4386,6 +4389,7 @@ public struct CronRunLogEntry: Codable, Sendable {
case status
case error
case summary
case diagnostics
case delivered
case deliverystatus = "deliveryStatus"
case deliveryerror = "deliveryError"

View File

@@ -5,6 +5,8 @@ import Testing
struct SessionDataTests {
@Test func `session kind from key detects common kinds`() {
#expect(SessionKind.from(key: "global") == .global)
#expect(SessionKind.from(key: "cron:daily") == .cron)
#expect(SessionKind.from(key: "agent:main:cron:daily") == .cron)
#expect(SessionKind.from(key: "discord:group:engineering") == .group)
#expect(SessionKind.from(key: "unknown") == .unknown)
#expect(SessionKind.from(key: "user@example.com") == .direct)

View File

@@ -6,6 +6,16 @@ public enum DeepLinkRoute: Sendable, Equatable {
}
public struct GatewayConnectDeepLink: Codable, Sendable, Equatable {
private struct SetupPayload: Decodable {
let url: String?
let host: String?
let port: Int?
let tls: Bool?
let bootstrapToken: String?
let token: String?
let password: String?
}
public let host: String
public let port: Int
public let tls: Bool
@@ -27,28 +37,118 @@ public struct GatewayConnectDeepLink: Codable, Sendable, Equatable {
return URL(string: "\(scheme)://\(self.host):\(self.port)")
}
/// Parse a device-pair setup code (base64url-encoded JSON: `{url, bootstrapToken?, token?, password?}`).
/// Parse a gateway setup input from the QR/scanner/manual entry surfaces.
///
/// Accepted inputs are:
/// - device-pair setup code (base64url-encoded JSON)
/// - raw setup JSON
/// - a copied message containing a `Setup code:` line
/// - an `openclaw://gateway?...` deep link
/// - a raw `ws://` or `wss://` gateway URL
public static func fromSetupInput(_ input: String) -> GatewayConnectDeepLink? {
let trimmed = input.trimmingCharacters(in: .whitespacesAndNewlines)
guard !trimmed.isEmpty else { return nil }
if let link = fromSetupCode(trimmed) {
return link
}
if let url = URL(string: trimmed),
let route = DeepLinkParser.parse(url),
case let .gateway(link) = route
{
return link
}
return fromGatewayURLString(
trimmed,
bootstrapToken: nil,
token: nil,
password: nil)
}
/// Parse a gateway setup payload from a device-pair setup code or copied setup text.
///
/// Accepted inputs are:
/// - base64url-encoded setup JSON
/// - raw setup JSON
/// - copied text/message content containing one or more extractable setup-code candidates
///
/// Accepted payload shapes are:
/// - `{url, bootstrapToken?, token?, password?}`
/// - `{host, port?, tls?, bootstrapToken?, token?, password?}`
///
/// URL-based payloads provide the gateway WebSocket URL via `url`. Host-based payloads
/// provide `host` plus optional `port` and `tls`. In both cases, the optional
/// `bootstrapToken`, `token`, and `password` fields are also supported.
public static func fromSetupCode(_ code: String) -> GatewayConnectDeepLink? {
guard let data = decodeBase64Url(code) else { return nil }
guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else { return nil }
guard let urlString = json["url"] as? String,
let parsed = URLComponents(string: urlString),
let trimmed = code.trimmingCharacters(in: .whitespacesAndNewlines)
guard !trimmed.isEmpty else { return nil }
if let link = decodeSetupPayload(from: Data(trimmed.utf8)) {
return link
}
if let data = decodeBase64Url(trimmed),
let link = decodeSetupPayload(from: data)
{
return link
}
for candidate in setupCodeCandidates(in: trimmed) where candidate != trimmed {
if let data = decodeBase64Url(candidate),
let link = decodeSetupPayload(from: data)
{
return link
}
}
return nil
}
private static func decodeSetupPayload(from data: Data) -> GatewayConnectDeepLink? {
guard let payload = try? JSONDecoder().decode(SetupPayload.self, from: data) else { return nil }
if let urlString = payload.url?.trimmingCharacters(in: .whitespacesAndNewlines),
!urlString.isEmpty
{
return fromGatewayURLString(
urlString,
bootstrapToken: payload.bootstrapToken,
token: payload.token,
password: payload.password)
}
guard let host = payload.host?.trimmingCharacters(in: .whitespacesAndNewlines),
!host.isEmpty
else {
return nil
}
let tls = payload.tls ?? true
if !tls, !LoopbackHost.isLoopbackHost(host) {
return nil
}
return GatewayConnectDeepLink(
host: host,
port: payload.port ?? (tls ? 443 : 18789),
tls: tls,
bootstrapToken: payload.bootstrapToken,
token: payload.token,
password: payload.password)
}
private static func fromGatewayURLString(
_ urlString: String,
bootstrapToken: String?,
token: String?,
password: String?) -> GatewayConnectDeepLink?
{
guard let parsed = URLComponents(string: urlString),
let hostname = parsed.host, !hostname.isEmpty
else { return nil }
let scheme = (parsed.scheme ?? "ws").lowercased()
guard scheme == "ws" || scheme == "wss" else { return nil }
let tls = scheme == "wss"
guard scheme == "ws" || scheme == "wss" || scheme == "http" || scheme == "https" else {
return nil
}
let tls = scheme == "wss" || scheme == "https"
if !tls, !LoopbackHost.isLoopbackHost(hostname) {
return nil
}
let port = parsed.port ?? (tls ? 443 : 18789)
let bootstrapToken = json["bootstrapToken"] as? String
let token = json["token"] as? String
let password = json["password"] as? String
return GatewayConnectDeepLink(
host: hostname,
port: port,
port: parsed.port ?? (tls ? 443 : 18789),
tls: tls,
bootstrapToken: bootstrapToken,
token: token,
@@ -65,6 +165,19 @@ public struct GatewayConnectDeepLink: Codable, Sendable, Equatable {
}
return Data(base64Encoded: base64)
}
private static func setupCodeCandidates(in input: String) -> [String] {
let surroundingPunctuation = CharacterSet(charactersIn: "`'\"“”‘’()[]{}<>.,;:")
return input
.components(separatedBy: .whitespacesAndNewlines)
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines.union(surroundingPunctuation)) }
.filter { candidate in
guard candidate.count >= 24 else { return false }
return candidate.allSatisfy { ch in
ch.isLetter || ch.isNumber || ch == "-" || ch == "_" || ch == "="
}
}
}
}
public struct AgentDeepLink: Codable, Sendable, Equatable {

View File

@@ -4323,6 +4323,7 @@ public struct CronRunLogEntry: Codable, Sendable {
public let status: AnyCodable?
public let error: String?
public let summary: String?
public let diagnostics: [String: AnyCodable]?
public let delivered: Bool?
public let deliverystatus: AnyCodable?
public let deliveryerror: String?
@@ -4344,6 +4345,7 @@ public struct CronRunLogEntry: Codable, Sendable {
status: AnyCodable?,
error: String?,
summary: String?,
diagnostics: [String: AnyCodable]?,
delivered: Bool?,
deliverystatus: AnyCodable?,
deliveryerror: String?,
@@ -4364,6 +4366,7 @@ public struct CronRunLogEntry: Codable, Sendable {
self.status = status
self.error = error
self.summary = summary
self.diagnostics = diagnostics
self.delivered = delivered
self.deliverystatus = deliverystatus
self.deliveryerror = deliveryerror
@@ -4386,6 +4389,7 @@ public struct CronRunLogEntry: Codable, Sendable {
case status
case error
case summary
case diagnostics
case delivered
case deliverystatus = "deliveryStatus"
case deliveryerror = "deliveryError"

View File

@@ -2,6 +2,14 @@ import Foundation
import OpenClawKit
import Testing
private func setupCode(from payload: String) -> String {
Data(payload.utf8)
.base64EncodedString()
.replacingOccurrences(of: "+", with: "-")
.replacingOccurrences(of: "/", with: "_")
.replacingOccurrences(of: "=", with: "")
}
@Suite struct DeepLinksSecurityTests {
@Test func gatewayDeepLinkRejectsInsecureNonLoopbackWs() {
let url = URL(
@@ -31,33 +39,18 @@ import Testing
@Test func setupCodeRejectsInsecureNonLoopbackWs() {
let payload = #"{"url":"ws://attacker.example:18789","bootstrapToken":"tok"}"#
let encoded = Data(payload.utf8)
.base64EncodedString()
.replacingOccurrences(of: "+", with: "-")
.replacingOccurrences(of: "/", with: "_")
.replacingOccurrences(of: "=", with: "")
#expect(GatewayConnectDeepLink.fromSetupCode(encoded) == nil)
#expect(GatewayConnectDeepLink.fromSetupCode(setupCode(from: payload)) == nil)
}
@Test func setupCodeRejectsInsecurePrefixBypassHost() {
let payload = #"{"url":"ws://127.attacker.example:18789","bootstrapToken":"tok"}"#
let encoded = Data(payload.utf8)
.base64EncodedString()
.replacingOccurrences(of: "+", with: "-")
.replacingOccurrences(of: "/", with: "_")
.replacingOccurrences(of: "=", with: "")
#expect(GatewayConnectDeepLink.fromSetupCode(encoded) == nil)
#expect(GatewayConnectDeepLink.fromSetupCode(setupCode(from: payload)) == nil)
}
@Test func setupCodeAllowsLoopbackWs() {
let payload = #"{"url":"ws://127.0.0.1:18789","bootstrapToken":"tok"}"#
let encoded = Data(payload.utf8)
.base64EncodedString()
.replacingOccurrences(of: "+", with: "-")
.replacingOccurrences(of: "/", with: "_")
.replacingOccurrences(of: "=", with: "")
#expect(
GatewayConnectDeepLink.fromSetupCode(encoded) == .init(
GatewayConnectDeepLink.fromSetupCode(setupCode(from: payload)) == .init(
host: "127.0.0.1",
port: 18789,
tls: false,
@@ -65,4 +58,62 @@ import Testing
token: nil,
password: nil))
}
@Test func setupCodeParsesHostPayload() {
let payload = #"{"host":"gateway.tailnet.ts.net","port":443,"tls":true,"bootstrapToken":"tok"}"#
#expect(
GatewayConnectDeepLink.fromSetupCode(setupCode(from: payload)) == .init(
host: "gateway.tailnet.ts.net",
port: 443,
tls: true,
bootstrapToken: "tok",
token: nil,
password: nil))
}
@Test func setupCodeParsesHostPayloadWithTLSDefaultPort() {
let payload = #"{"host":"gateway.tailnet.ts.net","tls":true,"bootstrapToken":"tok"}"#
#expect(
GatewayConnectDeepLink.fromSetupCode(setupCode(from: payload)) == .init(
host: "gateway.tailnet.ts.net",
port: 443,
tls: true,
bootstrapToken: "tok",
token: nil,
password: nil))
}
@Test func setupCodeRejectsInsecureHostPayload() {
let payload = #"{"host":"gateway.tailnet.ts.net","port":18789,"tls":false,"bootstrapToken":"tok"}"#
#expect(GatewayConnectDeepLink.fromSetupCode(setupCode(from: payload)) == nil)
}
@Test func setupInputParsesFullCopiedSetupMessage() {
let payload = #"{"url":"wss://gateway.tailnet.ts.net","bootstrapToken":"tok"}"#
let message = """
Pairing setup code generated.
Setup code:
\(setupCode(from: payload))
"""
#expect(
GatewayConnectDeepLink.fromSetupInput(message) == .init(
host: "gateway.tailnet.ts.net",
port: 443,
tls: true,
bootstrapToken: "tok",
token: nil,
password: nil))
}
@Test func setupInputParsesRawGatewayURL() {
#expect(
GatewayConnectDeepLink.fromSetupInput("wss://gateway.example.com:444") == .init(
host: "gateway.example.com",
port: 444,
tls: true,
bootstrapToken: nil,
token: nil,
password: nil))
}
}

View File

@@ -48,4 +48,4 @@
--allman false
# Exclusions
--exclude .build,.swiftpm,DerivedData,node_modules,dist,coverage,xcuserdata,Peekaboo,apps/swabble,apps/android,apps/ios,apps/shared,apps/macos/Sources/OpenClawProtocol,apps/macos/Sources/OpenClaw/HostEnvSecurityPolicy.generated.swift
--exclude .build,.swiftpm,DerivedData,node_modules,dist,coverage,xcuserdata,Peekaboo,apps/swabble,apps/android,apps/ios,apps/shared

View File

@@ -1,11 +1,11 @@
# SwiftLint configuration adapted from Peekaboo defaults (Swift 6 friendly)
included:
- apps/macos/Sources
- ../apps/macos/Sources
excluded:
- .build
- DerivedData
- ../.build
- ../DerivedData
- "**/.build"
- "**/.swiftpm"
- "**/DerivedData"
@@ -13,14 +13,14 @@ excluded:
- "**/Resources"
- "**/Package.swift"
- "**/Tests/Resources"
- node_modules
- dist
- coverage
- ../node_modules
- ../dist
- ../coverage
- "*.playground"
# Generated (protocol-gen-swift.ts)
- apps/macos/Sources/OpenClawProtocol/GatewayModels.swift
- ../apps/macos/Sources/OpenClawProtocol/GatewayModels.swift
# Generated (generate-host-env-security-policy-swift.mjs)
- apps/macos/Sources/OpenClaw/HostEnvSecurityPolicy.generated.swift
- ../apps/macos/Sources/OpenClaw/HostEnvSecurityPolicy.generated.swift
analyzer_rules:
- unused_declaration

View File

@@ -0,0 +1,5 @@
{
"extends": "../../tsconfig.json",
"include": ["../../src/**/*", "../../ui/**/*", "../../packages/**/*"],
"exclude": ["../../node_modules", "../../dist", "../../dist-runtime"]
}

View File

@@ -0,0 +1,10 @@
{
"extends": "../../tsconfig.json",
"include": [
"../../src/**/*",
"../../ui/src/**/*",
"../../packages/**/*.ts",
"../../extensions/**/*"
],
"exclude": ["../../node_modules", "../../dist", "../../dist-runtime"]
}

View File

@@ -0,0 +1,11 @@
{
"extends": "../../tsconfig.json",
"include": [
"../../src/**/*",
"../../ui/**/*",
"../../packages/**/*",
"../../extensions/**/*",
"../../scripts/**/*"
],
"exclude": ["../../node_modules", "../../dist", "../../dist-runtime"]
}

View File

@@ -0,0 +1,5 @@
{
"extends": "../../tsconfig.json",
"include": ["../../src/**/*.d.ts", "../../packages/**/*.d.ts", "../../scripts/**/*"],
"exclude": ["../../node_modules", "../../dist", "../../dist-runtime"]
}

View File

@@ -1,12 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SCRIPT_PATH="$ROOT_DIR/scripts/docker/setup.sh"
if [[ ! -f "$SCRIPT_PATH" ]]; then
echo "Docker setup script not found at $SCRIPT_PATH" >&2
exit 1
fi
exec "$SCRIPT_PATH" "$@"

View File

@@ -1,4 +1,4 @@
d9dbaace82aff4445be6ed11e52e69b4548239e3a4e659538f96dfb3ed3c57ac config-baseline.json
9d4d4ab553dadca237d837f71dc7fc13e4ea65d33a564c2ea6775180c413e86a config-baseline.core.json
f2a1aad257c570b497865680c331568a6775369528749826dfa35c1f644483fc config-baseline.channel.json
858f82733d9828b28bf88bc226e155d8417c494215eb3f808f15799daa42a7d7 config-baseline.plugin.json
2c78fb7af01e2ee9e919be5ab7b675347b36cae1e347f97fd2640a6f7c72f3ac config-baseline.json
31ec333df9f8b92c7656ac7107cecd5860dd02e08f7e18c7c674dc47a8811baa config-baseline.core.json
cd7c0c7fb1435bc7e59099e9ac334462d5ad444016e9ab4512aae63a238f78dc config-baseline.channel.json
9832b30a696930a3da7efccf38073137571e1b66cae84e54d747b733fdafcc54 config-baseline.plugin.json

View File

@@ -1,2 +1,2 @@
f829dd720df7c9c8eb9d59eda3b3f879bff278f74b4c00d8d788c1483865b649 plugin-sdk-api-baseline.json
1b3504c8f9ddd00801f095f94f417d469b47370064478eae389d33f4b8e10c76 plugin-sdk-api-baseline.jsonl
a7116e6c0cae4c7b9ee7cd6dc48f2978812f4b5be647f3e36eee91ec9a81d85e plugin-sdk-api-baseline.json
2b6c9883d701379761724e21946d417399c1247e6a244d6b00c4a982c8ef5968 plugin-sdk-api-baseline.jsonl

View File

@@ -15,6 +15,10 @@
"source": "OpenAI provider",
"target": "OpenAI provider"
},
{
"source": "Mantis",
"target": "Mantis"
},
{
"source": "OpenClaw App SDK",
"target": "OpenClaw 应用 SDK"
@@ -79,6 +83,10 @@
"source": "Steering queue",
"target": "Steering queue"
},
{
"source": "Steer",
"target": "Steer"
},
{
"source": "Models",
"target": "Models"
@@ -283,6 +291,42 @@
"source": "Block streaming",
"target": "分块流式传输"
},
{
"source": "Progress drafts",
"target": "进度草稿"
},
{
"source": "Streaming and chunking",
"target": "流式传输和分块"
},
{
"source": "Messages",
"target": "消息"
},
{
"source": "Channel configuration",
"target": "频道配置"
},
{
"source": "Discord",
"target": "Discord"
},
{
"source": "Matrix",
"target": "Matrix"
},
{
"source": "Microsoft Teams",
"target": "Microsoft Teams"
},
{
"source": "Slack",
"target": "Slack"
},
{
"source": "Telegram",
"target": "Telegram"
},
{
"source": "Discovery + transports",
"target": "设备发现 + 传输协议"

View File

@@ -160,12 +160,13 @@ Npm specs are registry-only (package name + optional exact version or dist-tag).
## Bundled hooks
| Hook | Events | What it does |
| --------------------- | ------------------------------ | ----------------------------------------------------- |
| session-memory | `command:new`, `command:reset` | Saves session context to `<workspace>/memory/` |
| bootstrap-extra-files | `agent:bootstrap` | Injects additional bootstrap files from glob patterns |
| command-logger | `command` | Logs all commands to `~/.openclaw/logs/commands.log` |
| boot-md | `gateway:startup` | Runs `BOOT.md` when the gateway starts |
| Hook | Events | What it does |
| --------------------- | ------------------------------------------------- | -------------------------------------------------------------- |
| session-memory | `command:new`, `command:reset` | Saves session context to `<workspace>/memory/` |
| bootstrap-extra-files | `agent:bootstrap` | Injects additional bootstrap files from glob patterns |
| command-logger | `command` | Logs all commands to `~/.openclaw/logs/commands.log` |
| compaction-notifier | `session:compact:before`, `session:compact:after` | Sends visible chat notices when session compaction starts/ends |
| boot-md | `gateway:startup` | Runs `BOOT.md` when the gateway starts |
Enable any bundled hook:
@@ -206,6 +207,12 @@ Paths resolve relative to workspace. Only recognized bootstrap basenames are loa
Logs every slash command to `~/.openclaw/logs/commands.log`.
<a id="compaction-notifier"></a>
### compaction-notifier details
Sends short status messages into the current conversation when OpenClaw starts and finishes compacting the session transcript. This makes long turns less confusing on chat surfaces because the user can see that the assistant is summarizing context and will continue after compaction.
<a id="boot-md"></a>
### boot-md details

View File

@@ -267,15 +267,7 @@ With the BlueBubbles Private API enabled, inbound messages arrive with short mes
bluebubbles: {
groups: {
"iMessage;+;chat-family": {
systemPrompt: [
"When replying in this group, always call action=reply with the",
"[[reply_to:N]] messageId from context so your response threads",
"under the triggering message. Never send a new unlinked message.",
"",
"For short acknowledgements ('ok', 'got it', 'on it'), use",
"action=react with an appropriate tapback emoji (❤️, 👍, 😂, ‼️, ❓)",
"instead of sending a text reply.",
].join(" "),
systemPrompt: "When replying in this group, always call action=reply with the [[reply_to:N]] messageId from context so your response threads under the triggering message. Never send a new unlinked message. For short acknowledgements ('ok', 'got it', 'on it'), use action=react with an appropriate tapback emoji (❤️, 👍, 😂, ‼️, ❓) instead of sending a text reply.",
},
},
},

View File

@@ -258,15 +258,17 @@ In group `120363403215116621@g.us` with agents `["alfred", "baerbel"]`:
{
"agents": {
"reviewer": {
"tools": { "allow": ["read", "exec"] } // Read-only
"tools": { "allow": ["read", "exec"] }
},
"fixer": {
"tools": { "allow": ["read", "write", "edit", "exec"] } // Read-write
"tools": { "allow": ["read", "write", "edit", "exec"] }
}
}
}
```
`reviewer` is read-only. `fixer` can read and write.
</Accordion>
<Accordion title="4. Monitor performance">
With many agents, consider:

View File

@@ -252,6 +252,8 @@ Once DMs are working, you can set up your Discord server as a full workspace whe
In guild channels, normal assistant final replies stay private by default. Visible Discord output must be sent explicitly with the `message` tool, so the agent can lurk by default and only post when it decides a channel reply is useful.
This means the selected model must reliably call tools. If Discord shows typing and the logs show token usage but no posted message, check the session log for assistant text with `didSendViaMessagingTool: false`. That means the model produced a private final answer instead of calling `message(action=send)`. Switch to a stronger tool-calling model, or use the config below to restore legacy automatic final replies.
<Tabs>
<Tab title="Ask your agent">
> "Allow my agent to respond on this server without having to be @mentioned"
@@ -622,7 +624,7 @@ Use `bindings[].match.roles` to route Discord guild members to different agents
- `commands.native` defaults to `"auto"` and is enabled for Discord.
- Per-channel override: `channels.discord.commands.native`.
- `commands.native=false` explicitly clears previously registered Discord native commands.
- `commands.native=false` skips Discord slash-command registration and cleanup during startup. Previously registered commands may remain visible in Discord until you remove them from the Discord app.
- Native command auth uses the same Discord allowlists/policies as normal message handling.
- Commands may still be visible in Discord UI for users who are not authorized; execution still enforces OpenClaw auth and returns "not authorized".
@@ -660,7 +662,7 @@ Default slash command settings:
</Accordion>
<Accordion title="Live stream preview">
OpenClaw can stream draft replies by sending a temporary message and editing it as text arrives. `channels.discord.streaming` takes `off` (default) | `partial` | `block` | `progress`. `progress` maps to `partial` on Discord; `streamMode` is a legacy alias and is auto-migrated.
OpenClaw can stream draft replies by sending a temporary message and editing it as text arrives. `channels.discord.streaming` takes `off` (default) | `partial` | `block` | `progress`. `progress` keeps one editable status draft and updates it with tool progress until final delivery; `streamMode` is a legacy alias and is auto-migrated.
Default stays `off` because Discord preview edits hit rate limits quickly when multiple bots or gateways share an account.
@@ -683,6 +685,25 @@ Default slash command settings:
- `block` emits draft-sized chunks (use `draftChunk` to tune size and breakpoints, clamped to `textChunkLimit`).
- Media, error, and explicit-reply finals cancel pending preview edits.
- `streaming.preview.toolProgress` (default `true`) controls whether tool/progress updates reuse the preview message.
- `streaming.preview.commandText` / `streaming.progress.commandText` controls command/exec detail in compact progress lines: `raw` (default) or `status` (tool label only).
Hide raw command/exec text while keeping compact progress lines:
```json
{
"channels": {
"discord": {
"streaming": {
"mode": "progress",
"progress": {
"toolProgress": true,
"commandText": "status"
}
}
}
}
}
```
Preview streaming is text-only; media replies fall back to normal delivery. When `block` streaming is explicitly enabled, OpenClaw skips the preview stream to avoid double-streaming.
@@ -1320,6 +1341,29 @@ openclaw logs --follow
If you set `channels.discord.allowBots=true`, use strict mention and allowlist rules to avoid loop behavior.
Prefer `channels.discord.allowBots="mentions"` to only accept bot messages that mention the bot.
```json5
{
channels: {
discord: {
accounts: {
mantis: {
// Mantis listens to other bots only when they mention her.
allowBots: "mentions",
},
molty: {
// Molty listens to all bot-authored Discord messages.
allowBots: true,
mentionAliases: {
// Lets Molty write "@Mantis" and send a real Discord mention.
Mantis: "MANTIS_DISCORD_USER_ID",
},
},
},
},
},
}
```
</Accordion>
<Accordion title="Voice STT drops with DecryptionFailed(...)">

View File

@@ -273,13 +273,13 @@ Feishu/Lark supports streaming replies via interactive cards. When enabled, the
channels: {
feishu: {
streaming: true, // enable streaming card output (default: true)
blockStreaming: true, // enable block-level streaming (default: true)
blockStreaming: true, // opt into completed-block streaming
},
},
}
```
Set `streaming: false` to send the complete reply in one message.
Set `streaming: false` to send the complete reply in one message. `blockStreaming` is off by default; enable it only when you want completed assistant blocks flushed before the final reply.
### Quota optimization
@@ -428,7 +428,7 @@ Full configuration: [Gateway configuration](/gateway/configuration)
| `channels.feishu.textChunkLimit` | Message chunk size | `2000` |
| `channels.feishu.mediaMaxMb` | Media size limit | `30` |
| `channels.feishu.streaming` | Streaming card output | `true` |
| `channels.feishu.blockStreaming` | Block-level streaming | `true` |
| `channels.feishu.blockStreaming` | Completed-block reply streaming | `false` |
| `channels.feishu.typingIndicator` | Send typing reactions | `true` |
| `channels.feishu.resolveSenderNames` | Resolve sender display names | `true` |

View File

@@ -192,7 +192,7 @@ Use these identifiers for delivery and allowlists:
groupPolicy: "allowlist",
groups: {
"spaces/AAAA": {
allow: true,
enabled: true,
requireMention: true,
users: ["users/1234567890"],
systemPrompt: "Short answers only.",

View File

@@ -44,6 +44,14 @@ For group/channel rooms, OpenClaw defaults to `messages.groupChat.visibleReplies
`openclaw doctor --fix` writes this default into configured-channel configs that omit it.
That means the agent still processes the turn and can update memory/session state, but its normal final answer is not automatically posted back into the room. To speak visibly, the agent uses `message(action=send)`.
This default depends on a model/runtime that reliably calls tools. If logs show
assistant text but `didSendViaMessagingTool: false`, the model answered
privately instead of calling the message tool. That is not a
Discord/Slack/Telegram send failure. Use a tool-call-reliable model for
group/channel sessions, or set
`messages.groupChat.visibleReplies: "automatic"` to restore legacy visible
final replies.
If the message tool is unavailable under the active tool policy, OpenClaw falls
back to automatic visible replies instead of silently suppressing the response.
`openclaw doctor` warns about this mismatch.

View File

@@ -39,6 +39,7 @@ openclaw gateway run
## Security defaults
- IRC uses raw TCP/TLS sockets outside OpenClaw operator-managed forward proxy routing. In deployments that require all egress through that forward proxy, set `channels.irc.enabled=false` unless direct IRC egress is explicitly approved.
- `channels.irc.dmPolicy` defaults to `"pairing"`.
- `channels.irc.groupPolicy` defaults to `"allowlist"`.
- With `groupPolicy="allowlist"`, set `channels.irc.groups` to define allowed channels.

View File

@@ -113,7 +113,7 @@ If you use the `device-pair` plugin, you can do first-time device pairing entire
1. In Telegram, message your bot: `/pair`
2. The bot replies with two messages: an instruction message and a separate **setup code** message (easy to copy/paste in Telegram).
3. On your phone, open the OpenClaw iOS app → Settings → Gateway.
4. Paste the setup code and connect.
4. Scan the QR code or paste the setup code and connect.
5. Back in Telegram: `/pair pending` (review request IDs, role, and scopes), then approve.
The setup code is a base64-encoded JSON payload that contains:
@@ -134,6 +134,13 @@ That bootstrap token carries the built-in pairing bootstrap profile:
Treat the setup code like a password while it is valid.
For Tailscale, public, or other non-loopback mobile pairing, use Tailscale
Serve/Funnel or another `wss://` Gateway URL. Direct non-loopback `ws://` setup
URLs are rejected before QR/setup-code issuance. Plaintext `ws://` setup codes
are limited to loopback URLs; private-network `ws://` clients still require the explicit
`OPENCLAW_ALLOW_INSECURE_PRIVATE_WS=1` break-glass described in the remote
Gateway guide.
### Approve a node device
```bash
@@ -142,6 +149,13 @@ openclaw devices approve <requestId>
openclaw devices reject <requestId>
```
When an explicit approval is denied because the approving paired-device session
was opened with pairing-only scope, the CLI retries the same request with
`operator.admin`. This lets an existing admin-capable paired device recover a new
Control UI/browser pairing without editing `devices/paired.json` by hand. The
Gateway still validates the retried connection; tokens that cannot authenticate
with `operator.admin` remain blocked.
If the same device retries with different auth details (for example different
role/scopes/public key), the previous pending request is superseded and a new
`requestId` is created.

View File

@@ -209,7 +209,7 @@ STT and TTS support two-level configuration with priority fallback:
voice: "your-voice",
},
accounts: {
qq-main: {
"qq-main": {
tts: {
providers: {
openai: { voice: "shimmer" },

View File

@@ -251,7 +251,19 @@ For **HTTP Request URLs mode**, replace `settings` with the HTTP variant and add
"event_subscriptions": {
"request_url": "https://gateway-host.example.com/slack/events",
"bot_events": [
/* same as Socket Mode */
"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": {
@@ -283,116 +295,123 @@ The default manifest enables the Slack App Home **Home** tab and subscribes to `
<Tab title="Socket Mode (default)">
```json
"slash_commands": [
{
"command": "/new",
"description": "Start a new session",
"usage_hint": "[model]"
},
{
"command": "/reset",
"description": "Reset the current session"
},
{
"command": "/compact",
"description": "Compact the session context",
"usage_hint": "[instructions]"
},
{
"command": "/stop",
"description": "Stop the current run"
},
{
"command": "/session",
"description": "Manage thread-binding expiry",
"usage_hint": "idle <duration|off> or max-age <duration|off>"
},
{
"command": "/think",
"description": "Set the thinking level",
"usage_hint": "<level>"
},
{
"command": "/verbose",
"description": "Toggle verbose output",
"usage_hint": "on|off|full"
},
{
"command": "/fast",
"description": "Show or set fast mode",
"usage_hint": "[status|on|off]"
},
{
"command": "/reasoning",
"description": "Toggle reasoning visibility",
"usage_hint": "[on|off|stream]"
},
{
"command": "/elevated",
"description": "Toggle elevated mode",
"usage_hint": "[on|off|ask|full]"
},
{
"command": "/exec",
"description": "Show or set exec defaults",
"usage_hint": "host=<auto|sandbox|gateway|node> security=<deny|allowlist|full> ask=<off|on-miss|always> node=<id>"
},
{
"command": "/model",
"description": "Show or set the model",
"usage_hint": "[name|#|status]"
},
{
"command": "/models",
"description": "List providers/models",
"usage_hint": "[provider] [page] [limit=<n>|size=<n>|all]"
},
{
"command": "/help",
"description": "Show the short help summary"
},
{
"command": "/commands",
"description": "Show the generated command catalog"
},
{
"command": "/tools",
"description": "Show what the current agent can use right now",
"usage_hint": "[compact|verbose]"
},
{
"command": "/agentstatus",
"description": "Show runtime status, including provider usage/quota when available"
},
{
"command": "/tasks",
"description": "List active/recent background tasks for the current session"
},
{
"command": "/context",
"description": "Explain how context is assembled",
"usage_hint": "[list|detail|json]"
},
{
"command": "/whoami",
"description": "Show your sender identity"
},
{
"command": "/skill",
"description": "Run a skill by name",
"usage_hint": "<name> [input]"
},
{
"command": "/btw",
"description": "Ask a side question without changing session context",
"usage_hint": "<question>"
},
{
"command": "/usage",
"description": "Control the usage footer or show cost summary",
"usage_hint": "off|tokens|full|cost"
}
]
{
"slash_commands": [
{
"command": "/new",
"description": "Start a new session",
"usage_hint": "[model]"
},
{
"command": "/reset",
"description": "Reset the current session"
},
{
"command": "/compact",
"description": "Compact the session context",
"usage_hint": "[instructions]"
},
{
"command": "/stop",
"description": "Stop the current run"
},
{
"command": "/session",
"description": "Manage thread-binding expiry",
"usage_hint": "idle <duration|off> or max-age <duration|off>"
},
{
"command": "/think",
"description": "Set the thinking level",
"usage_hint": "<level>"
},
{
"command": "/verbose",
"description": "Toggle verbose output",
"usage_hint": "on|off|full"
},
{
"command": "/fast",
"description": "Show or set fast mode",
"usage_hint": "[status|on|off]"
},
{
"command": "/reasoning",
"description": "Toggle reasoning visibility",
"usage_hint": "[on|off|stream]"
},
{
"command": "/elevated",
"description": "Toggle elevated mode",
"usage_hint": "[on|off|ask|full]"
},
{
"command": "/exec",
"description": "Show or set exec defaults",
"usage_hint": "host=<auto|sandbox|gateway|node> security=<deny|allowlist|full> ask=<off|on-miss|always> node=<id>"
},
{
"command": "/model",
"description": "Show or set the model",
"usage_hint": "[name|#|status]"
},
{
"command": "/models",
"description": "List providers/models",
"usage_hint": "[provider] [page] [limit=<n>|size=<n>|all]"
},
{
"command": "/help",
"description": "Show the short help summary"
},
{
"command": "/commands",
"description": "Show the generated command catalog"
},
{
"command": "/tools",
"description": "Show what the current agent can use right now",
"usage_hint": "[compact|verbose]"
},
{
"command": "/agentstatus",
"description": "Show runtime status, including provider usage/quota when available"
},
{
"command": "/tasks",
"description": "List active/recent background tasks for the current session"
},
{
"command": "/context",
"description": "Explain how context is assembled",
"usage_hint": "[list|detail|json]"
},
{
"command": "/whoami",
"description": "Show your sender identity"
},
{
"command": "/skill",
"description": "Run a skill by name",
"usage_hint": "<name> [input]"
},
{
"command": "/btw",
"description": "Ask a side question without changing session context",
"usage_hint": "<question>"
},
{
"command": "/side",
"description": "Ask a side question without changing session context",
"usage_hint": "<question>"
},
{
"command": "/usage",
"description": "Control the usage footer or show cost summary",
"usage_hint": "off|tokens|full|cost"
}
]
}
```
</Tab>
@@ -400,22 +419,25 @@ The default manifest enables the Slack App Home **Home** tab and subscribes to `
Use the same `slash_commands` list as Socket Mode above, and add `"url": "https://gateway-host.example.com/slack/events"` to every entry. Example:
```json
"slash_commands": [
{
"command": "/new",
"description": "Start a new session",
"usage_hint": "[model]",
"url": "https://gateway-host.example.com/slack/events"
},
{
"command": "/help",
"description": "Show the short help summary",
"url": "https://gateway-host.example.com/slack/events"
}
// ...repeat for every command with the same `url` value
]
{
"slash_commands": [
{
"command": "/new",
"description": "Start a new session",
"usage_hint": "[model]",
"url": "https://gateway-host.example.com/slack/events"
},
{
"command": "/help",
"description": "Show the short help summary",
"url": "https://gateway-host.example.com/slack/events"
}
]
}
```
Repeat that `url` value on every command in the list.
</Tab>
</Tabs>
@@ -644,6 +666,25 @@ Notes:
- `block`: append chunked preview updates.
- `progress`: show progress status text while generating, then send final text.
- `streaming.preview.toolProgress`: when draft preview is active, route tool/progress updates into the same edited preview message (default: `true`). Set `false` to keep separate tool/progress messages.
- `streaming.preview.commandText` / `streaming.progress.commandText`: set to `status` to keep compact tool-progress lines while hiding raw command/exec text (default: `raw`).
Hide raw command/exec text while keeping compact progress lines:
```json
{
"channels": {
"slack": {
"streaming": {
"mode": "progress",
"progress": {
"toolProgress": true,
"commandText": "status"
}
}
}
}
}
```
`channels.slack.streaming.nativeTransport` controls Slack native text streaming when `channels.slack.streaming.mode` is `partial` (default: `true`).

View File

@@ -278,11 +278,12 @@ curl "https://api.telegram.org/bot<bot_token>/getUpdates"
Requirement:
- `channels.telegram.streaming` is `off | partial | block | progress` (default: `partial`)
- `progress` maps to `partial` on Telegram (compat with cross-channel naming)
- `progress` keeps one editable status draft and updates it with tool progress until final delivery
- `streaming.preview.toolProgress` controls whether tool/progress updates reuse the same edited preview message (default: `true` when preview streaming is active)
- `streaming.preview.commandText` controls command/exec detail inside those tool-progress lines: `raw` (default, preserves released behavior) or `status` (tool label only)
- legacy `channels.telegram.streamMode` and boolean `streaming` values are detected; run `openclaw doctor --fix` to migrate them to `channels.telegram.streaming.mode`
Tool-progress preview updates are the short "Working..." lines shown while tools run, for example command execution, file reads, planning updates, or patch summaries. Telegram keeps these enabled by default to match released OpenClaw behavior from `v2026.4.22` and later. To keep the edited preview for answer text but hide tool-progress lines, set:
Tool-progress preview updates are the short status lines shown while tools run, for example command execution, file reads, planning updates, or patch summaries. Telegram keeps these enabled by default to match released OpenClaw behavior from `v2026.4.22` and later. To keep the edited preview for answer text but hide tool-progress lines, set:
```json
{
@@ -299,11 +300,51 @@ curl "https://api.telegram.org/bot<bot_token>/getUpdates"
}
```
Use `streaming.mode: "off"` only when you want final-only delivery: Telegram preview edits are disabled and generic tool/progress chatter is suppressed instead of being sent as standalone "Working..." messages. Approval prompts, media payloads, and errors still route through normal final delivery. Use `streaming.preview.toolProgress: false` when you only want to keep answer preview edits while hiding the tool-progress status lines.
To keep tool-progress visible but hide command/exec text, set:
```json
{
"channels": {
"telegram": {
"streaming": {
"mode": "partial",
"preview": {
"commandText": "status"
}
}
}
}
}
```
For progress-draft mode, put the same command-text policy under `streaming.progress`:
```json
{
"channels": {
"telegram": {
"streaming": {
"mode": "progress",
"progress": {
"toolProgress": true,
"commandText": "status"
}
}
}
}
}
```
Use `streaming.mode: "off"` only when you want final-only delivery: Telegram preview edits are disabled and generic tool/progress chatter is suppressed instead of being sent as standalone status messages. Approval prompts, media payloads, and errors still route through normal final delivery. Use `streaming.preview.toolProgress: false` when you only want to keep answer preview edits while hiding the tool-progress status lines.
<Note>
Telegram selected quote replies are the exception. When `replyToMode` is `"first"`, `"all"`, or `"batched"` and the inbound message includes selected quote text, OpenClaw sends the final answer through Telegram's native quote-reply path instead of editing the answer preview, so `streaming.preview.toolProgress` cannot show the short status lines for that turn. Current-message replies without selected quote text still keep preview streaming. Set `replyToMode: "off"` when tool-progress visibility matters more than native quote replies, or set `streaming.preview.toolProgress: false` to acknowledge the trade-off.
</Note>
For text-only replies:
- short DM/group/topic previews: OpenClaw keeps the same preview message and performs a final edit in place
- 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
- 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
For complex replies (for example media payloads), OpenClaw falls back to normal final delivery and then cleans up the preview message.
@@ -313,6 +354,7 @@ curl "https://api.telegram.org/bot<bot_token>/getUpdates"
Telegram-only reasoning stream:
- `/reasoning stream` sends reasoning to the live preview while generating
- the reasoning preview is deleted after final delivery; use `/reasoning on` when reasoning should remain visible
- final answer is sent without reasoning text
</Accordion>
@@ -724,6 +766,7 @@ curl "https://api.telegram.org/bot<bot_token>/getUpdates"
- `channels.telegram.textChunkLimit` default is 4000.
- `channels.telegram.chunkMode="newline"` prefers paragraph boundaries (blank lines) before length splitting.
- `channels.telegram.mediaMaxMb` (default 100) caps inbound and outbound Telegram media size.
- `channels.telegram.mediaGroupFlushMs` (default 500) controls how long Telegram albums/media groups are buffered before OpenClaw dispatches them as one inbound message. Increase it if album parts arrive late; decrease it to reduce album reply latency.
- `channels.telegram.timeoutSeconds` overrides Telegram API client timeout (if unset, grammY default applies). Bot clients clamp configured values below the 60-second outbound text/typing request guard so grammY does not abort visible reply delivery before OpenClaw's transport guard and fallback can run. Long polling still uses a 45-second `getUpdates` request guard so idle polls are not abandoned indefinitely.
- `channels.telegram.pollingStallThresholdMs` defaults to `120000`; tune between `30000` and `600000` only for false-positive polling-stall restarts.
- group context history uses `channels.telegram.historyLimit` or `messages.groupChat.historyLimit` (default 50); `0` disables.
@@ -734,11 +777,12 @@ curl "https://api.telegram.org/bot<bot_token>/getUpdates"
- `channels.telegram.dms["<user_id>"].historyLimit`
- `channels.telegram.retry` config applies to Telegram send helpers (CLI/tools/actions) for recoverable outbound API errors. Inbound final-reply delivery also uses a bounded safe-send retry for Telegram pre-connect failures, but it does not retry ambiguous post-send network envelopes that could duplicate visible messages.
CLI send target can be numeric chat ID or username:
CLI and message-tool send targets can be numeric chat ID, username, or a forum topic target:
```bash
openclaw message send --channel telegram --target 123456789 --message "hi"
openclaw message send --channel telegram --target @name --message "hi"
openclaw message send --channel telegram --target -1001234567890:topic:42 --message "hi topic"
```
Telegram polls use `openclaw message poll` and support forum topics:
@@ -855,7 +899,6 @@ Per-account, per-group, and per-topic overrides are supported (same inheritance
- `getMe returned 401` is a Telegram authentication failure for the configured bot token.
- Re-copy or regenerate the bot token in BotFather, then update `channels.telegram.botToken`, `channels.telegram.tokenFile`, `channels.telegram.accounts.<id>.botToken`, or `TELEGRAM_BOT_TOKEN` for the default account.
- `deleteWebhook 401 Unauthorized` during startup is also an auth failure; treating it as "no webhook exists" would only defer the same bad-token failure to later API calls.
- If `deleteWebhook` fails with a transient network error during polling startup, OpenClaw checks `getWebhookInfo`; when Telegram reports an empty webhook URL, polling continues because cleanup is already satisfied.
</Accordion>
@@ -864,6 +907,8 @@ Per-account, per-group, and per-topic overrides are supported (same inheritance
- Node 22+ + custom fetch/proxy can trigger immediate abort behavior if AbortSignal types mismatch.
- Some hosts resolve `api.telegram.org` to IPv6 first; broken IPv6 egress can cause intermittent Telegram API failures.
- If logs include `TypeError: fetch failed` or `Network request for 'getUpdates' failed!`, OpenClaw now retries these as recoverable network errors.
- During polling startup, OpenClaw reuses the successful startup `getMe` probe for grammY so the runner does not need a second `getMe` before the first `getUpdates`.
- If `deleteWebhook` fails with a transient network error during polling startup, OpenClaw continues into long polling instead of making another pre-poll control-plane call. A still-active webhook surfaces as a `getUpdates` conflict; OpenClaw then rebuilds the Telegram transport and retries webhook cleanup.
- If Telegram sockets recycle on a short fixed cadence, check for a low `channels.telegram.timeoutSeconds`; bot clients clamp configured values below the outbound and `getUpdates` request guards, but older releases could abort every poll or reply when this was set below those guards.
- If logs include `Polling stall detected`, OpenClaw restarts polling and rebuilds the Telegram transport after 120 seconds without completed long-poll liveness by default.
- `openclaw channels status --probe` and `openclaw doctor` warn when a running polling account has not completed `getUpdates` after startup grace, when a running webhook account has not completed `setWebhook` after startup grace, or when the last successful polling transport activity is stale.
@@ -944,7 +989,7 @@ Primary reference: [Configuration reference - Telegram](/gateway/config-channels
- threading/replies: `replyToMode`, `dm.threadReplies`, `direct.*.threadReplies`
- streaming: `streaming` (preview), `streaming.preview.toolProgress`, `blockStreaming`
- formatting/delivery: `textChunkLimit`, `chunkMode`, `linkPreview`, `responsePrefix`
- media/network: `mediaMaxMb`, `timeoutSeconds`, `pollingStallThresholdMs`, `retry`, `network.autoSelectFamily`, `network.dangerouslyAllowPrivateNetwork`, `proxy`
- media/network: `mediaMaxMb`, `mediaGroupFlushMs`, `timeoutSeconds`, `pollingStallThresholdMs`, `retry`, `network.autoSelectFamily`, `network.dangerouslyAllowPrivateNetwork`, `proxy`
- custom API root: `apiRoot` (Bot API root only; do not include `/bot<TOKEN>`)
- webhook: `webhookUrl`, `webhookSecret`, `webhookPath`, `webhookHost`
- actions/capabilities: `capabilities.inlineButtons`, `actions.sendMessage|editMessage|deleteMessage|reactions|sticker`

View File

@@ -190,18 +190,22 @@ Auto-accept DM invites (for ships in dmAllowlist):
}
```
Auto-accept group invites:
Auto-accept group invites from trusted ships:
```json5
{
channels: {
tlon: {
autoAcceptGroupInvites: true,
groupInviteAllowlist: ["~zod"],
},
},
}
```
`autoAcceptGroupInvites` fails closed when `groupInviteAllowlist` is empty. Set the
allowlist to the ships whose group invites should be accepted automatically.
## Delivery targets (CLI/cron)
Use these with `openclaw message send` or cron delivery:
@@ -268,7 +272,8 @@ Provider options:
- `channels.tlon.ownerShip`: owner ship for approval system (always authorized).
- `channels.tlon.dmAllowlist`: ships allowed to DM (empty = none).
- `channels.tlon.autoAcceptDmInvites`: auto-accept DMs from allowlisted ships.
- `channels.tlon.autoAcceptGroupInvites`: auto-accept all group invites.
- `channels.tlon.autoAcceptGroupInvites`: auto-accept group invites from allowlisted ships.
- `channels.tlon.groupInviteAllowlist`: ships whose group invites may be auto-accepted.
- `channels.tlon.autoDiscoverChannels`: auto-discover group channels (default: true).
- `channels.tlon.groupChannels`: manually pinned channel nests.
- `channels.tlon.defaultAuthorizedShips`: ships authorized for all channels.

View File

@@ -60,11 +60,12 @@ Full troubleshooting: [Telegram troubleshooting](/channels/telegram#troubleshoot
### Discord failure signatures
| Symptom | Fastest check | Fix |
| ------------------------------- | ----------------------------------- | --------------------------------------------------------- |
| Bot online but no guild replies | `openclaw channels status --probe` | Allow guild/channel and verify message content intent. |
| Group messages ignored | Check logs for mention gating drops | Mention bot or set guild/channel `requireMention: false`. |
| DM replies missing | `openclaw pairing list discord` | Approve DM pairing or adjust DM policy. |
| Symptom | Fastest check | Fix |
| ----------------------------------------- | ---------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Bot online but no guild replies | `openclaw channels status --probe` | Allow guild/channel and verify message content intent. |
| Group messages ignored | Check logs for mention gating drops | Mention bot or set guild/channel `requireMention: false`. |
| Typing/token usage but no Discord message | Session log shows assistant text with `didSendViaMessagingTool: false` | The model answered privately instead of calling the message tool. Use a tool-call-reliable model, or set `messages.groupChat.visibleReplies: "automatic"` to auto-post. |
| DM replies missing | `openclaw pairing list discord` | Approve DM pairing or adjust DM policy. |
Full troubleshooting: [Discord troubleshooting](/channels/discord#troubleshooting)

View File

@@ -81,7 +81,9 @@ openclaw directory groups list --channel zalouser --query "work"
`channels.zalouser.dmPolicy` supports: `pairing | allowlist | open | disabled` (default: `pairing`).
`channels.zalouser.allowFrom` accepts user IDs or names. During setup, names are resolved to IDs using the plugin's in-process contact lookup.
`channels.zalouser.allowFrom` should use stable Zalo user IDs. During interactive setup, entered names can be resolved to IDs using the plugin's in-process contact lookup.
If a raw name remains in config, startup resolves it only when `channels.zalouser.dangerouslyAllowNameMatching: true` is enabled. Without that opt-in, runtime sender checks are ID-only and raw names are ignored for authorization.
Approve via:
@@ -93,13 +95,13 @@ Approve via:
- Default: `channels.zalouser.groupPolicy = "open"` (groups allowed). Use `channels.defaults.groupPolicy` to override the default when unset.
- Restrict to an allowlist with:
- `channels.zalouser.groupPolicy = "allowlist"`
- `channels.zalouser.groups` (keys should be stable group IDs; names are resolved to IDs on startup when possible)
- `channels.zalouser.groups` (keys should be stable group IDs; names are resolved to IDs on startup only when `channels.zalouser.dangerouslyAllowNameMatching: true` is enabled)
- `channels.zalouser.groupAllowFrom` (controls which senders in allowed groups can trigger the bot)
- Block all groups: `channels.zalouser.groupPolicy = "disabled"`.
- The configure wizard can prompt for group allowlists.
- On startup, OpenClaw resolves group/user names in allowlists to IDs and logs the mapping.
- On startup, OpenClaw resolves group/user names in allowlists to IDs and logs the mapping only when `channels.zalouser.dangerouslyAllowNameMatching: true` is enabled.
- Group allowlist matching is ID-only by default. Unresolved names are ignored for auth unless `channels.zalouser.dangerouslyAllowNameMatching: true` is enabled.
- `channels.zalouser.dangerouslyAllowNameMatching: true` is a break-glass compatibility mode that re-enables mutable group-name matching.
- `channels.zalouser.dangerouslyAllowNameMatching: true` is a break-glass compatibility mode that re-enables mutable startup name resolution and runtime group-name matching.
- If `groupAllowFrom` is unset, runtime falls back to `allowFrom` for group sender checks.
- Sender checks apply to both normal group messages and control commands (for example `/new`, `/reset`).
@@ -181,7 +183,7 @@ Accounts map to `zalouser` profiles in OpenClaw state. Example:
**Allowlist/group name didn't resolve:**
- Use numeric IDs in `allowFrom`/`groupAllowFrom`/`groups`, or exact friend/group names.
- Use numeric IDs in `allowFrom`/`groupAllowFrom` and stable group IDs in `groups`. If you intentionally need exact friend/group names, enable `channels.zalouser.dangerouslyAllowNameMatching: true`.
**Upgraded from old CLI-based setup:**

View File

@@ -12,30 +12,30 @@ OpenClaw CI runs on every push to `main` and every pull request. The `preflight`
## Pipeline overview
| Job | Purpose | When it runs |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------- | ---------------------------------- |
| `preflight` | Detect docs-only changes, changed scopes, changed extensions, and build the CI manifest | Always on non-draft pushes and PRs |
| `security-scm-fast` | Private key detection and workflow audit via `zizmor` | Always on non-draft pushes and PRs |
| `security-dependency-audit` | Dependency-free production lockfile audit against npm advisories | Always on non-draft pushes and PRs |
| `security-fast` | Required aggregate for the fast security jobs | Always on non-draft pushes and PRs |
| `check-dependencies` | Production Knip dependency-only pass plus the unused-file allowlist guard | Node-relevant changes |
| `build-artifacts` | Build `dist/`, Control UI, built-artifact checks, and reusable downstream artifacts | Node-relevant changes |
| `checks-fast-core` | Fast Linux correctness lanes such as bundled/plugin-contract/protocol checks | Node-relevant changes |
| `checks-fast-contracts-channels` | Sharded channel contract checks with a stable aggregate check result | Node-relevant changes |
| `checks-node-core-test` | Core Node test shards, excluding channel, bundled, contract, and extension lanes | Node-relevant changes |
| `check` | Sharded main local gate equivalent: prod types, lint, guards, test types, and strict smoke | Node-relevant changes |
| `check-additional` | Architecture, boundary, prompt snapshot drift, extension-surface guards, package-boundary, and gateway-watch shards | Node-relevant changes |
| `build-smoke` | Built-CLI smoke tests and startup-memory smoke | Node-relevant changes |
| `checks` | Verifier for built-artifact channel tests | Node-relevant changes |
| `checks-node-compat-node22` | Node 22 compatibility build and smoke lane | Manual CI dispatch for releases |
| `check-docs` | Docs formatting, lint, and broken-link checks | Docs changed |
| `skills-python` | Ruff + pytest for Python-backed skills | Python-skill-relevant changes |
| `checks-windows` | Windows-specific process/path tests plus shared runtime import specifier regressions | Windows-relevant changes |
| `macos-node` | macOS TypeScript test lane using the shared built artifacts | macOS-relevant changes |
| `macos-swift` | Swift lint, build, and tests for the macOS app | macOS-relevant changes |
| `android` | Android unit tests for both flavors plus one debug APK build | Android-relevant changes |
| `test-performance-agent` | Daily Codex slow-test optimization after trusted activity | Main CI success or manual dispatch |
| `openclaw-performance` | Daily/on-demand Kova runtime performance reports with mock-provider, deep-profile, and GPT 5.4 live lanes | Scheduled and manual dispatch |
| Job | Purpose | When it runs |
| -------------------------------- | --------------------------------------------------------------------------------------------------------- | ---------------------------------- |
| `preflight` | Detect docs-only changes, changed scopes, changed extensions, and build the CI manifest | Always on non-draft pushes and PRs |
| `security-scm-fast` | Private key detection and workflow audit via `zizmor` | Always on non-draft pushes and PRs |
| `security-dependency-audit` | Dependency-free production lockfile audit against npm advisories | Always on non-draft pushes and PRs |
| `security-fast` | Required aggregate for the fast security jobs | Always on non-draft pushes and PRs |
| `check-dependencies` | Production Knip dependency-only pass plus the unused-file allowlist guard | Node-relevant changes |
| `build-artifacts` | Build `dist/`, Control UI, built-artifact checks, and reusable downstream artifacts | Node-relevant changes |
| `checks-fast-core` | Fast Linux correctness lanes such as bundled/plugin-contract/protocol checks | Node-relevant changes |
| `checks-fast-contracts-channels` | Sharded channel contract checks with a stable aggregate check result | Node-relevant changes |
| `checks-node-core-test` | Core Node test shards, excluding channel, bundled, contract, and extension lanes | Node-relevant changes |
| `check` | Sharded main local gate equivalent: prod types, lint, guards, test types, and strict smoke | Node-relevant changes |
| `check-additional` | Architecture, sharded boundary/prompt drift, extension guards, package boundary, and gateway watch | Node-relevant changes |
| `build-smoke` | Built-CLI smoke tests and startup-memory smoke | Node-relevant changes |
| `checks` | Verifier for built-artifact channel tests | Node-relevant changes |
| `checks-node-compat-node22` | Node 22 compatibility build and smoke lane | Manual CI dispatch for releases |
| `check-docs` | Docs formatting, lint, and broken-link checks | Docs changed |
| `skills-python` | Ruff + pytest for Python-backed skills | Python-skill-relevant changes |
| `checks-windows` | Windows-specific process/path tests plus shared runtime import specifier regressions | Windows-relevant changes |
| `macos-node` | macOS TypeScript test lane using the shared built artifacts | macOS-relevant changes |
| `macos-swift` | Swift lint, build, and tests for the macOS app | macOS-relevant changes |
| `android` | Android unit tests for both flavors plus one debug APK build | Android-relevant changes |
| `test-performance-agent` | Daily Codex slow-test optimization after trusted activity | Main CI success or manual dispatch |
| `openclaw-performance` | Daily/on-demand Kova runtime performance reports with mock-provider, deep-profile, and GPT 5.4 live lanes | Scheduled and manual dispatch |
## Fail-fast order
@@ -54,7 +54,7 @@ Scope logic lives in `scripts/ci-changed-scope.mjs` and is covered by unit tests
- **CI routing-only edits, selected cheap core-test fixture edits, and narrow plugin contract helper/test-routing edits** use a fast Node-only manifest path: `preflight`, security, and a single `checks-fast-core` task. That path skips build artifacts, Node 22 compatibility, channel contracts, full core shards, bundled-plugin shards, and additional guard matrices when the change is limited to the routing or helper surfaces the fast task exercises directly.
- **Windows Node checks** are scoped to Windows-specific process/path wrappers, npm/pnpm/UI runner helpers, package manager config, and the CI workflow surfaces that execute that lane; unrelated source, plugin, install-smoke, and test-only changes stay on the Linux Node lanes.
The slowest Node test families are split or balanced so each job stays small without over-reserving runners: channel contracts run as three weighted shards, small core unit lanes are paired, auto-reply runs as four balanced workers (with the reply subtree split into agent-runner, dispatch, and commands/state-routing shards), and agentic gateway/plugin configs are spread across the existing source-only agentic Node jobs instead of waiting on built artifacts. Broad browser, QA, media, and miscellaneous plugin tests use their dedicated Vitest configs instead of the shared plugin catch-all. Include-pattern shards record timing entries using the CI shard name, so `.artifacts/vitest-shard-timings.json` can distinguish a whole config from a filtered shard. `check-additional` keeps package-boundary compile/canary work together and separates runtime topology architecture from gateway watch coverage; the boundary guard shard runs its small independent guards concurrently inside one job, including `pnpm prompt:snapshots:check` so Codex runtime happy-path prompt drift is pinned to the PR that caused it. Gateway watch, channel tests, and the core support-boundary shard run concurrently inside `build-artifacts` after `dist/` and `dist-runtime/` are already built.
The slowest Node test families are split or balanced so each job stays small without over-reserving runners: channel contracts run as three weighted shards, core unit fast/support lanes run separately, core runtime infra is split between state and process/config shards, auto-reply runs as balanced workers (with the reply subtree split into agent-runner, dispatch, and commands/state-routing shards), and agentic gateway/server configs are split across chat/auth/model/http-plugin/runtime/startup lanes instead of waiting on built artifacts. Broad browser, QA, media, and miscellaneous plugin tests use their dedicated Vitest configs instead of the shared plugin catch-all. Include-pattern shards record timing entries using the CI shard name, so `.artifacts/vitest-shard-timings.json` can distinguish a whole config from a filtered shard. `check-additional` keeps package-boundary compile/canary work together and separates runtime topology architecture from gateway watch coverage; the boundary guard list is striped across four matrix shards, each running selected independent guards concurrently and printing per-check timings, including `pnpm prompt:snapshots:check` so Codex runtime happy-path prompt drift is pinned to the PR that caused it. Gateway watch, channel tests, and the core support-boundary shard run concurrently inside `build-artifacts` after `dist/` and `dist-runtime/` are already built.
Android CI runs both `testPlayDebugUnitTest` and `testThirdPartyDebugUnitTest` and then builds the Play debug APK. The third-party flavor has no separate source set or manifest; its unit-test lane still compiles the flavor with the SMS/call-log BuildConfig flags, while avoiding a duplicate debug APK packaging job on every Android-relevant push.
@@ -493,16 +493,93 @@ The sanity check fails fast when required root files such as `pnpm-lock.yaml` di
`pnpm testbox:run` also terminates a local Blacksmith CLI invocation that stays in the sync phase for more than five minutes without post-sync output. Set `OPENCLAW_TESTBOX_SYNC_TIMEOUT_MS=0` to disable that guard, or use a larger millisecond value for unusually large local diffs.
Crabbox is the repo-owned second remote-box path for Linux proof when Blacksmith is unavailable or when owned cloud capacity is preferable. Warm a box, hydrate it through the project workflow, then run commands through the Crabbox CLI:
Crabbox is the repo-owned remote-box wrapper for maintainer Linux proof. Use it when a check is too broad for a local edit loop, when CI parity matters, or when the proof needs secrets, Docker, package lanes, reusable boxes, or remote logs. The normal OpenClaw backend is `blacksmith-testbox`; owned AWS/Hetzner capacity is a fallback for Blacksmith outages, quota issues, or explicit owned-capacity testing.
Before a first run, check the wrapper from the repo root:
```bash
pnpm crabbox:warmup -- --idle-timeout 90m
pnpm crabbox:hydrate -- --id <cbx_id>
pnpm crabbox:run -- --id <cbx_id> --shell "OPENCLAW_TESTBOX=1 pnpm check:changed"
pnpm crabbox:stop -- <cbx_id>
pnpm crabbox:run -- --help | sed -n '1,120p'
```
`.crabbox.yaml` owns provider, sync, and GitHub Actions hydration defaults. It excludes local `.git` so the hydrated Actions checkout keeps its own remote Git metadata instead of syncing maintainer-local remotes and object stores, and it excludes local runtime/build artifacts that should never be transferred. `.github/workflows/crabbox-hydrate.yml` owns checkout, Node/pnpm setup, `origin/main` fetch, and the non-secret environment handoff that later `crabbox run --id <cbx_id>` commands source.
The repo wrapper refuses a stale Crabbox binary that does not advertise `blacksmith-testbox`. Pass the provider explicitly even though `.crabbox.yaml` has owned-cloud defaults.
Changed gate:
```bash
pnpm crabbox:run -- --provider blacksmith-testbox \
--blacksmith-org openclaw \
--blacksmith-workflow .github/workflows/ci-check-testbox.yml \
--blacksmith-job check \
--blacksmith-ref main \
--idle-timeout 90m \
--ttl 240m \
--timing-json \
--shell -- \
"env CI=1 NODE_OPTIONS=--max-old-space-size=4096 OPENCLAW_TEST_PROJECTS_PARALLEL=6 OPENCLAW_VITEST_MAX_WORKERS=1 OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=900000 pnpm check:changed"
```
Focused test rerun:
```bash
pnpm crabbox:run -- --provider blacksmith-testbox \
--blacksmith-org openclaw \
--blacksmith-workflow .github/workflows/ci-check-testbox.yml \
--blacksmith-job check \
--blacksmith-ref main \
--idle-timeout 90m \
--ttl 240m \
--timing-json \
--shell -- \
"env CI=1 NODE_OPTIONS=--max-old-space-size=4096 OPENCLAW_VITEST_MAX_WORKERS=1 OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=900000 pnpm test <path-or-filter>"
```
Full suite:
```bash
pnpm crabbox:run -- --provider blacksmith-testbox \
--blacksmith-org openclaw \
--blacksmith-workflow .github/workflows/ci-check-testbox.yml \
--blacksmith-job check \
--blacksmith-ref main \
--idle-timeout 90m \
--ttl 240m \
--timing-json \
--shell -- \
"env CI=1 NODE_OPTIONS=--max-old-space-size=4096 OPENCLAW_TEST_PROJECTS_PARALLEL=6 OPENCLAW_VITEST_MAX_WORKERS=1 OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=900000 pnpm test"
```
Read the final JSON summary. The useful fields are `provider`, `leaseId`, `syncDelegated`, `exitCode`, `commandMs`, and `totalMs`. One-shot Blacksmith-backed Crabbox runs should stop the Testbox automatically; if a run is interrupted or cleanup is unclear, inspect live boxes and stop only the boxes you created:
```bash
blacksmith testbox list
blacksmith testbox stop --id <tbx_id>
```
Use reuse only when you intentionally need multiple commands on the same hydrated box:
```bash
pnpm crabbox:run -- --provider blacksmith-testbox --id <tbx_id> --no-sync --timing-json --shell -- "pnpm test <path-or-filter>"
pnpm crabbox:stop -- <tbx_id>
```
If Crabbox is the broken layer but Blacksmith itself works, use direct Blacksmith as a narrow fallback:
```bash
blacksmith testbox warmup ci-check-testbox.yml --ref main --idle-timeout 90
blacksmith testbox run --id <tbx_id> "env CI=1 NODE_OPTIONS=--max-old-space-size=4096 OPENCLAW_TEST_PROJECTS_PARALLEL=6 OPENCLAW_VITEST_MAX_WORKERS=1 OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=900000 pnpm check:changed"
blacksmith testbox stop --id <tbx_id>
```
Escalate to owned Crabbox capacity only when Blacksmith is down, quota-limited, missing the needed environment, or owned capacity is explicitly the goal:
```bash
pnpm crabbox:warmup -- --provider aws --class beast --market on-demand --idle-timeout 90m
pnpm crabbox:hydrate -- --id <cbx_id-or-slug>
pnpm crabbox:run -- --id <cbx_id-or-slug> --timing-json --shell -- "env NODE_OPTIONS=--max-old-space-size=4096 OPENCLAW_TEST_PROJECTS_PARALLEL=6 OPENCLAW_VITEST_MAX_WORKERS=1 OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=900000 pnpm check:changed"
pnpm crabbox:stop -- <cbx_id-or-slug>
```
`.crabbox.yaml` owns provider, sync, and GitHub Actions hydration defaults for owned-cloud lanes. It excludes local `.git` so the hydrated Actions checkout keeps its own remote Git metadata instead of syncing maintainer-local remotes and object stores, and it excludes local runtime/build artifacts that should never be transferred. `.github/workflows/crabbox-hydrate.yml` owns checkout, Node/pnpm setup, `origin/main` fetch, and the non-secret environment handoff for owned-cloud `crabbox run --id <cbx_id>` commands.
## Related

View File

@@ -439,9 +439,9 @@ ls -lt "$CONFIG".rejected.* 2>/dev/null | head
openclaw config validate
```
Direct editor writes are still allowed, but the running Gateway treats them as untrusted until they validate. Invalid direct edits can be restored from the last-known-good backup during startup or hot reload. See [Gateway troubleshooting](/gateway/troubleshooting#gateway-restored-last-known-good-config).
Direct editor writes are still allowed, but the running Gateway treats them as untrusted until they validate. Invalid direct edits fail startup or are skipped by hot reload; Gateway does not rewrite `openclaw.json`. Run `openclaw doctor --fix` to repair prefixed/clobbered config or restore the last-known-good copy. See [Gateway troubleshooting](/gateway/troubleshooting#gateway-rejected-invalid-config).
Whole-file recovery is reserved for globally broken config, such as parse errors, root-level schema failures, legacy migration failures, or mixed plugin and root failures. If validation fails only under `plugins.entries.<id>...`, OpenClaw keeps the active `openclaw.json` in place and reports the plugin-local issue instead of restoring `.last-good`. This prevents plugin schema changes or `minHostVersion` skew from rolling back unrelated user settings such as models, providers, auth profiles, channels, gateway exposure, tools, memory, browser, or cron config.
Whole-file recovery is reserved for doctor repair. Plugin schema changes or `minHostVersion` skew stay loud instead of rolling back unrelated user settings such as models, providers, auth profiles, channels, gateway exposure, tools, memory, browser, or cron config.
## Subcommands

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

@@ -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.
- 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 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>

View File

@@ -51,7 +51,7 @@ openclaw memory index --agent main --verbose
`memory status`:
- `--deep`: probe vector + embedding availability. Plain `memory status` stays fast and does not run a live embedding ping. QMD lexical `searchMode: "search"` skips semantic vector probes and embedding maintenance even with `--deep`.
- `--deep`: probe local vector-store readiness, embedding-provider readiness, and semantic vector-search readiness. Plain `memory status` stays fast and does not run live embedding or provider discovery work; unknown vector-store or semantic-vector state means it was not probed in that command. QMD lexical `searchMode: "search"` skips semantic vector probes and embedding maintenance even with `--deep`.
- `--index`: run a reindex if the store is dirty (implies `--deep`).
- `--fix`: repair stale recall locks and normalize promotion metadata.
- `--json`: print JSON output.

View File

@@ -27,7 +27,7 @@ Channel selection:
Target formats (`--target`):
- WhatsApp: E.164, group JID, or WhatsApp Channel/Newsletter JID (`...@newsletter`)
- Telegram: chat id or `@username`
- Telegram: chat id, `@username`, or forum topic target (`-1001234567890:topic:42`, or `--thread-id 42`)
- Discord: `channel:<id>` or `user:<id>` (or `<@id>` mention; raw numeric ids are treated as channels)
- Google Chat: `spaces/<spaceId>` or `users/<userId>`
- Slack: `channel:<id>` or `user:<id>` (raw channel id is accepted)

View File

@@ -162,6 +162,7 @@ openclaw models fallbacks list
```bash
openclaw models auth add
openclaw models auth list [--provider <id>] [--json]
openclaw models auth login --provider <id>
openclaw models auth setup-token --provider <id>
openclaw models auth paste-token
@@ -171,16 +172,22 @@ openclaw models auth paste-token
flow (OAuth/API key) or guide you into manual token paste, depending on the
provider you choose.
`models auth list` lists saved auth profiles for the selected agent without
printing token, API-key, or OAuth secret material. Use `--provider <id>` to
filter to one provider, such as `openai-codex`, and `--json` for scripting.
`models auth login` runs a provider plugins auth flow (OAuth/API key). Use
`openclaw plugins list` to see which providers are installed.
Use `openclaw models auth --agent <id> <subcommand>` to write auth results to a
specific configured agent store. The parent `--agent` flag is honored by
`add`, `login`, `setup-token`, `paste-token`, and `login-github-copilot`.
`add`, `list`, `login`, `setup-token`, `paste-token`, and
`login-github-copilot`.
Examples:
```bash
openclaw models auth login --provider openai-codex --set-default
openclaw models auth list --provider openai-codex
```
Notes:

View File

@@ -104,10 +104,10 @@ is available, then fall back to `latest`.
</Note>
<AccordionGroup>
<Accordion title="Config includes and invalid-config recovery">
<Accordion title="Config includes and invalid-config repair">
If your `plugins` section is backed by a single-file `$include`, `plugins install/update/enable/disable/uninstall` write through to that included file and leave `openclaw.json` untouched. Root includes, include arrays, and includes with sibling overrides fail closed instead of flattening. See [Config includes](/gateway/configuration) for the supported shapes.
If config is invalid during install, `plugins install` normally fails closed and tells you to run `openclaw doctor --fix` first. During Gateway startup, invalid config for one plugin is isolated to that plugin so other channels and plugins can keep running; `openclaw doctor --fix` can quarantine the invalid plugin entry. The only documented install-time exception is a narrow bundled-plugin recovery path for plugins that explicitly opt into `openclaw.install.allowInvalidConfigRecovery`.
If config is invalid during install, `plugins install` normally fails closed and tells you to run `openclaw doctor --fix` first. During Gateway startup and hot reload, invalid plugin config fails closed like any other invalid config; `openclaw doctor --fix` can quarantine the invalid plugin entry. The only documented install-time exception is a narrow bundled-plugin recovery path for plugins that explicitly opt into `openclaw.install.allowInvalidConfigRecovery`.
</Accordion>
<Accordion title="--force and reinstall vs update">
@@ -134,7 +134,7 @@ is available, then fall back to `latest`.
Use `npm:<package>` when you want to make npm resolution explicit. Bare package specs also install directly from npm during the launch cutover.
Bare specs and `@latest` stay on the stable track. If npm resolves either of those to a prerelease, OpenClaw stops and asks you to opt in explicitly with a prerelease tag such as `@beta`/`@rc` or an exact prerelease version such as `@1.2.3-beta.4`.
Bare specs and `@latest` stay on the stable track. OpenClaw date-stamped correction versions such as `2026.5.3-1` are stable releases for this check. If npm resolves either of those to a prerelease, OpenClaw stops and asks you to opt in explicitly with a prerelease tag such as `@beta`/`@rc` or an exact prerelease version such as `@1.2.3-beta.4`.
If a bare install spec matches an official plugin id (for example `diffs`), OpenClaw installs the catalog entry directly. To install an npm package with the same name, use an explicit scoped spec (for example `@scope/diffs`).
@@ -371,6 +371,8 @@ openclaw plugins doctor
`doctor` reports plugin load errors, manifest/discovery diagnostics, and compatibility notices. When everything is clean it prints `No plugin issues detected.`
If a configured plugin is present on disk but blocked by the loader's path-safety checks, config validation keeps the plugin entry and reports it as `present but blocked`. Fix the preceding blocked-plugin diagnostic, such as path ownership or world-writable permissions, instead of removing the `plugins.entries.<id>` or `plugins.allow` config.
For module-shape failures such as missing `register`/`activate` exports, rerun with `OPENCLAW_PLUGIN_LOAD_DEBUG=1` to include a compact export-shape summary in the diagnostic output.
### Registry
@@ -385,6 +387,8 @@ The local plugin registry is OpenClaw's persisted cold read model for installed
Use `plugins registry` to inspect whether the persisted registry is present, current, or stale. Use `--refresh` to rebuild it from the persisted plugin index, config policy, and manifest/package metadata. This is a repair path, not a runtime activation path.
`openclaw doctor --fix` also repairs registry-adjacent managed npm drift: if an orphaned or recovered `@openclaw/*` package under the managed plugin npm root shadows a bundled plugin, doctor removes that stale package and rebuilds the registry so startup validates against the bundled manifest.
<Warning>
`OPENCLAW_DISABLE_PERSISTED_PLUGIN_REGISTRY=1` is a deprecated break-glass compatibility switch for registry read failures. Prefer `plugins registry --refresh` or `openclaw doctor --fix`; the env fallback is only for emergency startup recovery while the migration rolls out.
</Warning>

View File

@@ -23,7 +23,7 @@ captured blobs, and purge local capture data.
```bash
openclaw proxy start [--host <host>] [--port <port>]
openclaw proxy run [--host <host>] [--port <port>] -- <cmd...>
openclaw proxy validate [--json] [--proxy-url <url>] [--allowed-url <url>] [--denied-url <url>] [--timeout-ms <ms>]
openclaw proxy validate [--json] [--proxy-url <url>] [--allowed-url <url>] [--denied-url <url>] [--apns-reachable] [--apns-authority <url>] [--timeout-ms <ms>]
openclaw proxy coverage
openclaw proxy sessions [--limit <count>]
openclaw proxy query --preset <name> [--session <id>]
@@ -40,7 +40,10 @@ before changing config. By default it verifies that a public destination succeed
through the proxy and that the proxy cannot reach a temporary loopback canary.
Custom denied destinations are fail-closed: HTTP responses and ambiguous
transport failures both fail unless you can verify a deployment-specific denial
signal separately.
signal separately. Add `--apns-reachable` to also open an APNs HTTP/2 CONNECT
tunnel through the proxy and confirm sandbox APNs responds; the probe uses an
intentionally invalid provider token, so an APNs `403 InvalidProviderToken`
response is a successful reachability signal.
Options:
@@ -48,6 +51,8 @@ Options:
- `--proxy-url <url>`: validate this proxy URL instead of config or env.
- `--allowed-url <url>`: add a destination expected to succeed through the proxy. Repeat to check multiple destinations.
- `--denied-url <url>`: add a destination expected to be blocked by the proxy. Repeat to check multiple destinations.
- `--apns-reachable`: also verify sandbox APNs HTTP/2 is reachable through the proxy.
- `--apns-authority <url>`: APNs authority to probe with `--apns-reachable` (`https://api.sandbox.push.apple.com` by default; production is `https://api.push.apple.com`).
- `--timeout-ms <ms>`: per-request timeout in milliseconds.
See [Network Proxy](/security/network-proxy) for deployment guidance and denial
@@ -68,6 +73,7 @@ semantics.
- `start` defaults to `127.0.0.1` unless `--host` is set.
- `run` starts a local debug proxy and then runs the command after `--`.
- The debug proxy's direct upstream forwarding opens upstream sockets for diagnostics. When OpenClaw managed proxy mode is active, direct forwarding for proxy requests and CONNECT tunnels is disabled by default; set `OPENCLAW_DEBUG_PROXY_ALLOW_DIRECT_CONNECT_WITH_MANAGED_PROXY=1` only for approved local diagnostics.
- `validate` exits with code 1 when proxy config or destination checks fail.
- Captures are local debugging data; use `openclaw proxy purge` when finished.

View File

@@ -16,11 +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.
`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
```
@@ -32,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:
@@ -63,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" },

View File

@@ -2,6 +2,7 @@
summary: "CLI reference for `openclaw update` (safe-ish source update + gateway auto-restart)"
read_when:
- You want to update a source checkout safely
- You are debugging `openclaw update` output or options
- You need to understand `--update` shorthand behavior
title: "Update"
---
@@ -42,6 +43,14 @@ openclaw --update
- `--timeout <seconds>`: per-step timeout (default is 1800s).
- `--yes`: skip confirmation prompts (for example downgrade confirmation).
`openclaw update` does not have a `--verbose` flag. Use `--dry-run` to preview
the planned channel/tag/install/restart actions, `--json` for machine-readable
results, and `openclaw update status --json` when you only need channel and
availability details. If you are debugging Gateway logs around an update,
console verbosity and file log level are separate: Gateway `--verbose` affects
terminal/WebSocket output, while file logs require `logging.level: "debug"` or
`"trace"` in config. See [Gateway logging](/gateway/logging).
<Warning>
Downgrades require confirmation because older versions can break configuration.
</Warning>
@@ -103,10 +112,19 @@ explicit `openclaw completion --write-state` runs.
When a local managed Gateway service is installed and restart is enabled,
package-manager updates stop the running service before replacing the package
tree, then refresh the service metadata from the updated install, restart the
service, and verify the restarted Gateway reports the expected version. With
`--no-restart`, package replacement still runs but the managed service is not
stopped or restarted, so the running Gateway may keep old code until you restart
it manually.
service, and verify the restarted Gateway reports the expected version before
reporting success. On macOS, the post-update check also verifies the LaunchAgent
is loaded/running for the active profile and the configured loopback port is
healthy. If the plist is installed but launchd is not supervising it, OpenClaw
re-bootstraps the LaunchAgent automatically, then reruns the
health/version/channel readiness checks. A fresh bootstrap loads the RunAtLoad
job directly, so update recovery does not immediately `kickstart -k` the newly
spawned Gateway. If the Gateway still does not become healthy, the command exits
non-zero and prints the restart log path plus explicit restart, reinstall, and
package rollback instructions. With `--no-restart`,
package replacement still runs but the managed service is not stopped or
restarted, so the running Gateway may keep old code until you restart it
manually.
## Git checkout flow
@@ -150,8 +168,9 @@ it 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, and only that path releases the affected session lane so queued startup work can drain. 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 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.
- 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

@@ -33,13 +33,13 @@ Inside `agents.defaults.workspace`, OpenClaw expects these user-editable files:
- `IDENTITY.md` — agent name/vibe/emoji
- `USER.md` — user profile + preferred address
On the first turn of a new session, OpenClaw injects the contents of these files directly into the agent context.
On the first turn of a new session, OpenClaw injects the contents of these files into the system prompt's Project Context.
Blank files are skipped. Large files are trimmed and truncated with a marker so prompts stay lean (read the file for full content).
If a file is missing, OpenClaw injects a single “missing file” marker line (and `openclaw setup` will create a safe default template).
`BOOTSTRAP.md` is only created for a **brand new workspace** (no other bootstrap files present). If you delete it after completing the ritual, it should not be recreated on later restarts.
`BOOTSTRAP.md` is only created for a **brand new workspace** (no other bootstrap files present). While it is pending, OpenClaw keeps it in Project Context and adds system-prompt bootstrap guidance for the initial ritual instead of copying it into the user message. If you delete it after completing the ritual, it should not be recreated on later restarts.
To disable bootstrap file creation entirely (for pre-seeded workspaces), set:

551
docs/concepts/mantis.md Normal file
View File

@@ -0,0 +1,551 @@
---
summary: "Mantis is the visual end-to-end verification system for reproducing OpenClaw bugs on live transports, capturing before and after evidence, and attaching artifacts to PRs."
title: "Mantis"
read_when:
- Building or running live visual QA for OpenClaw bugs
- Adding before and after verification for a pull request
- Adding Discord, Slack, WhatsApp, or other live transport scenarios
- Debugging QA runs that need screenshots, browser automation, or VNC access
---
Mantis is the OpenClaw end-to-end verification system for bugs that need a real
runtime, a real transport, and visible proof. It runs a scenario against a known
bad ref, captures evidence, runs the same scenario against a candidate ref, and
publishes the comparison as artifacts that a maintainer can inspect from a PR or
from a local command.
Mantis starts with Discord because Discord gives us a high-value first lane:
real bot auth, real guild channels, reactions, threads, native commands, and a
browser UI where humans can visually confirm what the transport showed.
## Goals
- Reproduce a bug from a GitHub issue or PR with the same transport shape users
see.
- Capture a **before** artifact on the baseline ref before applying the fix.
- Capture an **after** artifact on the candidate ref after applying the fix.
- Use a deterministic oracle whenever possible, such as a Discord REST reaction
read or channel transcript check.
- Capture screenshots when the bug has a visible UI surface.
- Run locally from an agent-controlled CLI and remotely from GitHub.
- Preserve enough machine state for VNC rescue when login, browser automation, or
provider auth gets stuck.
- Post concise status to an operator Discord channel when the run is blocked,
needs manual VNC help, or finishes.
## Non Goals
- Mantis is not a replacement for unit tests. A Mantis run should usually become
a smaller regression test after the fix is understood.
- Mantis is not the normal fast CI gate. It is slower, uses live credentials, and
is reserved for bugs where the live environment matters.
- Mantis should not require a human for normal operation. Manual VNC is a rescue
path, not the happy path.
- Mantis does not store raw secrets in artifacts, logs, screenshots, Markdown
reports, or PR comments.
## Ownership
Mantis lives in the OpenClaw QA stack.
- OpenClaw owns the scenario runtime, transport adapters, evidence schema, and
local CLI under `pnpm openclaw qa mantis`.
- QA Lab owns the live transport harness pieces, browser capture helpers, and
artifact writers.
- Crabbox owns warmed Linux machines when a remote VM is needed.
- GitHub Actions owns the remote workflow entrypoint and artifact retention.
- ClawSweeper owns GitHub comment routing: parsing maintainer commands,
dispatching the workflow, and posting the final PR comment.
- OpenClaw agents drive Mantis through Codex when a scenario needs agentic setup,
debugging, or stuck-state reporting.
This boundary keeps transport knowledge in OpenClaw, machine scheduling in
Crabbox, and maintainer workflow glue in ClawSweeper.
## Command Shape
The first local command verifies the Discord bot, guild, channel, message send,
reaction send, and artifact path:
```bash
pnpm openclaw qa mantis discord-smoke \
--output-dir .artifacts/qa-e2e/mantis/discord-smoke
```
The local before and after runner accepts this shape:
```bash
pnpm openclaw qa mantis run \
--transport discord \
--scenario discord-status-reactions-tool-only \
--baseline origin/main \
--candidate HEAD \
--output-dir .artifacts/qa-e2e/mantis/local-discord-status-reactions
```
The runner creates detached baseline and candidate worktrees under the output
directory, installs dependencies, builds each ref, runs the scenario with
`--allow-failures`, then writes `baseline/`, `candidate/`, `comparison.json`,
and `mantis-report.md`. For the first Discord scenario, a successful verification
means baseline status is `fail` and candidate status is `pass`.
The first VM/browser primitive is the desktop smoke:
```bash
pnpm openclaw qa mantis desktop-browser-smoke \
--output-dir .artifacts/qa-e2e/mantis/desktop-browser
```
It leases or reuses a Crabbox desktop machine, starts a visible browser inside the
VNC session, captures the desktop, pulls artifacts back to the local output
directory, and writes the reconnect command into the report. The command defaults
to the Hetzner provider because it is the first provider with working desktop/VNC
coverage in the Mantis lane. Override it with `--provider`, `--crabbox-bin`, or
`OPENCLAW_MANTIS_CRABBOX_PROVIDER` when running against another Crabbox fleet.
Useful desktop smoke flags:
- `--lease-id <cbx_...>` or `OPENCLAW_MANTIS_CRABBOX_LEASE_ID` reuses a warmed desktop.
- `--browser-url <url>` changes the page opened in the visible browser.
- `--html-file <path>` renders a repo-local HTML artifact in the visible browser. Mantis uses this to capture the generated Discord status-reaction timeline through a real Crabbox desktop.
- `--keep-lease` or `OPENCLAW_MANTIS_KEEP_VM=1` keeps a newly created passing lease open for VNC inspection. Failed runs keep the lease by default when one was created so an operator can reconnect.
- `--class`, `--idle-timeout`, and `--ttl` tune machine size and lease lifetime.
The first full desktop transport primitive is the Slack desktop smoke:
```bash
pnpm openclaw qa mantis slack-desktop-smoke \
--output-dir .artifacts/qa-e2e/mantis/slack-desktop \
--gateway-setup \
--scenario slack-canary \
--keep-lease
```
It leases or reuses a Crabbox desktop machine, syncs the current checkout into
the VM, runs `pnpm openclaw qa slack` inside that VM, opens Slack Web in the VNC
browser, captures the visible desktop, and copies both the Slack QA artifacts and
the VNC screenshot back to the local output directory. This is the first Mantis
shape where the SUT OpenClaw gateway and the browser both live inside the same
Linux desktop VM.
With `--gateway-setup`, the command prepares a persistent disposable OpenClaw
home at `$HOME/.openclaw-mantis/slack-openclaw`, patches Slack Socket Mode
configuration for the selected channel, starts `openclaw gateway run` on port
`38973`, and keeps Chrome running in the VNC session. This is the "leave me a
Linux desktop with Slack and a claw running" mode; the bot-to-bot Slack QA lane
remains the default when `--gateway-setup` is omitted.
Required inputs for `--credential-source env`:
- `OPENCLAW_QA_SLACK_CHANNEL_ID`
- `OPENCLAW_QA_SLACK_DRIVER_BOT_TOKEN`
- `OPENCLAW_QA_SLACK_SUT_BOT_TOKEN`
- `OPENCLAW_QA_SLACK_SUT_APP_TOKEN`
- `OPENCLAW_LIVE_OPENAI_KEY` for the remote model lane. If only
`OPENAI_API_KEY` is set locally, Mantis maps it to `OPENCLAW_LIVE_OPENAI_KEY`
before invoking Crabbox so Crabbox's `OPENCLAW_*` env forwarding can carry it
into the VM.
Useful Slack desktop flags:
- `--lease-id <cbx_...>` reruns against a machine where an operator already logged in to Slack Web through VNC.
- `--gateway-setup` starts a persistent OpenClaw Slack gateway in the VM instead of only running the bot-to-bot QA lane.
- `--slack-url <url>` opens a specific Slack Web URL. Without it, Mantis derives `https://app.slack.com/client/<team>/<channel>` from Slack `auth.test` when the SUT bot token is available.
- `--slack-channel-id <id>` controls the Slack channel allowlist used by gateway setup.
- `OPENCLAW_MANTIS_SLACK_BROWSER_PROFILE_DIR` controls the persistent Chrome profile inside the VM. The default is `$HOME/.config/openclaw-mantis/slack-chrome-profile`, so a manual Slack Web login survives reruns on the same lease.
- `--credential-source convex --credential-role ci` uses the shared credential pool instead of direct Slack env tokens.
- `--provider-mode`, `--model`, `--alt-model`, and `--fast` pass through to the Slack live lane.
The GitHub smoke workflow is `Mantis Discord Smoke`. The before and after GitHub
workflow for the first real scenario is `Mantis Discord Status Reactions`. It
accepts:
- `baseline_ref`: the ref expected to reproduce queued-only behavior.
- `candidate_ref`: the ref expected to show `queued -> thinking -> done`.
It checks out the workflow harness ref, builds separate baseline and candidate
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
`openclaw/crabbox` main so it can use the current desktop/browser lease flags
before the next Crabbox binary release is cut.
You can also trigger the status-reactions run directly from a PR comment:
```text
@Mantis discord status reactions
```
The comment trigger is intentionally narrow. It only runs on pull request
comments from users with write, maintain, or admin access, and it only recognizes
Discord status-reaction requests. By default it uses the known bad baseline ref
and the current PR head SHA as the candidate. Maintainers can override either
ref:
```text
@Mantis discord status reactions baseline=origin/main candidate=HEAD
```
ClawSweeper command examples:
```text
@clawsweeper mantis discord discord-status-reactions-tool-only
@clawsweeper verify e2e discord
```
The first command is explicit and scenario-focused. The second can later map a PR
or issue to recommended Mantis scenarios from labels, changed files, and
ClawSweeper review findings.
## Run Lifecycle
1. Acquire credentials.
2. Allocate or reuse a VM.
3. Prepare the desktop/browser profile when the scenario needs UI evidence.
4. Prepare a clean checkout for the baseline ref.
5. Install dependencies and build only what the scenario needs.
6. Start a child OpenClaw Gateway with an isolated state directory.
7. Configure the live transport, provider, model, and browser profile.
8. Run the scenario and capture baseline evidence.
9. Stop the gateway and preserve logs.
10. Prepare the candidate ref in the same VM.
11. Run the same scenario and capture candidate evidence.
12. Compare the oracle results and visual evidence.
13. Write Markdown, JSON, logs, screenshots, and optional trace artifacts.
14. Upload GitHub Actions artifacts.
15. Post a concise PR or Discord status message.
The scenario should be able to fail in two different ways:
- **Bug reproduced**: baseline failed in the expected way.
- **Harness failure**: environment setup, credentials, Discord API, browser, or
provider failed before the bug oracle was meaningful.
The final report must separate these cases so maintainers do not confuse a flaky
environment with product behavior.
## Discord MVP
The first scenario should target Discord status reactions in guild channels where
the source reply delivery mode is `message_tool_only`.
Why it is a good Mantis seed:
- It is visible in Discord as reactions on the triggering message.
- It has a strong REST oracle through Discord message reaction state.
- It exercises a real OpenClaw Gateway, Discord bot auth, message dispatch,
source reply delivery mode, status reaction state, and model turn lifecycle.
- It is narrow enough to keep the first implementation honest.
Expected scenario shape:
```yaml
id: discord-status-reactions-tool-only
transport: discord
baseline:
expect:
reproduced: true
candidate:
expect:
fixed: true
config:
messages:
ackReaction: "👀"
ackReactionScope: "group-mentions"
groupChat:
visibleReplies: "message_tool"
statusReactions:
enabled: true
timing:
debounceMs: 0
discord:
requireMention: true
notifyChannel: operator-notify
evidence:
rest:
messageReactions: true
browser:
screenshotMessageRow: true
```
Baseline evidence should show the queued acknowledgement reaction but no
lifecycle transition in tool-only mode. Candidate evidence should show lifecycle
status reactions running when `messages.statusReactions.enabled` is explicitly
true.
The executable first slice is the opt-in Discord live QA scenario:
```bash
pnpm openclaw qa discord \
--scenario discord-status-reactions-tool-only \
--provider-mode live-frontier \
--model openai/gpt-5.4 \
--alt-model openai/gpt-5.4 \
--fast \
--output-dir .artifacts/qa-e2e/mantis/discord-status-reactions-candidate
```
It configures the SUT with always-on guild handling, `visibleReplies:
"message_tool"`, `ackReaction: "👀"`, and explicit status reactions. The oracle
polls the real Discord triggering message and expects the observed sequence
`👀 -> 🤔 -> 👍`. Artifacts include `discord-qa-reaction-timelines.json`,
`discord-status-reactions-tool-only-timeline.html`, and
`discord-status-reactions-tool-only-timeline.png`.
## Existing QA Pieces
Mantis should build on the existing private QA stack instead of starting from
zero:
- `pnpm openclaw qa discord` already runs a live Discord lane with driver and
SUT bots.
- The live transport runner already writes reports and observed-message
artifacts under `.artifacts/qa-e2e/`.
- Convex credential leases already provide exclusive access to shared live
transport credentials.
- The browser control service already supports screenshots, snapshots,
headless managed profiles, and remote CDP profiles.
- QA Lab already has a debugger UI and bus for transport-shaped testing.
The first Mantis implementation can be a thin before/after runner over these
pieces, plus one visual evidence layer.
## Evidence Model
Every run writes a stable artifact directory:
```text
.artifacts/qa-e2e/mantis/<run-id>/
mantis-report.md
mantis-summary.json
baseline/
summary.json
discord-message.json
screenshot-message-row.png
gateway-debug/
candidate/
summary.json
discord-message.json
screenshot-message-row.png
gateway-debug/
comparison.json
run.log
```
`mantis-summary.json` should be the machine-readable source of truth. The
Markdown report is for PR comments and human review.
The summary must include:
- refs and SHAs tested
- transport and scenario id
- machine provider and machine id or lease id
- credential source without secret values
- baseline result
- candidate result
- whether the bug reproduced on baseline
- whether the candidate fixed it
- artifact paths
- sanitized setup or cleanup issues
Screenshots are evidence, not secrets. They still need redaction discipline:
private channel names, user names, or message content may appear. For public PRs,
prefer GitHub Actions artifact links over inline images until the redaction story
is stronger.
## Browser And VNC
The browser lane has two modes:
- **Headless automation**: default for CI. Chrome runs with CDP enabled, and
Playwright or OpenClaw browser control captures screenshots.
- **VNC rescue**: enabled on the same VM when login, MFA, Discord anti-automation,
or visual debugging needs a human.
The Discord observer browser profile should be persistent enough to avoid
logging in for every run, but isolated from personal browser state. A profile
belongs to the Mantis machine pool, not to a developer laptop.
When Mantis gets stuck, it posts a Discord status message with:
- run id
- scenario id
- machine provider
- artifact directory
- VNC or noVNC connection instructions if available
- short blocker text
The first private deployment can post these messages to the existing operator
channel and move to a dedicated Mantis channel later.
## Machines
Mantis should prefer AWS through Crabbox for the first remote implementation.
Crabbox gives us warmed machines, lease tracking, hydration, logs, results, and
cleanup. If AWS capacity is too slow or unavailable, add a Hetzner provider
behind the same machine interface.
Minimum VM requirements:
- Linux with a desktop-capable Chrome or Chromium install
- CDP access for browser automation
- VNC or noVNC for rescue
- Node 22 and pnpm
- OpenClaw checkout and dependency cache
- Playwright Chromium browser cache when Playwright is used
- enough CPU and memory for one OpenClaw Gateway, one browser, and one model run
- outbound access to Discord, GitHub, model providers, and the credential broker
The VM should not keep long-lived raw secrets outside the expected credential or
browser profile stores.
## Secrets
Secrets live in GitHub organization or repository secrets for remote runs, and in
a local operator-controlled secret file for local runs.
Recommended secret names:
- `OPENCLAW_QA_DISCORD_MANTIS_BOT_TOKEN`
- `OPENCLAW_QA_DISCORD_DRIVER_BOT_TOKEN`
- `OPENCLAW_QA_DISCORD_SUT_BOT_TOKEN`
- `OPENCLAW_QA_DISCORD_GUILD_ID`
- `OPENCLAW_QA_DISCORD_CHANNEL_ID`
- `OPENCLAW_QA_DISCORD_NOTIFY_CHANNEL_ID`
- `OPENCLAW_QA_REDACT_PUBLIC_METADATA=1` for public GitHub artifact uploads
- `OPENCLAW_QA_CONVEX_SITE_URL`
- `OPENCLAW_QA_CONVEX_SECRET_CI`
- `OPENCLAW_QA_MANTIS_CRABBOX_COORDINATOR`
- `OPENCLAW_QA_MANTIS_CRABBOX_COORDINATOR_TOKEN`
Long term, the Convex credential pool should remain the normal source for live
transport credentials. GitHub secrets bootstrap the broker and fallback lanes.
The Discord status-reactions workflow maps the Mantis Crabbox secrets back to
the `CRABBOX_COORDINATOR` and `CRABBOX_COORDINATOR_TOKEN` environment variables
that the Crabbox CLI expects. The plain `CRABBOX_*` GitHub secret names remain
accepted as a compatibility fallback.
The Mantis runner must never print:
- Discord bot tokens
- provider API keys
- browser cookies
- auth profile contents
- VNC passwords
- raw credential payloads
Public artifact uploads should also redact Discord target metadata such as bot,
guild, channel, and message ids. The GitHub smoke workflow enables
`OPENCLAW_QA_REDACT_PUBLIC_METADATA=1` for this reason.
If a token is accidentally pasted into an issue, PR, chat, or log, rotate it
after the new secret has been stored.
## GitHub Artifacts And PR Comments
Mantis workflows should upload the full evidence bundle as a short-lived Actions
artifact. When the workflow is run for a bug report or fix PR, it should also
publish the redacted PNG screenshots to the `qa-artifacts` branch and upsert a
comment on that bug or fix PR with inline before/after screenshots. Do not post
the primary proof only on a generic QA automation PR. Raw logs, observed
messages, and other bulky evidence stay in the Actions artifact.
Production workflows should post those comments with the Mantis GitHub App, not
with `github-actions[bot]`. Store the app id and private key as
`MANTIS_GITHUB_APP_ID` and `MANTIS_GITHUB_APP_PRIVATE_KEY` GitHub Actions
secrets. The workflow uses a hidden marker as the upsert key, updates that
comment when the token can edit it, and creates a new Mantis-owned comment when
an older bot-owned marker cannot be edited.
The PR comment should be short and visual:
```md
Mantis Discord Status Reactions QA
Summary: Mantis reran the reported Discord status-reaction bug against the known
bad baseline and the candidate fix. The baseline reproduced the bug, while the
candidate showed the expected queued -> thinking -> done sequence.
- Scenario: `discord-status-reactions-tool-only`
- Run: <workflow run link>
- Artifact: <artifact link>
- Baseline: `<status>` at `<sha>`
- Candidate: `<status>` at `<sha>`
| Baseline | Candidate |
| ------------------- | ------------------- |
| <inline screenshot> | <inline screenshot> |
```
When the run fails because the harness failed, the comment must say that instead
of implying the candidate failed.
## Private Deployment Notes
A private deployment may already have a Mantis Discord application. Reuse that
application instead of creating another app when it has the right bot
permissions and can be safely rotated.
Set the initial operator notification channel through secrets or deployment
configuration. It can point at an existing maintainer or operations channel
first, then move to a dedicated Mantis channel once one exists.
Do not put guild ids, channel ids, bot tokens, browser cookies, or VNC passwords
in this document. Store them in GitHub secrets, the credential broker, or the
operator's local secret store.
## Adding A Scenario
A Mantis scenario should declare:
- id and title
- transport
- required credentials
- baseline ref policy
- candidate ref policy
- OpenClaw config patch
- setup steps
- stimulus
- expected baseline oracle
- expected candidate oracle
- visual capture targets
- timeout budget
- cleanup steps
Scenarios should prefer small, typed oracles:
- Discord reaction state for reaction bugs
- Discord message references for threading bugs
- Slack thread ts and reaction API state for Slack bugs
- email message ids and headers for email bugs
- browser screenshots when UI is the only reliable observable
Vision checks should be additive. If a platform API can prove the bug, use the
API as the pass/fail oracle and keep screenshots for human confidence.
## Provider Expansion
After Discord, the same runner can add:
- Slack: reactions, threads, app mentions, modals, file uploads.
- Email: Gmail auth and message threading using `gog` where connectors are not
enough.
- WhatsApp: QR login, re-identification, message delivery, media, reactions.
- Telegram: group mention gating, commands, reactions where available.
- Matrix: encrypted rooms, thread or reply relations, restart resume.
Each transport should have one cheap smoke scenario and one or more bug-class
scenarios. Expensive visual scenarios should stay opt-in.
## Open Questions
- Which Discord bot should be the driver, and which should be the SUT, when the
existing Mantis bot is reused?
- Should the observer browser login use a human Discord account, a test account,
or only bot-readable REST evidence for the first phase?
- How long should GitHub retain Mantis artifacts for PRs?
- When should ClawSweeper automatically recommend Mantis instead of waiting for a
maintainer command?
- Should screenshots be redacted or cropped before upload for public PRs?

View File

@@ -127,7 +127,10 @@ when `memorySearch.local.modelPath` points to an existing local file.
may miss changes in rare edge cases.
**sqlite-vec not loading?** OpenClaw falls back to in-process cosine similarity
automatically. Check logs for the specific load error.
automatically. `openclaw memory status --deep` reports the local vector store
separately from the embedding provider, so `Vector store: unavailable` points
at sqlite-vec loading while `Embeddings: unavailable` points at provider/auth
or model readiness. Check logs for the specific load error.
## Configuration

View File

@@ -166,7 +166,7 @@ OpenClaw can expose or hide model reasoning:
- `/reasoning on|off|stream` controls visibility.
- Reasoning content still counts toward token usage when produced by the model.
- Telegram supports reasoning stream into the draft bubble.
- Telegram supports reasoning stream into a transient draft bubble that is deleted after final delivery; use `/reasoning on` for persistent reasoning output.
Details: [Thinking + reasoning directives](/tools/thinking) and [Token use](/reference/token-use).

View File

@@ -688,6 +688,7 @@ Example (OpenAIcompatible):
- For OpenAI-compatible Completions proxies that need vendor-specific fields, set `agents.defaults.models["provider/model"].params.extra_body` (or `extraBody`) to merge extra JSON into the outbound request body.
- For vLLM chat-template controls, set `agents.defaults.models["provider/model"].params.chat_template_kwargs`. The bundled vLLM plugin automatically sends `enable_thinking: false` and `force_nonempty_content: true` for `vllm/nemotron-3-*` when the session thinking level is off.
- For slow local models or remote LAN/tailnet hosts, set `models.providers.<id>.timeoutSeconds`. This extends provider model HTTP request handling, including connect, headers, body streaming, and the total guarded-fetch abort, without increasing the whole agent runtime timeout.
- Model provider HTTP calls allow Surge, Clash, and sing-box fake-IP DNS answers in `198.18.0.0/15` and `fc00::/7` only for the configured provider `baseUrl` hostname. Other private, loopback, link-local, and metadata destinations still require an explicit `models.providers.<id>.request.allowPrivateNetwork: true` opt-in.
- If `baseUrl` is empty/omitted, OpenClaw keeps the default OpenAI behavior (which resolves to `api.openai.com`).
- For safety, an explicit `compat.supportsDeveloperRole: true` is still overridden on non-native `openai-completions` endpoints.
- For `api: "anthropic-messages"` on non-direct endpoints (any provider other than canonical `anthropic`, or a custom `models.providers.anthropic.baseUrl` whose host is not a public `api.anthropic.com` endpoint), OpenClaw suppresses implicit Anthropic beta headers such as `claude-code-20250219`, `interleaved-thinking-2025-05-14`, and OAuth markers, so custom Anthropic-compatible proxies do not reject unsupported beta flags. Set `models.providers.<id>.headers["anthropic-beta"]` explicitly if your proxy needs specific beta features.

View File

@@ -0,0 +1,342 @@
---
summary: "Progress drafts: one visible work-in-progress message that updates while an agent runs"
read_when:
- Configuring visible progress updates for long-running chat turns
- Choosing between partial, block, and progress streaming modes
- Explaining how OpenClaw updates one channel message while work is in progress
- Troubleshooting progress drafts, standalone progress messages, or finalization fallback
title: "Progress drafts"
---
Progress drafts make long-running agent turns feel alive in chat without turning
the conversation into a stack of temporary status replies.
When progress drafts are enabled, OpenClaw creates one visible work-in-progress
message only after the turn proves it is doing real work, updates it while the
agent reads, plans, calls tools, or waits for approval, and then turns that draft
into the final answer when the channel can do that safely.
```text
Shelling...
📖 Read: from docs/concepts/progress-drafts.md
🔎 Web Search: for "discord edit message"
🛠️ Exec: run tests
```
Use progress drafts when you want one tidy status message during tool-heavy work
and the final answer when the turn is done.
## Quick Start
Enable progress drafts per channel with `streaming.mode: "progress"`:
```json5
{
channels: {
discord: {
streaming: {
mode: "progress",
},
},
},
}
```
That is usually enough. OpenClaw will pick an automatic one-word label, wait
until work lasts at least five seconds or emits a second work event, add compact
progress lines while useful work happens, and suppress duplicate standalone
progress chatter for that turn.
## What Users See
A progress draft has two parts:
| Part | Purpose |
| -------------- | --------------------------------------------------------------------------- |
| Label | A short title such as `Thinking...` or `Shelling...`. |
| Progress lines | Compact run updates using the same tool labels and icons as verbose output. |
The label appears after the agent starts meaningful work and either remains busy
for five seconds or emits a second work event. Plain text-only replies do not
show a progress draft. Progress lines are added only when the agent emits useful
work updates, for example `🛠️ Exec`, `🔎 Web Search`, or `✍️ Write: to /tmp/file`.
By default they use the same compact explain mode as `/verbose`; set
`agents.defaults.toolProgressDetail: "raw"` when debugging and you also want raw
commands/details appended.
The final answer replaces the draft when possible; otherwise
OpenClaw sends the final answer normally and cleans up or stops updating the
draft according to the channel's transport.
## Choose A Mode
`channels.<channel>.streaming.mode` controls the visible in-progress behavior:
| Mode | Best for | What appears in chat |
| ---------- | -------------------------------- | ------------------------------------------------- |
| `off` | Quiet channels | Only the final answer. |
| `partial` | Watching answer text appear | One draft edited with the latest answer text. |
| `block` | Larger answer-preview chunks | One preview updated or appended in bigger chunks. |
| `progress` | Tool-heavy or long-running turns | One status draft, then the final answer. |
Choose `progress` when users care more about "what is happening" than watching
the answer text stream token by token.
Choose `partial` when the answer itself is the progress signal.
Choose `block` when you want draft preview updates in larger text chunks. On
Discord and Telegram, `streaming.mode: "block"` is still preview streaming, not
normal block delivery. Use `streaming.block.enabled` or legacy
`blockStreaming` when you want normal block replies.
## Configure Labels
Progress labels live under `channels.<channel>.streaming.progress`.
The default label is `auto`, which chooses from OpenClaw's built-in
single-word-with-ellipsis label pool:
```text
Thinking...
Shelling...
Scuttling...
Clawing...
Pinching...
Molting...
Bubbling...
Tiding...
Reefing...
Cracking...
Sifting...
Brining...
Nautiling...
Krilling...
Barnacling...
Lobstering...
Tidepooling...
Pearling...
Snapping...
Surfacing...
```
Use a fixed label:
```json5
{
channels: {
discord: {
streaming: {
mode: "progress",
progress: {
label: "Investigating",
},
},
},
},
}
```
Use your own automatic label pool:
```json5
{
channels: {
discord: {
streaming: {
mode: "progress",
progress: {
label: "auto",
labels: ["Checking", "Reading", "Testing", "Finishing"],
},
},
},
},
}
```
Hide the label and show only progress lines:
```json5
{
channels: {
discord: {
streaming: {
mode: "progress",
progress: {
label: false,
},
},
},
},
}
```
## Control Progress Lines
Progress lines are enabled by default in progress mode. They come from real run
events: tool starts, item updates, task plans, approvals, command output, patch
summaries, and similar agent activity.
OpenClaw uses the same formatter for progress drafts and `/verbose`:
```json5
{
agents: {
defaults: {
toolProgressDetail: "explain", // explain | raw
},
},
}
```
`"explain"` is the default and keeps drafts stable with concise labels like
`🛠️ Exec: check JS syntax for /tmp/app.js`. `"raw"` appends the underlying
command/detail when available, which is useful while debugging but noisier in
chat.
For example, the same command appears differently depending on the detail mode:
| Mode | Progress line |
| --------- | -------------------------------------------------------------------- |
| `explain` | `🛠️ Exec: check JS syntax for /tmp/app.js` |
| `raw` | `🛠️ Exec: check JS syntax for /tmp/app.js, node --check /tmp/app.js` |
Limit how many lines stay visible:
```json5
{
channels: {
discord: {
streaming: {
mode: "progress",
progress: {
maxLines: 4,
},
},
},
},
}
```
Progress lines are compacted automatically to reduce chat-bubble reflow while the draft is edited.
OpenClaw truncates long progress lines by default so repeated draft edits do not
wrap differently on every update. The prefix stays readable, and long details
such as paths or raw commands are shortened with an ellipsis.
Slack can render progress lines as structured Block Kit fields instead of a
single text body:
```json5
{
channels: {
slack: {
streaming: {
mode: "progress",
progress: {
render: "rich",
},
},
},
},
}
```
Rich rendering keeps the same plain-text fallback so channels and clients that
do not support the richer shape can still show the compact progress text.
Keep the single progress draft but hide tool and task lines:
```json5
{
channels: {
discord: {
streaming: {
mode: "progress",
progress: {
toolProgress: false,
},
},
},
},
}
```
With `toolProgress: false`, OpenClaw still suppresses the older standalone
tool-progress messages for that turn. The channel stays visually quiet until the
final answer, except for the label if one is configured.
## Channel Behavior
Each channel uses the cleanest transport it supports:
| Channel | Progress transport | Notes |
| --------------- | -------------------------------------- | --------------------------------------------------------------------- |
| Discord | Send one message, then edit it. | Final text edits in place when it fits one safe preview message. |
| Matrix | Send one event, then edit it. | Account-level streaming config controls account-level drafts. |
| Microsoft Teams | Native Teams stream in personal chats. | `streaming.mode: "block"` maps to Teams block delivery. |
| Slack | Native stream or editable draft post. | Thread availability affects whether native streaming can be used. |
| Telegram | Send one message, then edit it. | Older visible drafts may be replaced so final timestamps stay useful. |
| Mattermost | Editable draft post. | Tool activity is folded into the same draft-style post. |
Channels without safe edit support usually fall back to typing indicators or
final-only delivery.
## Finalization
When the final answer is ready, OpenClaw tries to keep the chat clean:
- If the draft can safely become the final answer, OpenClaw edits it in place.
- If the channel uses native progress streaming, OpenClaw finalizes that stream
when the native transport accepts the final text.
- If the final answer has media, an approval prompt, an explicit reply target,
too many chunks, or a failed edit/send, OpenClaw sends the final answer through
the normal channel delivery path.
The fallback path is intentional. It is better to send a fresh final answer than
to lose text, mis-thread a reply, or overwrite a draft with a payload the channel
cannot represent safely.
## Troubleshooting
**I only see the final answer.**
Check that `channels.<channel>.streaming.mode` is set to `progress` for the
account or channel that handled the message. Some group or quote-reply paths may
disable draft previews for a turn when the channel cannot safely edit the right
message.
**I see the label but no tool lines.**
Check `streaming.progress.toolProgress`. If it is `false`, OpenClaw keeps the
single draft behavior but hides tool and task progress lines.
**I see a fresh final message instead of an edited draft.**
That is a safety fallback. It can happen for media replies, long answers,
explicit reply targets, old Telegram drafts, missing Slack thread targets,
deleted preview messages, or failed native stream finalization.
**I still see standalone progress messages.**
Progress mode suppresses default standalone tool-progress messages when a draft
is active. If standalone messages still appear, verify that the turn is actually
using progress mode and not `streaming.mode: "off"` or a channel path that
cannot create a draft for that message.
**Teams behaves differently from Discord or Telegram.**
Microsoft Teams uses a native stream in personal chats instead of the generic
send-and-edit preview transport. Teams also treats `streaming.mode: "block"` as
Teams block delivery because it does not have the same draft-preview block mode
used by Discord and Telegram.
## Related
- [Streaming and chunking](/concepts/streaming)
- [Messages](/concepts/messages)
- [Channel configuration](/gateway/config-channels)
- [Discord](/channels/discord)
- [Matrix](/channels/matrix)
- [Microsoft Teams](/channels/msteams)
- [Slack](/channels/slack)
- [Telegram](/channels/telegram)

View File

@@ -21,30 +21,34 @@ Current pieces:
drive a real channel inside a child QA gateway.
- `qa/`: repo-backed seed assets for the kickoff task and baseline QA
scenarios.
- [Mantis](/concepts/mantis): before and after live verification for bugs that
need real transports, browser screenshots, VM state, and PR evidence.
## Command surface
Every QA flow runs under `pnpm openclaw qa <subcommand>`. Many have `pnpm qa:*`
script aliases; both forms are supported.
| Command | Purpose |
| --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `qa run` | Bundled QA self-check; writes a Markdown report. |
| `qa suite` | Run repo-backed scenarios against the QA gateway lane. Aliases: `pnpm openclaw qa suite --runner multipass` for a disposable Linux VM. |
| `qa coverage` | Print the markdown scenario-coverage inventory (`--json` for machine output). |
| `qa parity-report` | Compare two `qa-suite-summary.json` files and write the agentic parity report. |
| `qa character-eval` | Run the character QA scenario across multiple live models with a judged report. See [Reporting](#reporting). |
| `qa manual` | Run a one-off prompt against the selected provider/model lane. |
| `qa ui` | Start the QA debugger UI and local QA bus (alias: `pnpm qa:lab:ui`). |
| `qa docker-build-image` | Build the prebaked QA Docker image. |
| `qa docker-scaffold` | Write a docker-compose scaffold for the QA dashboard + gateway lane. |
| `qa up` | Build the QA site, start the Docker-backed stack, print the URL (alias: `pnpm qa:lab:up`; `:fast` variant adds `--use-prebuilt-image --bind-ui-dist --skip-ui-build`). |
| `qa aimock` | Start only the AIMock provider server. |
| `qa mock-openai` | Start only the scenario-aware `mock-openai` provider server. |
| `qa credentials doctor` / `add` / `list` / `remove` | Manage the shared Convex credential pool. |
| `qa matrix` | Live transport lane against a disposable Tuwunel homeserver. See [Matrix QA](/concepts/qa-matrix). |
| `qa telegram` | Live transport lane against a real private Telegram group. |
| `qa discord` | Live transport lane against a real private Discord guild channel. |
| Command | Purpose |
| --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `qa run` | Bundled QA self-check; writes a Markdown report. |
| `qa suite` | Run repo-backed scenarios against the QA gateway lane. Aliases: `pnpm openclaw qa suite --runner multipass` for a disposable Linux VM. |
| `qa coverage` | Print the markdown scenario-coverage inventory (`--json` for machine output). |
| `qa parity-report` | Compare two `qa-suite-summary.json` files and write the agentic parity report. |
| `qa character-eval` | Run the character QA scenario across multiple live models with a judged report. See [Reporting](#reporting). |
| `qa manual` | Run a one-off prompt against the selected provider/model lane. |
| `qa ui` | Start the QA debugger UI and local QA bus (alias: `pnpm qa:lab:ui`). |
| `qa docker-build-image` | Build the prebaked QA Docker image. |
| `qa docker-scaffold` | Write a docker-compose scaffold for the QA dashboard + gateway lane. |
| `qa up` | Build the QA site, start the Docker-backed stack, print the URL (alias: `pnpm qa:lab:up`; `:fast` variant adds `--use-prebuilt-image --bind-ui-dist --skip-ui-build`). |
| `qa aimock` | Start only the AIMock provider server. |
| `qa mock-openai` | Start only the scenario-aware `mock-openai` provider server. |
| `qa credentials doctor` / `add` / `list` / `remove` | Manage the shared Convex credential pool. |
| `qa matrix` | Live transport lane against a disposable Tuwunel homeserver. See [Matrix QA](/concepts/qa-matrix). |
| `qa telegram` | Live transport lane against a real private Telegram group. |
| `qa discord` | Live transport lane against a real private Discord guild channel. |
| `qa slack` | Live transport lane against a real private Slack channel. |
| `qa mantis` | Before and after verification runner for live transport bugs, with Discord status-reactions evidence, Crabbox desktop/browser smoke, and Slack-in-VNC smoke. See [Mantis](/concepts/mantis). |
## Operator flow
@@ -107,14 +111,32 @@ pnpm openclaw qa matrix --profile fast --fail-fast
The full CLI reference, profile/scenario catalog, env vars, and artifact layout for this lane live in [Matrix QA](/concepts/qa-matrix). At a glance: it provisions a disposable Tuwunel homeserver in Docker, registers temporary driver/SUT/observer users, runs the real Matrix plugin inside a child QA gateway scoped to that transport (no `qa-channel`), then writes a Markdown report, JSON summary, observed-events artifact, and combined output log under `.artifacts/qa-e2e/matrix-<timestamp>/`.
For transport-real Telegram and Discord smoke lanes:
For transport-real Telegram, Discord, and Slack smoke lanes:
```bash
pnpm openclaw qa telegram
pnpm openclaw qa discord
pnpm openclaw qa slack
```
Both target a pre-existing real channel with two bots (driver + SUT). Required env vars, scenario lists, output artifacts, and the Convex credential pool are documented in [Telegram and Discord QA reference](#telegram-and-discord-qa-reference) below.
They target a pre-existing real channel with two bots (driver + SUT). Required env vars, scenario lists, output artifacts, and the Convex credential pool are documented in [Telegram, Discord, and Slack QA reference](#telegram-discord-and-slack-qa-reference) below.
For a full Slack desktop VM run with VNC rescue, run:
```bash
pnpm openclaw qa mantis slack-desktop-smoke \
--gateway-setup \
--scenario slack-canary \
--keep-lease
```
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
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.
Before using pooled live credentials, run:
@@ -133,6 +155,7 @@ Live transport lanes share one contract instead of each inventing their own scen
| Matrix | x | x | x | x | x | x | x | x | x | | |
| Telegram | x | x | x | | | | | | | x | |
| Discord | x | x | x | | | | | | | | x |
| Slack | x | x | x | | | | | | | | |
This keeps `qa-channel` as the broad product-behavior suite while Matrix,
Telegram, and future live transports share one explicit transport-contract
@@ -159,27 +182,27 @@ guest: env-based provider keys, the QA live provider config path, and
`CODEX_HOME` when present. Keep `--output-dir` under the repo root so the guest
can write back through the mounted workspace.
## Telegram and Discord QA reference
## Telegram, Discord, and Slack QA reference
Matrix has a [dedicated page](/concepts/qa-matrix) because of its scenario count and Docker-backed homeserver provisioning. Telegram and Discord are smaller — a handful of scenarios each, no profile system, against pre-existing real channels — so their reference lives here.
Matrix has a [dedicated page](/concepts/qa-matrix) because of its scenario count and Docker-backed homeserver provisioning. Telegram, Discord, and Slack are smaller — a handful of scenarios each, no profile system, against pre-existing real channels — so their reference lives here.
### Shared CLI flags
Both lanes register through `extensions/qa-lab/src/live-transports/shared/live-transport-cli.ts` and accept the same flags:
These lanes register through `extensions/qa-lab/src/live-transports/shared/live-transport-cli.ts` and accept the same flags:
| Flag | Default | Description |
| ------------------------------------- | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| `--scenario <id>` | — | Run only this scenario. Repeatable. |
| `--output-dir <path>` | `<repo>/.artifacts/qa-e2e/{telegram,discord}-<timestamp>` | Where reports/summary/observed messages and the output log are written. Relative paths resolve against `--repo-root`. |
| `--repo-root <path>` | `process.cwd()` | Repository root when invoking from a neutral cwd. |
| `--sut-account <id>` | `sut` | Temporary account id inside the QA gateway config. |
| `--provider-mode <mode>` | `live-frontier` | `mock-openai` or `live-frontier` (legacy `live-openai` still works). |
| `--model <ref>` / `--alt-model <ref>` | provider default | Primary/alternate model refs. |
| `--fast` | off | Provider fast mode where supported. |
| `--credential-source <env\|convex>` | `env` | See [Convex credential pool](#convex-credential-pool). |
| `--credential-role <maintainer\|ci>` | `ci` in CI, `maintainer` otherwise | Role used when `--credential-source convex`. |
| Flag | Default | Description |
| ------------------------------------- | --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| `--scenario <id>` | — | Run only this scenario. Repeatable. |
| `--output-dir <path>` | `<repo>/.artifacts/qa-e2e/{telegram,discord,slack}-<timestamp>` | Where reports/summary/observed messages and the output log are written. Relative paths resolve against `--repo-root`. |
| `--repo-root <path>` | `process.cwd()` | Repository root when invoking from a neutral cwd. |
| `--sut-account <id>` | `sut` | Temporary account id inside the QA gateway config. |
| `--provider-mode <mode>` | `live-frontier` | `mock-openai` or `live-frontier` (legacy `live-openai` still works). |
| `--model <ref>` / `--alt-model <ref>` | provider default | Primary/alternate model refs. |
| `--fast` | off | Provider fast mode where supported. |
| `--credential-source <env\|convex>` | `env` | See [Convex credential pool](#convex-credential-pool). |
| `--credential-role <maintainer\|ci>` | `ci` in CI, `maintainer` otherwise | Role used when `--credential-source convex`. |
Both exit non-zero on any failed scenario. `--allow-failures` writes artifacts without setting a failing exit code.
Each lane exits non-zero on any failed scenario. `--allow-failures` writes artifacts without setting a failing exit code.
### Telegram QA
@@ -222,7 +245,7 @@ Output artifacts:
pnpm openclaw qa discord
```
Targets one real private Discord guild channel with two bots: a driver bot controlled by the harness and a SUT bot started by the child OpenClaw gateway through the bundled Discord plugin. Verifies channel mention handling and that the SUT bot has registered the native `/help` command with Discord.
Targets one real private Discord guild channel with two bots: a driver bot controlled by the harness and a SUT bot started by the child OpenClaw gateway through the bundled Discord plugin. Verifies channel mention handling, that the SUT bot has registered the native `/help` command with Discord, and opt-in Mantis evidence scenarios.
Required env when `--credential-source env`:
@@ -241,16 +264,59 @@ 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.
Run the Mantis status-reaction scenario explicitly:
```bash
pnpm openclaw qa discord \
--scenario discord-status-reactions-tool-only \
--provider-mode live-frontier \
--model openai/gpt-5.4 \
--alt-model openai/gpt-5.4 \
--fast
```
Output artifacts:
- `discord-qa-report.md`
- `discord-qa-summary.json`
- `discord-qa-observed-messages.json` — bodies redacted unless `OPENCLAW_QA_DISCORD_CAPTURE_CONTENT=1`.
- `discord-qa-reaction-timelines.json` and `discord-status-reactions-tool-only-timeline.png` when the status-reaction scenario runs.
### Slack QA
```bash
pnpm openclaw qa slack
```
Targets one real private Slack channel with two distinct bots: a driver bot controlled by the harness and a SUT bot started by the child OpenClaw gateway through the bundled Slack plugin.
Required env when `--credential-source env`:
- `OPENCLAW_QA_SLACK_CHANNEL_ID`
- `OPENCLAW_QA_SLACK_DRIVER_BOT_TOKEN`
- `OPENCLAW_QA_SLACK_SUT_BOT_TOKEN`
- `OPENCLAW_QA_SLACK_SUT_APP_TOKEN`
Optional:
- `OPENCLAW_QA_SLACK_CAPTURE_CONTENT=1` keeps message bodies in observed-message artifacts.
Scenarios (`extensions/qa-lab/src/live-transports/slack/slack-live.runtime.ts:39`):
- `slack-canary`
- `slack-mention-gating`
Output artifacts:
- `slack-qa-report.md`
- `slack-qa-summary.json`
- `slack-qa-observed-messages.json` — bodies redacted unless `OPENCLAW_QA_SLACK_CAPTURE_CONTENT=1`.
### Convex credential pool
Both Telegram and Discord 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"` and `"discord"`.
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"`.
Payload shapes the broker validates on `admin/add`:

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