Compare commits

..

192 Commits

Author SHA1 Message Date
Peter Steinberger
85e6e3edbb docs: document stateful target driver contracts 2026-06-02 00:09:27 -04:00
Peter Steinberger
1e3b2b24fb docs: document binding target contracts 2026-06-02 00:07:41 -04:00
Peter Steinberger
b0e8418862 docs: document configured binding compiler contracts 2026-06-02 00:05:41 -04:00
Peter Steinberger
e842363667 docs: document configured binding registry contracts 2026-06-02 00:04:09 -04:00
Peter Steinberger
b72baa7727 docs: document configured binding match contracts 2026-06-02 00:02:05 -04:00
Peter Steinberger
74990d4fed docs: document account lookup contracts 2026-06-02 00:00:38 -04:00
Peter Steinberger
684e827346 docs: document account helper contracts 2026-06-01 23:58:41 -04:00
Peter Steinberger
fc9e3b7b77 docs: document account fallback contract 2026-06-01 23:56:54 -04:00
Peter Steinberger
d1c653406e docs: document request URL fallback contract 2026-06-01 23:54:51 -04:00
Peter Steinberger
e24035d9a4 docs: document webhook numeric option contract 2026-06-01 23:53:43 -04:00
Peter Steinberger
58f60ea247 docs: document webhook memory guard contracts 2026-06-01 23:52:06 -04:00
Peter Steinberger
1ac45d0889 docs: document webhook request guard contracts 2026-06-01 23:51:03 -04:00
Peter Steinberger
0b0ae3117d docs: move webhook path docs to APIs 2026-06-01 23:49:12 -04:00
Peter Steinberger
decc930303 docs: document webhook target contracts 2026-06-01 23:48:12 -04:00
Peter Steinberger
60a5e8e720 docs: document custom command config contracts 2026-06-01 23:46:12 -04:00
Peter Steinberger
4f3f7150fa docs: document telegram command config contracts 2026-06-01 23:44:55 -04:00
Peter Steinberger
a5b2b42721 docs: document runtime store contracts 2026-06-01 23:44:08 -04:00
Peter Steinberger
025da47025 docs: document file lock contracts 2026-06-01 23:44:08 -04:00
Peter Steinberger
4e5366aa91 docs: document fetch runtime contracts 2026-06-01 23:44:08 -04:00
Peter Steinberger
7b8a805ac6 docs: document lazy value contracts 2026-06-01 23:44:08 -04:00
Peter Steinberger
1abf74258e docs: document fetch auth contracts 2026-06-01 23:44:08 -04:00
Peter Steinberger
6b5ab53b71 docs: document keyed async queue contracts 2026-06-01 23:44:08 -04:00
Peter Steinberger
816ac0ee1e docs: document channel entry loader contracts 2026-06-01 23:44:08 -04:00
Peter Steinberger
e20e9adda2 docs: document delivery queue contracts 2026-06-01 23:44:08 -04:00
Peter Steinberger
4b1ce400ee docs: document channel pairing contracts 2026-06-01 23:44:08 -04:00
Peter Steinberger
5ff221ba30 docs: document channel setup contracts 2026-06-01 23:44:08 -04:00
Peter Steinberger
c26b660403 docs: document channel policy contracts 2026-06-01 23:44:08 -04:00
Peter Steinberger
d6dfa06981 docs: document approval renderer contracts 2026-06-01 23:44:08 -04:00
Peter Steinberger
8d19ee6e0d docs: document approval client contracts 2026-06-01 23:44:08 -04:00
Peter Steinberger
cd2e5bc6cf docs: document allow from contracts 2026-06-01 23:44:08 -04:00
Peter Steinberger
2aba860546 docs: document reply payload contracts 2026-06-01 23:44:08 -04:00
Peter Steinberger
fd114b9998 docs: document provider oauth contracts 2026-06-01 23:44:08 -04:00
Peter Steinberger
a4c74e2ec1 docs: document text chunking contracts 2026-06-01 23:44:08 -04:00
Peter Steinberger
c13ed2868a docs: document channel config helper contracts 2026-06-01 23:44:08 -04:00
Peter Steinberger
9d88eddae8 docs: document status helper contracts 2026-06-01 23:44:08 -04:00
Peter Steinberger
681491f6fe docs: document provider selection contracts 2026-06-01 23:44:08 -04:00
Peter Steinberger
0617de9dda docs: document access group allowlist contracts 2026-06-01 23:44:08 -04:00
Peter Steinberger
f1b1a12fbc docs: document allowlist config edit contracts 2026-06-01 23:44:07 -04:00
Peter Steinberger
48a9b32494 docs: document approval delivery contracts 2026-06-01 23:44:07 -04:00
Peter Steinberger
be16e6f7ac docs: document native approval route contracts 2026-06-01 23:44:07 -04:00
Peter Steinberger
8d5a385a21 docs: document approval auth helper contracts 2026-06-01 23:44:07 -04:00
Peter Steinberger
2057cb4c1c docs: document channel send result contracts 2026-06-01 23:44:07 -04:00
Peter Steinberger
fbcc290459 docs: document approval reaction contracts 2026-06-01 23:44:07 -04:00
Peter Steinberger
ac7fa63036 docs: document provider tool schema contracts 2026-06-01 23:44:07 -04:00
Peter Steinberger
cce72ebf27 docs: document provider stream family contracts 2026-06-01 23:44:07 -04:00
Peter Steinberger
42f6d93ed5 docs: document pair loop guard contracts 2026-06-01 23:44:07 -04:00
Peter Steinberger
31be6bc311 docs: document channel route identity contracts 2026-06-01 23:44:07 -04:00
Peter Steinberger
65a190530c docs: document command auth compatibility contracts 2026-06-01 23:44:07 -04:00
Vincent Koc
7c4fb1bd2c refactor: share session search test helpers 2026-06-02 05:42:38 +02:00
Vincent Koc
7d5d62511f fix(e2e): preserve null rpc results 2026-06-02 05:33:07 +02:00
Vincent Koc
cc6a6f5682 refactor: share readiness test fixtures 2026-06-02 05:32:25 +02:00
Vincent Koc
7a8d307bdc refactor: share node invoke approval test helpers 2026-06-02 05:24:23 +02:00
Peter Steinberger
b7d363cadf fix(agents): bypass stale auth for plugin harnesses
Explicit non-Codex plugin harness runtimes now bypass stale OpenClaw provider auth cooldowns before harness startup, while Codex/OpenClaw and missing-harness gates remain fail-closed. Fixes #85105.
2026-06-01 23:22:54 -04:00
Vincent Koc
68b4dd1816 fix(crabbox): serialize macos node bootstrap 2026-06-02 05:21:16 +02:00
Vincent Koc
0e16e72091 refactor: share session reset hook test helpers 2026-06-02 05:16:03 +02:00
Peter Steinberger
9ead0ae921 fix: repair live model inference edge cases
Fix live model inference edge cases across provider streaming, model switching, outbound delivery, and gateway tool resolution.

Includes live/provider issue fixes and leaves #89100 explicitly partial for the remaining FM-2 group routing case.
2026-06-01 23:03:27 -04:00
Vincent Koc
3128ec9858 refactor: share gateway probe test helpers 2026-06-02 04:59:36 +02:00
Vincent Koc
1ec291c682 fix(ios): require explicit gateway log target 2026-06-02 04:52:50 +02:00
Vincent Koc
9d9a6140a3 refactor: share sessions list changed test helpers 2026-06-02 04:48:54 +02:00
Vincent Koc
674bd6fc93 fix(mac): isolate build run logs 2026-06-02 04:47:00 +02:00
Peter Steinberger
b2a55a282a fix(update): do not fail core update on plugin repair fetch 2026-06-02 03:42:54 +01:00
Vincent Koc
3cf4c1ad69 refactor: share connect policy test helpers 2026-06-02 04:38:59 +02:00
Vincent Koc
fa9ce6ea0e fix(mac): isolate dmg resize limits 2026-06-02 04:32:38 +02:00
Vincent Koc
0f1f1a1fd7 refactor: share startup config recovery test helpers 2026-06-02 04:29:40 +02:00
Vincent Koc
d944aaa9ec fix(test): reject retired live shard 2026-06-02 04:20:53 +02:00
Vincent Koc
baade28397 refactor: share subagent delivery context test helpers 2026-06-02 04:20:09 +02:00
Vincent Koc
883c0f1254 fix(mac): scope restart log by worktree 2026-06-02 04:11:23 +02:00
Vincent Koc
793ab78ebb refactor: share cron validation test helpers 2026-06-02 04:08:21 +02:00
Peter Steinberger
57ea5aff81 test(release): expect cheap docker preflight 2026-06-02 03:03:48 +01:00
Vincent Koc
f1d65b3cd6 fix(e2e): isolate trash shim bin dir 2026-06-02 04:01:47 +02:00
Vincent Koc
e6b951a6a6 refactor: share operator approval client test setup 2026-06-02 03:58:27 +02:00
Vincent Koc
55e9194a4c perf(scripts): avoid duplicate build cache input hashing 2026-06-02 03:50:19 +02:00
Vincent Koc
8929838159 refactor: share gateway credentials test fixtures 2026-06-02 03:49:48 +02:00
Peter Steinberger
a355c8897d ci(release): keep docker preflight cheap 2026-06-02 02:48:41 +01:00
Vincent Koc
b06dc17537 refactor: share gateway e2e test setup 2026-06-02 03:40:29 +02:00
Vincent Koc
7967a3582c fix(e2e): isolate onboard gateway logs 2026-06-02 03:39:10 +02:00
Vincent Koc
2e6016fdec fix(ci): keep crabbox pnpm hydrate off tmpfs 2026-06-02 03:38:51 +02:00
Peter Steinberger
8a1a8ea8a3 ci(release): wait out live provider rate limits 2026-06-02 02:38:22 +01:00
Vincent Koc
4608f7dcf9 refactor: share probe auth test fixtures 2026-06-02 03:29:33 +02:00
Vincent Koc
49ac93bda6 refactor: share talk session response helpers 2026-06-02 03:20:00 +02:00
Peter Steinberger
f6653b9b35 fix(ci): retry live Docker image pulls 2026-06-02 02:08:26 +01:00
Vincent Koc
2f92fddef0 refactor: share node invoke wake test helpers 2026-06-02 03:02:03 +02:00
Vincent Koc
489efc8f5e refactor: share device token authz test fixtures 2026-06-02 02:58:38 +02:00
Vincent Koc
459abfc26b fix(e2e): isolate plugin sweep scratch files 2026-06-02 02:50:41 +02:00
Vincent Koc
340cc2c1e4 refactor: share session history test fixtures 2026-06-02 02:41:09 +02:00
Vincent Koc
be8cb5d4ea refactor: share agent wait dedupe test fixtures 2026-06-02 02:37:48 +02:00
Vincent Koc
222ade9fa6 fix(e2e): clean kitchen sink sweep state 2026-06-02 02:29:52 +02:00
Peter Steinberger
6667b9734a fix(ci): avoid rg dependency in changelog gate 2026-06-02 01:29:15 +01:00
Vincent Koc
ebbb2e8f01 refactor: share handshake auth helper test fixtures 2026-06-02 02:20:52 +02:00
Vincent Koc
dea3e835c5 refactor: share channel health policy test fixtures 2026-06-02 02:16:09 +02:00
Peter Steinberger
722af385d2 test(release): accept gateway schema rejection wrapper 2026-06-02 01:10:00 +01:00
Vincent Koc
dacd18a8aa refactor: share chat attachment test helpers 2026-06-02 02:00:15 +02:00
Vincent Koc
8a9acd2940 test(mac): exercise codesign entitlement use 2026-06-02 01:56:24 +02:00
Vincent Koc
bd8353dbaa fix(testing): fail plugin gauntlet on failed qa summaries 2026-06-02 01:52:00 +02:00
Vincent Koc
3baf78dd0a refactor: share node invoke approval test helpers 2026-06-02 01:51:08 +02:00
Vincent Koc
1ed7692d2f test(changelog): exercise attribution gate policy 2026-06-02 01:46:34 +02:00
Omar Shahine
12798eb789 fix(agents): avoid duplicate generated media fallback (#89220)
Treat targetless current-chat message-tool media telemetry as delivered for generated-media completion dedupe while preserving fallback delivery for mismatched provider/account/thread evidence.

Real behavior proof was added from the live iMessage generated-image run: inbound id 5805, exactly one outgoing media reply id 5806, and no follow-up generated-image fallback.

Co-authored-by: omarshahine <10343873+omarshahine@users.noreply.github.com>
Reviewed-by: @lobster
2026-06-01 16:46:14 -07:00
Omar Shahine
02192bd27f fix(imessage): keep typing active during tool work (#88948)
Keep iMessage native typing indicators alive through long tool-running gaps by bridging tool-start activity into the existing typing controller, while preserving typingMode and sendPolicy suppression semantics.

Real behavior proof was added from the live iMessage generated-image run: inbound id 5805, outgoing media reply id 5806, and requester-observed typing during the 84s tool path.

Co-authored-by: omarshahine <10343873+omarshahine@users.noreply.github.com>
Reviewed-by: @lobster
2026-06-01 16:45:46 -07:00
Vincent Koc
086274fd7e test(e2e): exercise onboard wizard exit status 2026-06-02 01:38:46 +02:00
Vincent Koc
ed07a7a2de refactor: share node pairing authz test setup 2026-06-02 01:33:11 +02:00
Vincent Koc
829fb5dcb3 fix(e2e): clean generated docker client state 2026-06-02 01:30:10 +02:00
Peter Steinberger
4c6285e8ff test(release): retry google tool-read failovers 2026-06-02 00:26:55 +01:00
Vincent Koc
7c52969d49 fix(e2e): clean plugin fixture servers on timeout 2026-06-02 01:17:08 +02:00
Vincent Koc
42d3acfc99 refactor: share ios approval push delivery 2026-06-02 01:09:58 +02:00
Vincent Koc
32f98d7fe8 fix(e2e): forward sighup in node watchdogs 2026-06-02 01:05:29 +02:00
Vincent Koc
4bd7421182 refactor: share gateway auth request guards 2026-06-02 00:56:06 +02:00
Vincent Koc
d91d8ff060 refactor: share chat abort test setup 2026-06-02 00:47:43 +02:00
Vincent Koc
af44fb9b6c fix(test): preserve vitest batch wrapper signals 2026-06-02 00:46:55 +02:00
Vincent Koc
45e0545e82 refactor: share gateway shutdown abort helpers 2026-06-02 00:44:16 +02:00
Peter Steinberger
2d17cb295d fix(discord): use libopus structured decode errors 2026-06-01 23:43:31 +01:00
Peter Steinberger
e8120a72e1 ci(release): retry quiet node shard stalls 2026-06-01 23:43:03 +01:00
Dallin Romney
0904f3e553 revert: undo gateway memory watch warning (#89246) 2026-06-01 15:32:42 -07:00
Vincent Koc
2770aa5f4c fix(scripts): clean boundary step process groups 2026-06-02 00:29:22 +02:00
Vincent Koc
285401ced8 refactor: share cli session history test helpers 2026-06-02 00:25:39 +02:00
Vincent Koc
64697fbe24 chore(release): add matrix plugin changelog 2026-06-02 00:23:41 +02:00
Vincent Koc
e9aae26b22 fix(test): clean live wrapper children 2026-06-02 00:19:53 +02:00
Vincent Koc
cb12a9af94 refactor: share node pairing request test helpers 2026-06-02 00:17:07 +02:00
Peter Steinberger
65d7fa2420 fix(memory): reattach Linux watchers on directory rename
(cherry picked from commit 0db7781514cc84fac4f3a999d24b4b747fc871f9)
2026-06-01 23:15:00 +01:00
Peter Steinberger
bd4a7f4119 fix(discord): classify corrupt opus packets structurally 2026-06-01 23:14:23 +01:00
Vincent Koc
14f61d0637 fix(test): clean delegated vitest runners 2026-06-02 00:09:20 +02:00
Vincent Koc
0f3a63b12e refactor: share preauth hardening test helpers 2026-06-02 00:07:53 +02:00
Peter Steinberger
a14eacf372 chore(release): set version 2026.6.2 2026-06-01 23:06:55 +01:00
Colin
646df2da83 fix skill workshop filtered fallback 2026-06-01 23:00:40 +01:00
Colin
211321ce5c address skill workshop review comments 2026-06-01 23:00:40 +01:00
Colin
a34e822cd4 fix skill workshop filtered navigation 2026-06-01 23:00:40 +01:00
Colin
8c180c9153 fix(ui): render skill workshop tab 2026-06-01 23:00:40 +01:00
Vincent Koc
990f0baff9 fix(e2e): scope gateway cleanup to tracked pid 2026-06-01 23:59:03 +02:00
Peter Steinberger
bd8baeb323 perf(gateway): narrow plugin lookup memo key 2026-06-01 22:58:46 +01:00
Vincent Koc
0771bbbd20 refactor: share discovery runtime test setup 2026-06-01 23:50:24 +02:00
Vincent Koc
74cf5c7e7d refactor: share session permission client setup 2026-06-01 23:48:17 +02:00
Vincent Koc
0cfd6b0504 fix(e2e): clean timed-out docker harness containers 2026-06-01 23:45:56 +02:00
Peter Steinberger
4e45010203 ci(release): fail fast on red release children
(cherry picked from commit 8d7038775f0a0a1bb5354ba6b6b708c6b2c3167b)
2026-06-01 22:42:53 +01:00
Vincent Koc
afdf9aaea0 refactor: share talk config test helpers 2026-06-01 23:34:51 +02:00
Vincent Koc
72ed2121f8 fix(scripts): guard delayed docker package kills 2026-06-01 23:33:00 +02:00
Dallin Romney
2405bbcbaf fix(memory): warn on gateway watcher FD risk (#89185)
* fix(memory): default gateway memory watch off

* fix(memory): warn on gateway watcher fd risk

* fix(config): avoid warning helper narrowing

* fix(config): remove redundant warning boolean cast

* docs(memory): clarify watcher default wording

* docs(memory): simplify watcher warning copy

* fix(config): scope watcher warning to local gateway
2026-06-01 14:23:25 -07:00
Vincent Koc
403190572b fix(e2e): isolate release media memory artifacts 2026-06-01 23:19:47 +02:00
Vincent Koc
67983a00c8 refactor: share session reset model test helpers 2026-06-01 23:12:52 +02:00
Vincent Koc
61aa499b53 test(scripts): trap test-state temp homes 2026-06-01 23:09:58 +02:00
Vincent Koc
420450b5cb fix(ci): timeout dependency guard GitHub requests 2026-06-01 22:59:55 +02:00
Kevin Lin
f8491b0fcf enhance(slack): route plugin approvals through native UI
Route Slack plugin approval delivery through the shared native approval route gates while preserving Slack Block Kit buttons and plugin resolver semantics.

Verification: Slack/native approval unit tests, Slack QA Lab, and live clawd native plugin approval via Slack desktop.
2026-06-01 13:55:59 -07:00
Vincent Koc
98e943ebdd refactor: share voicewake model test helpers 2026-06-01 22:53:29 +02:00
Vincent Koc
f8d5f162a1 fix(ui): terminate child on wrapper shutdown 2026-06-01 22:37:25 +02:00
Vincent Koc
a2fdd5bc70 refactor: share session delete lifecycle test helpers 2026-06-01 22:31:06 +02:00
Vincent Koc
2af2111ae0 refactor: share session history test helpers 2026-06-01 22:28:14 +02:00
Vincent Koc
c9d35c7172 fix(scripts): forward run-with-env termination 2026-06-01 22:24:34 +02:00
Dallin Romney
50b69e16dc fix(agents): dispatch auth failures by type (#89181) 2026-06-01 13:23:05 -07:00
Vincent Koc
fe97c6000c refactor: share browser auth test helpers 2026-06-01 22:19:07 +02:00
Dallin Romney
a99cbf29bd test: reset gateway timers at test boundaries (#89212) 2026-06-01 13:13:08 -07:00
Vyctor H. Brzezowski
05ea36a81f docs: refresh ClawHub showcase cards (#88734) 2026-06-01 13:08:56 -07:00
Vincent Koc
eb58c88598 refactor: share model catalog test helpers 2026-06-01 21:58:49 +02:00
Dallin Romney
5a67c5c556 fix(memory-core): reduce Linux watcher fan-out (#89188)
* fix(memory-core): reduce Linux watcher fan-out

* fix(memory-core): satisfy watcher type and lint checks

* fix(memory-core): harden Linux watcher subtree races
2026-06-01 12:54:30 -07:00
NianJiu
5a55135146 fix(memory): retry transient FileProvider-backed reads (#85351) 2026-06-01 12:40:20 -07:00
Vincent Koc
193988bc5b fix(e2e): isolate onboard temp artifacts 2026-06-01 21:25:03 +02:00
Vincent Koc
a20f57bf2e refactor: share startup auth test assertions 2026-06-01 21:16:55 +02:00
Vincent Koc
66f797b22c fix(e2e): wait for plugin update registry cleanup 2026-06-01 21:01:26 +02:00
Vincent Koc
65a805ac28 fix(e2e): harden web search cleanup 2026-06-01 20:35:33 +02:00
Vincent Koc
b18bab0bcc refactor: share session kill http test fixtures 2026-06-01 20:35:08 +02:00
Alexzhu
9ac30b587e Keep machine-readable CLI startup output parseable (#88689)
Constraint: CLI startup progress can render before Commander resolves a command's JSON output contract.

Rejected: Leaving Clack on its default stdout | contaminates JSON stdout when startup progress appears.

Confidence: high

Scope-risk: narrow

Directive: Keep progress output off stdout before full command parsing for machine-readable invocations.

Tested: git diff --check origin/main; OPENCLAW_HEAVY_CHECK_LOCK_SCOPE=worktree OPENCLAW_VITEST_MAX_WORKERS=1 node scripts/run-vitest.mjs src/cli/progress.test.ts src/cli/run-main.exit.test.ts; source CLI sessions --json parse proof.

Not-tested: broad pnpm check.
2026-06-01 11:33:22 -07:00
Peter Steinberger
82de264710 test(release): tolerate MiniMax portal nonce drift 2026-06-01 19:30:46 +01:00
Vincent Koc
7f7f0775ed fix(testing): keep crabbox sync checkouts durable 2026-06-01 20:30:08 +02:00
Vincent Koc
30819ed3da refactor: share http endpoint test scaffolding 2026-06-01 20:25:40 +02:00
Vincent Koc
1c3095e029 test(deps): clean dependency evidence temp roots 2026-06-01 20:20:42 +02:00
Vincent Koc
62cfc613f1 refactor: share startup early test inputs 2026-06-01 20:17:30 +02:00
Dallin Romney
64a946ac21 fix(agents): actionable copy for exhausted auth-profile failover (#85798)
* fix(agents): actionable copy for exhausted auth-profile failover

The pi-embedded runner threw a generic "No available auth profile for
<provider> (all in cooldown or unavailable)" message whenever every
configured profile was in cooldown, even though the failover machinery
had already resolved a concrete reason (auth, billing, rate_limit,
session_expired, etc.). The user-facing copy never used that reason and
never told the user how to recover.

Route the resolved reason through a single presenter
(`formatAuthProfileFailureMessage`) that composes a reason-specific
sentence with `buildProviderAuthRecoveryHint`, so FailoverError.message
ships with the right `openclaw models auth login --provider <id>` hint
when the cause is authentication/session/billing, and falls back to the
underlying provider error text otherwise. Helper moved out of
`src/commands/` into `src/agents/` because `src/agents/` cannot depend
on `src/commands/`.

* fix(agents): soften auth-profile failure copy for non-technical users

* refactor(agents): drop guidance re-export shim and de-brittle failure-copy tests

- Delete `src/commands/provider-auth-guidance.ts` and point doctor-auth, auth-choice.model-check, and models/list.status-command directly at `src/agents/provider-auth-recovery-hint.ts`. The cold-imports test moves with it.
- Rewrite `failure-copy.test.ts` to assert behavior (recovery-hint dispatch, provider mention, cause-suffix dedup) instead of pinning exact long copy strings, so wording tweaks no longer require a test update in two places.
2026-06-01 11:16:25 -07:00
Vincent Koc
96187089d4 refactor: share session history message fixtures 2026-06-01 20:05:18 +02:00
Vincent Koc
965e680603 test(control-ui): clean i18n timeout temp dirs 2026-06-01 20:03:05 +02:00
Vincent Koc
1cf39a2d6f refactor: table-drive lifecycle state tests 2026-06-01 19:57:35 +02:00
Vincent Koc
92b3d52e8a fix(e2e): isolate release media temp files 2026-06-01 19:56:05 +02:00
Dallin Romney
8ba6dfeaf6 fix(ci): restore dist cache before artifact builds (#89169) 2026-06-01 10:55:27 -07:00
Peter Steinberger
bddcf4448c fix(subagents): rotate steered restart sessions 2026-06-01 18:50:36 +01:00
Vincent Koc
c8a67768e3 fix(e2e): require expected web search rejection 2026-06-01 19:49:11 +02:00
Vincent Koc
26e61b2087 refactor: share single-row cache test helpers 2026-06-01 19:48:19 +02:00
Vincent Koc
ee48028028 fix(dev): clean tui pty watch children 2026-06-01 19:40:42 +02:00
Vincent Koc
3c324590ae refactor: share compaction checkpoint test helpers 2026-06-01 19:33:41 +02:00
Vincent Koc
ba88b7a178 fix(e2e): clean plugin lifecycle temp state 2026-06-01 19:27:04 +02:00
Vincent Koc
d767e296e2 refactor: share plugin node auth test helpers 2026-06-01 19:26:59 +02:00
Vincent Koc
83cd3cbe2a fix(e2e): bound bundled plugin lifecycle commands 2026-06-01 19:18:26 +02:00
Vincent Koc
16807824cc refactor: share node invoke approval test helpers 2026-06-01 19:18:14 +02:00
Dallin Romney
e3d24faecd fix: allow admins to approve dependency guard (#88966)
* fix: allow admins to approve dependency guard

* fix: auto-bypass trusted dependency authors
2026-06-01 10:17:14 -07:00
Peter Steinberger
469bec97ef test(codex): keep live subagent smoke lightweight 2026-06-01 18:09:48 +01:00
Vincent Koc
101db565ca refactor: share startup plugin test helpers 2026-06-01 19:09:39 +02:00
Vincent Koc
ef26e8dfce fix(repro): clean webchat tts proof artifacts 2026-06-01 19:04:12 +02:00
Vincent Koc
25c19e013a refactor: share startup memory test helpers 2026-06-01 19:00:26 +02:00
Vincent Koc
f2eea90dac fix(e2e): bound cron mcp probe waits 2026-06-01 18:52:13 +02:00
Vincent Koc
3113fe95ea refactor: share startup secrets test helpers 2026-06-01 18:49:58 +02:00
Vincent Koc
4e1f8b8ac7 fix(e2e): clean timed-out runtime commands 2026-06-01 18:43:25 +02:00
Vincent Koc
0b8f6b81e6 refactor: share probe request dispatch helper 2026-06-01 18:35:38 +02:00
Vincent Koc
ab1042d115 refactor: share talk transcription relay test setup 2026-06-01 18:34:05 +02:00
Peter Steinberger
9153aab037 fix(codex): abort app-server thread startup cleanly 2026-06-01 17:33:00 +01:00
Vincent Koc
285a792aa8 refactor: share maintenance test fixtures 2026-06-01 18:25:54 +02:00
Vincent Koc
a8bc1716dd fix(usage): skip empty timeseries scans 2026-06-01 18:20:52 +02:00
Vincent Koc
373ef81e83 refactor: share codex harness model assertions 2026-06-01 18:12:11 +02:00
1088 changed files with 16311 additions and 15289 deletions

View File

@@ -16,6 +16,10 @@ Use this with `$release-openclaw-maintainer` and `$openclaw-testing` when a rele
- Watch one parent run plus compact child summaries. Avoid broad `gh run view` polling loops; REST quota is easy to burn.
- Fetch logs only for failed or currently-blocking jobs. If quota is low, stop polling and wait for reset.
- Treat live-provider flakes separately from code failures: prove key validity, provider HTTP status, retry evidence, and exact failing lane before editing code.
- Full Release Validation parent monitors fail fast: once a required child job
fails, the parent cancels the remaining child matrix and prints the failed
job summary. Inspect that first red job instead of waiting for unrelated
matrix tails.
## Preflight
@@ -73,6 +77,9 @@ gh workflow run full-release-validation.yml \
```
Use `release_profile=stable` unless the operator explicitly asks for the broad advisory provider/media matrix. Use narrow `rerun_group` after focused fixes.
Publish with `openclaw-release-publish.yml` using `release_profile=from-validation`
unless a maintainer intentionally wants to cross-check a specific profile; the
publish workflow reads the effective profile from the full-validation manifest.
## Watch

View File

@@ -49,17 +49,21 @@ Use this skill for release and publish-time workflow. Load `$release-private` if
the next beta number until the matching npm package has actually published.
If a published beta needs a fix, commit the fix on the release branch and
increment to the next `-beta.N`.
- For a beta release train, run the fast local preflight first, publish the
beta to npm `beta`, then run the expensive published-package roster focused
on install/update/Docker/Parallels/NPM Telegram. If anything fails, fix it on
the release branch, commit/push/pull, increment beta number, and repeat. Run
the full expensive roster at least once before stable/latest promotion; for
later beta attempts, rerun only lanes whose evidence changed unless the fix
touches broad release, install/update, plugin, Docker, Parallels, or live QA
behavior. After each beta is published, scan current `main` once for critical
fixes that landed after the release branch cut and backport only important
low-risk fixes. Operators may authorize up to 4 autonomous beta attempts;
after 4 failed beta attempts, stop and report.
- For a beta release train, keep Full Release Validation as a pre-publish gate
unless the operator explicitly waives it. Run the fast local preflight, npm
preflight, full release validation, and performance in parallel where safe.
If anything fails before npm publish, fix it on the release branch,
forward-port the fix to `main`, move the unpublished beta tag/prerelease to
the fixed commit, and rerun the affected pre-publish gates. If anything fails
after npm publish, fix it, forward-port to `main`, increment beta number, and
repeat. After each beta publish, run the published-package roster focused on
install/update/Docker/Parallels/NPM Telegram. For later beta attempts, rerun
only lanes whose evidence changed unless the fix touches broad release,
install/update, plugin, Docker, Parallels, or live QA behavior. After each
beta is live, scan current `main` once for critical fixes that landed after
the release branch cut and backport only important low-risk fixes. Operators
may authorize up to 4 autonomous beta attempts; after 4 failed beta attempts,
stop and report.
- As soon as the release candidate SHA exists, dispatch `OpenClaw Performance`
with `target_ref=<release-sha>` in parallel with the other release work. Do
not wait for full release validation to start the performance signal.
@@ -468,8 +472,10 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
- The npm workflow and the private mac publish workflow accept
`preflight_only=true` to run validation/build/package steps without uploading
public release assets.
- Real npm publish requires a prior successful npm preflight run id so the
publish job promotes the prepared tarball instead of rebuilding it.
- Real npm publish requires a prior successful npm preflight run id and the
successful Full Release Validation run id for the same tag/SHA so the publish
job promotes the prepared tarball instead of rebuilding it and attaches the
correct release evidence.
- Real private mac publish requires a prior successful private mac preflight
run id so the publish job promotes the prepared artifacts instead of
rebuilding or renotarizing them again.
@@ -499,11 +505,12 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
instead of uploading public GitHub release assets.
- Private smoke-test runs upload ad-hoc, non-notarized build artifacts as
workflow artifacts and intentionally skip stable `appcast.xml` generation.
- For stable releases, npm preflight, public mac validation, private mac
validation, and private mac preflight must all pass before any real publish
run starts. For beta releases, npm preflight plus the selected Docker,
install/update, Parallels, and release-check lanes are sufficient unless mac
beta validation was explicitly requested.
- For stable releases, npm preflight, Full Release Validation, public mac
validation, private mac validation, and private mac preflight must all pass
before any real publish run starts. For beta releases, npm preflight and Full
Release Validation must pass before npm publish unless the operator explicitly
waives the full gate; mac beta validation is still only required when
requested.
- Real publish runs may be dispatched from `main` or from a
`release/YYYY.M.D` branch. For release-branch runs, the tag must be contained
in that release branch, and the real publish must reuse a successful preflight

View File

@@ -605,7 +605,19 @@ jobs:
restore-keys: |
${{ runner.os }}-build-all-v3-
- name: Restore dist build cache
id: dist_build_cache
uses: actions/cache/restore@v5
with:
path: |
dist/
dist-runtime/
extensions/*/src/host/**/.bundle.hash
extensions/*/src/host/**/*.bundle.js
key: ${{ runner.os }}-dist-build-${{ needs.preflight.outputs.checkout_revision }}
- name: Build dist
if: steps.dist_build_cache.outputs.cache-hit != 'true'
env:
NODE_OPTIONS: --max-old-space-size=8192
run: pnpm build:ci-artifacts
@@ -614,14 +626,6 @@ jobs:
if: needs.preflight.outputs.run_control_ui_i18n == 'true'
run: pnpm ui:i18n:check
- name: Cache dist build
uses: actions/cache@v5
with:
path: |
dist/
dist-runtime/
key: ${{ runner.os }}-dist-build-${{ needs.preflight.outputs.checkout_revision }}
- name: Pack built runtime artifacts
run: tar --posix -cf dist-runtime-build.tar.zst --use-compress-program zstdmt dist dist-runtime
@@ -751,6 +755,18 @@ jobs:
done
exit "$failures"
- name: Save dist build cache
if: steps.dist_build_cache.outputs.cache-hit != 'true'
uses: actions/cache/save@v5
continue-on-error: true
with:
path: |
dist/
dist-runtime/
extensions/*/src/host/**/.bundle.hash
extensions/*/src/host/**/*.bundle.js
key: ${{ steps.dist_build_cache.outputs.cache-primary-key }}
- name: Upload gateway watch regression artifacts
if: always() && needs.preflight.outputs.run_check_additional == 'true'
uses: actions/upload-artifact@v7
@@ -1151,7 +1167,8 @@ jobs:
OPENCLAW_NODE_TEST_CONFIGS_JSON: ${{ toJson(matrix.configs) }}
OPENCLAW_NODE_TEST_INCLUDE_PATTERNS_JSON: ${{ toJson(matrix.includePatterns) }}
OPENCLAW_VITEST_SHARD_NAME: ${{ matrix.shard_name }}
OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS: "900000"
OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS: "300000"
OPENCLAW_VITEST_NO_OUTPUT_RETRY: "1"
OPENCLAW_TEST_PROJECTS_PARALLEL: "2"
shell: bash
run: |

View File

@@ -32,11 +32,11 @@ permissions:
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
PNPM_CONFIG_CHILD_CONCURRENCY: "1"
PNPM_CONFIG_MODULES_DIR: "/tmp/openclaw-pnpm-node-modules"
PNPM_CONFIG_MODULES_DIR: "/var/tmp/openclaw-pnpm-node-modules"
PNPM_CONFIG_NETWORK_CONCURRENCY: "1"
PNPM_CONFIG_STORE_DIR: "/tmp/openclaw-pnpm-store"
PNPM_CONFIG_STORE_DIR: "/var/tmp/openclaw-pnpm-store"
PNPM_CONFIG_VERIFY_DEPS_BEFORE_RUN: "false"
PNPM_CONFIG_VIRTUAL_STORE_DIR: "/tmp/openclaw-pnpm-virtual-store"
PNPM_CONFIG_VIRTUAL_STORE_DIR: "/var/tmp/openclaw-pnpm-virtual-store"
jobs:
hydrate:

View File

@@ -229,7 +229,7 @@ jobs:
needs: [resolve_target]
if: inputs.rerun_group == 'all'
runs-on: ubuntu-24.04
timeout-minutes: 45
timeout-minutes: 20
permissions:
contents: read
steps:
@@ -245,54 +245,11 @@ jobs:
DOCKER_BUILDKIT: "1"
run: |
set -euo pipefail
timeout --kill-after=30s 35m docker build \
timeout --kill-after=30s 15m docker build \
--target runtime-assets \
--build-arg OPENCLAW_EXTENSIONS="diagnostics-otel,codex" \
.
- name: Build and smoke test final Docker runtime image
env:
DOCKER_BUILDKIT: "1"
TARGET_SHA: ${{ needs.resolve_target.outputs.sha }}
run: |
set -euo pipefail
image_ref="openclaw-release-runtime-smoke:${TARGET_SHA}"
timeout --kill-after=30s 35m docker build \
--build-arg OPENCLAW_EXTENSIONS="diagnostics-otel,codex" \
-t "${image_ref}" \
.
docker run --rm --entrypoint /bin/sh "${image_ref}" -lc '
set -eu
test -f /app/src/agents/templates/HEARTBEAT.md
temp_root="$(mktemp -d)"
trap "rm -rf \"${temp_root}\"" EXIT
mkdir -p "${temp_root}/home" "${temp_root}/cwd"
cd "${temp_root}/cwd"
set +e
HOME="${temp_root}/home" \
USERPROFILE="${temp_root}/home" \
OPENCLAW_HOME="${temp_root}/home" \
OPENCLAW_NO_ONBOARD=1 \
OPENCLAW_SUPPRESS_NOTES=1 \
OPENCLAW_DISABLE_BUNDLED_PLUGINS=1 \
OPENCLAW_DISABLE_BUNDLED_ENTRY_SOURCE_FALLBACK=1 \
AWS_EC2_METADATA_DISABLED=true \
AWS_SHARED_CREDENTIALS_FILE="${temp_root}/home/.aws/credentials" \
AWS_CONFIG_FILE="${temp_root}/home/.aws/config" \
node /app/openclaw.mjs agent --message "workspace bootstrap smoke" --session-id "workspace-bootstrap-smoke" --local --timeout 1 --json \
>"${temp_root}/out.log" 2>&1
status="$?"
set -e
if grep -F "Missing workspace template:" "${temp_root}/out.log"; then
cat "${temp_root}/out.log"
exit 1
fi
test -f "${temp_root}/home/.openclaw/workspace/HEARTBEAT.md"
if [ "${status}" -ne 0 ]; then
cat "${temp_root}/out.log"
fi
'
normal_ci:
name: Run normal full CI
needs: [resolve_target, docker_runtime_assets_preflight]
@@ -380,6 +337,21 @@ jobs:
gh_with_retry api --paginate "repos/${GITHUB_REPOSITORY}/actions/runs/${run_id}/jobs?per_page=100" --jq '.jobs[]'
}
fail_fast_failed_jobs() {
local failed_jobs_json
failed_jobs_json="$(
fetch_child_jobs |
jq -s '[.[] | select(.status == "completed" and .conclusion != "success" and .conclusion != "skipped")]'
)"
if jq -e 'length > 0' <<< "$failed_jobs_json" >/dev/null; then
echo "::error::${workflow} has failed child jobs before the workflow completed; cancelling the remaining matrix."
jq '.[] | {name, conclusion, url: .html_url}' <<< "$failed_jobs_json"
cancel_child
trap - EXIT INT TERM
exit 1
fi
}
cancel_child() {
if [[ -n "${run_id:-}" ]]; then
echo "Cancelling child workflow ${workflow}: ${run_id}" >&2
@@ -395,6 +367,9 @@ jobs:
break
fi
poll_count=$((poll_count + 1))
if (( poll_count % 2 == 0 )); then
fail_fast_failed_jobs
fi
if (( poll_count % 10 == 0 )); then
echo "Still waiting on ${workflow}: https://github.com/${GITHUB_REPOSITORY}/actions/runs/${run_id}"
fetch_child_jobs | jq 'select(.status != "completed") | {name, status, url: .html_url}' || true
@@ -510,6 +485,21 @@ jobs:
gh_with_retry api --paginate "repos/${GITHUB_REPOSITORY}/actions/runs/${run_id}/jobs?per_page=100" --jq '.jobs[]'
}
fail_fast_failed_jobs() {
local failed_jobs_json
failed_jobs_json="$(
fetch_child_jobs |
jq -s '[.[] | select(.status == "completed" and .conclusion != "success" and .conclusion != "skipped")]'
)"
if jq -e 'length > 0' <<< "$failed_jobs_json" >/dev/null; then
echo "::error::${workflow} has failed child jobs before the workflow completed; cancelling the remaining matrix."
jq '.[] | {name, conclusion, url: .html_url}' <<< "$failed_jobs_json"
cancel_child
trap - EXIT INT TERM
exit 1
fi
}
cancel_child() {
if [[ -n "${run_id:-}" ]]; then
echo "Cancelling child workflow ${workflow}: ${run_id}" >&2
@@ -525,6 +515,9 @@ jobs:
break
fi
poll_count=$((poll_count + 1))
if (( poll_count % 2 == 0 )); then
fail_fast_failed_jobs
fi
if (( poll_count % 10 == 0 )); then
echo "Still waiting on ${workflow}: https://github.com/${GITHUB_REPOSITORY}/actions/runs/${run_id}"
fetch_child_jobs | jq 'select(.status != "completed") | {name, status, url: .html_url}' || true
@@ -690,6 +683,24 @@ jobs:
[[ "$saw_advisory" == "1" && "$failed" == "0" ]]
}
fail_fast_failed_jobs() {
local failed_jobs_json
if [[ "$workflow" == "openclaw-release-checks.yml" && "$CHILD_WORKFLOW_REF" =~ ^tideclaw/alpha/[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{4}Z$ ]]; then
return 0
fi
failed_jobs_json="$(
fetch_child_jobs |
jq -s '[.[] | select(.status == "completed" and .conclusion != "success" and .conclusion != "skipped")]'
)"
if jq -e 'length > 0' <<< "$failed_jobs_json" >/dev/null; then
echo "::error::${workflow} has failed child jobs before the workflow completed; cancelling the remaining matrix."
jq '.[] | {name, conclusion, url: .html_url}' <<< "$failed_jobs_json"
cancel_child
trap - EXIT INT TERM
exit 1
fi
}
cancel_child() {
if [[ -n "${run_id:-}" ]]; then
echo "Cancelling child workflow ${workflow}: ${run_id}" >&2
@@ -705,6 +716,9 @@ jobs:
break
fi
poll_count=$((poll_count + 1))
if (( poll_count % 2 == 0 )); then
fail_fast_failed_jobs
fi
if (( poll_count % 10 == 0 )); then
echo "Still waiting on ${workflow}: https://github.com/${GITHUB_REPOSITORY}/actions/runs/${run_id}"
fetch_child_jobs | jq 'select(.status != "completed") | {name, status, url: .html_url}' || true
@@ -962,6 +976,21 @@ jobs:
}
trap cancel_child EXIT INT TERM
fail_fast_failed_jobs() {
local failed_jobs_json
failed_jobs_json="$(
gh_with_retry run view "$run_id" --json jobs \
--jq '[.jobs[] | select(.status == "completed" and .conclusion != "success" and .conclusion != "skipped")]'
)"
if jq -e 'length > 0' <<< "$failed_jobs_json" >/dev/null; then
echo "::error::npm-telegram-beta-e2e.yml has failed child jobs before the workflow completed; cancelling the remaining run."
jq '.[] | {name, conclusion, url}' <<< "$failed_jobs_json"
cancel_child
trap - EXIT INT TERM
exit 1
fi
}
poll_count=0
while true; do
status="$(gh_with_retry run view "$run_id" --json status --jq '.status')"
@@ -969,6 +998,9 @@ jobs:
break
fi
poll_count=$((poll_count + 1))
if (( poll_count % 2 == 0 )); then
fail_fast_failed_jobs
fi
if (( poll_count % 10 == 0 )); then
echo "Still waiting on npm-telegram-beta-e2e.yml: https://github.com/${GITHUB_REPOSITORY}/actions/runs/${run_id}"
gh_with_retry run view "$run_id" --json jobs --jq '.jobs[] | select(.status != "completed") | {name, status, url}' || true

View File

@@ -46,11 +46,12 @@ on:
default: true
type: boolean
release_profile:
description: Release coverage profile used for release evidence summaries
description: Release coverage profile used for release evidence summaries; default reads it from the validation manifest
required: false
default: beta
default: from-validation
type: choice
options:
- from-validation
- beta
- stable
- full
@@ -135,9 +136,9 @@ jobs:
exit 1
fi
case "$RELEASE_PROFILE" in
beta|stable|full) ;;
from-validation|beta|stable|full) ;;
*)
echo "release_profile must be one of: beta, stable, full" >&2
echo "release_profile must be one of: from-validation, beta, stable, full" >&2
exit 1
;;
esac
@@ -259,6 +260,7 @@ jobs:
echo "sha=$release_sha" >> "$GITHUB_OUTPUT"
- name: Validate full release validation manifest
id: full_manifest
if: ${{ inputs.publish_openclaw_npm }}
env:
GH_TOKEN: ${{ github.token }}
@@ -289,7 +291,7 @@ jobs:
echo "Full release validation target SHA mismatch: expected $EXPECTED_SHA, got $target_sha" >&2
exit 1
fi
if [[ "$release_profile" != "$EXPECTED_RELEASE_PROFILE" ]]; then
if [[ "$EXPECTED_RELEASE_PROFILE" != "from-validation" && "$release_profile" != "$EXPECTED_RELEASE_PROFILE" ]]; then
echo "Full release validation profile mismatch: expected $EXPECTED_RELEASE_PROFILE, got $release_profile" >&2
exit 1
fi
@@ -297,6 +299,7 @@ jobs:
echo "Full release validation must run rerun_group=all before npm publish; got $rerun_group" >&2
exit 1
fi
echo "release_profile=$release_profile" >> "$GITHUB_OUTPUT"
- name: Validate release tag is reachable from a trusted release branch
env:
@@ -332,7 +335,7 @@ jobs:
env:
RELEASE_TAG: ${{ inputs.tag }}
TARGET_SHA: ${{ steps.manifest.outputs.sha || steps.ref.outputs.sha }}
RELEASE_PROFILE: ${{ inputs.release_profile }}
RELEASE_PROFILE: ${{ steps.full_manifest.outputs.release_profile || inputs.release_profile }}
FULL_RELEASE_VALIDATION_RUN_ID: ${{ inputs.full_release_validation_run_id }}
run: |
{
@@ -501,7 +504,7 @@ jobs:
wait_for_run() {
local workflow="$1"
local run_id="$2"
local status conclusion url updated_at created_at duration_seconds duration_label last_state
local status conclusion url updated_at created_at duration_seconds duration_label last_state failed_json
last_state=""
while true; do
@@ -510,6 +513,14 @@ jobs:
if [[ "$status" == "completed" ]]; then
break
fi
failed_json="$(gh run view --repo "$GITHUB_REPOSITORY" "$run_id" --json jobs \
--jq '[.jobs[] | select(.status == "completed" and .conclusion != "success" and .conclusion != "skipped")]' || true)"
if [[ -n "${failed_json}" ]] && jq -e 'length > 0' <<< "$failed_json" >/dev/null; then
echo "${workflow} has failed jobs before the workflow completed: https://github.com/${GITHUB_REPOSITORY}/actions/runs/${run_id}" >&2
jq '.[] | {name, conclusion, url}' <<< "$failed_json" >&2 || true
print_failed_run_summary "${run_id}"
return 1
fi
url="$(printf '%s' "$run_json" | jq -r '.url')"
updated_at="$(printf '%s' "$run_json" | jq -r '.updatedAt')"
state="${status}:${updated_at}"

View File

@@ -818,6 +818,7 @@ jobs:
OPENCLAW_QA_CONVEX_SECRET_CI: ${{ secrets.OPENCLAW_QA_CONVEX_SECRET_CI }}
OPENCLAW_QA_REDACT_PUBLIC_METADATA: "1"
OPENCLAW_QA_SLACK_CAPTURE_CONTENT: "1"
OPENCLAW_QA_TRANSPORT_READY_TIMEOUT_MS: "180000"
INPUT_SCENARIO: ${{ github.event_name == 'workflow_dispatch' && inputs.slack_scenario || '' }}
run: |
set -euo pipefail

View File

@@ -2,7 +2,7 @@
Docs: https://docs.openclaw.ai
## 2026.6.1
## 2026.6.2
### Highlights
@@ -54,6 +54,7 @@ Docs: https://docs.openclaw.ai
- Agents/Codex: surface Skill Workshop guidance in Codex app-server prompts when `skill_workshop` is available. Thanks @shakkernerd.
- Agents/auth: write auth profiles atomically, add force re-login recovery, preserve workspaces during state-only uninstall, and compact before oversized turns so recovery paths avoid partial state.
- Skills: skip disabled skill env overrides from stale persisted snapshots so disabled skill `apiKey` SecretRefs cannot abort embedded or channel turns. (#79072, #79173) Thanks @zeus1959.
- Skill Workshop: render the Control UI tab from filtered navigation state and keep filtered fallback routing stable.
- CLI: avoid live catalog validation during `openclaw agents add`, so adding a secondary agent no longer depends on provider catalog availability. (#76284, #88314) Thanks @zhangguiping-xydt.
- CLI: keep `plugins list --json` on the snapshot-only path so plugin sweeps avoid loading the full runtime status graph.
- CLI/desktop: bridge WSL clipboard operations through the shell and recognize manual-update launchd jobs. (#88764)
@@ -83,6 +84,7 @@ Docs: https://docs.openclaw.ai
- Channels: preserve long Feishu streaming replies, send visible fallbacks when accepted Feishu turns produce no final reply, tolerate iMessage self-chat timestamp skew, preserve colon-prefixed slash commands in mention parsing, decode Nostr `npub` allowlists correctly, and suppress raw provider errors during channel delivery. (#87896)
- Config/status/doctor: skip unresolved shell references in state-dir dotenv files, resolve gateway auth secrets during deep status audits, respect explicit PI runtime policy, report runtime tool-schema errors, and keep post-upgrade JSON stable. (#88288)
- Gateway/session state: list commands from the Gateway plugin registry, harden MCP loopback tool schemas, hide phantom agent-store rows from `sessions.list`, make task persistence failures explicit, and carry session UUIDs on interactive dispatch events.
- Gateway/plugins: narrow plugin lookup memoization to the stable plugin/runtime inputs, avoiding repeated lookup work without mixing disabled or filtered plugin state.
- OpenAI/TTS: handle speed directives for OpenAI TTS voices. (#74089)
- CI/Crabbox: keep default runner capacity on the Azure credit-backed on-demand D4 lane with the Azure SSH port and a Git-independent full check job, so broad validation avoids low-priority spot quota stalls, hydrate port mismatches, non-Git hydrated workspaces, and stale AWS region hints.
- CI/Crabbox: route Crabbox wrapper and Testbox workflow edits to their regression tests so changed-test gates do not silently run zero specs.
@@ -657,6 +659,7 @@ Docs: https://docs.openclaw.ai
- Gateway/sessions: allow shared-secret bearer callers to read and stream session history without an explicit scope header. (#81815) Thanks @medns.
- Agents/embedded runner: classify HTML auth provider responses as `auth_html` and return a re-authentication hint instead of the CDN-blocked copy that `upstream_html` returns. Cloudflare Access login pages, nginx basic-auth challenges, and gateway login walls all produce HTML auth bodies that were previously misdiagnosed as transient CDN blocks. (#79900) Thanks @martingarramon.
- TUI/streaming watchdog: dismiss the `This response is taking longer than expected` notice as soon as a chat event for the same run arrives, so the message no longer sits next to the recovered response when the run was only briefly silent. Refs #67052, #69081 (closed), prior attempt #69026. Thanks @jpruit20 and @romneyda.
- Agents/auth profiles: replace the bare `No available auth profile for <provider> (all in cooldown or unavailable)` TUI error with plain-language copy that explains what happened in user terms (sign-in expired, provider asking us to slow down, billing issue on the account, etc.) and suggests the matching `openclaw models auth login --provider <provider>` recovery command for sign-in and billing causes, while falling back to the underlying provider error for cases without a clear recovery path. Thanks @romneyda.
- Agents/Pi: tolerate OpenClaw-owned transcript writes while embedded prompts are released for model I/O, keeping long-running Feishu, Slack, Telegram, and cron turns from failing with false session-takeover errors. Fixes #84059. (#84250) Thanks @tianxiaochannel-oss88.
## 2026.5.20

View File

@@ -65,8 +65,8 @@ android {
applicationId = "ai.openclaw.app"
minSdk = 31
targetSdk = 36
versionCode = 2026053101
versionName = "2026.6.1"
versionCode = 2026060201
versionName = "2026.6.2"
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,5 +1,9 @@
# OpenClaw iOS Changelog
## 2026.6.2 - 2026-06-02
Maintenance update for the current OpenClaw release.
## 2026.6.1 - 2026-06-01
Maintenance update for the current OpenClaw 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.6.1
OPENCLAW_MARKETING_VERSION = 2026.6.1
OPENCLAW_IOS_VERSION = 2026.6.2
OPENCLAW_MARKETING_VERSION = 2026.6.2
OPENCLAW_BUILD_VERSION = 1
#include? "../build/Version.xcconfig"

View File

@@ -1,5 +1 @@
Maintenance update for the current OpenClaw release.
- Added hosted push relay defaults, realtime Talk playback, and safer WebSocket ping handling for mobile sessions.
- Updated App Store screenshots to cover Gateway pairing, Command, Chat, Talk, Agent, and Settings flows.
- Highlighted realtime Talk relay, Gateway connection status, node capabilities, push wake, and privacy controls.

View File

@@ -1,3 +1,3 @@
{
"version": "2026.6.1"
"version": "2026.6.2"
}

View File

@@ -514,12 +514,16 @@ extension GatewayConnection {
var params: [String: AnyCodable] = [
"message": AnyCodable(trimmed),
"sessionKey": AnyCodable(sessionKey),
"thinking": AnyCodable(invocation.thinking ?? "default"),
"deliver": AnyCodable(invocation.deliver),
"to": AnyCodable(invocation.to ?? ""),
"channel": AnyCodable(invocation.channel.rawValue),
"idempotencyKey": AnyCodable(invocation.idempotencyKey),
]
if let thinking = invocation.thinking?.trimmingCharacters(in: .whitespacesAndNewlines),
!thinking.isEmpty
{
params["thinking"] = AnyCodable(thinking)
}
if let timeout = invocation.timeoutSeconds {
params["timeout"] = AnyCodable(timeout)
}
@@ -664,7 +668,7 @@ extension GatewayConnection {
func chatSend(
sessionKey: String,
message: String,
thinking: String,
thinking: String?,
idempotencyKey: String,
attachments: [OpenClawChatAttachmentPayload],
timeoutMs: Int = 30000) async throws -> OpenClawChatSendResponse
@@ -673,10 +677,14 @@ extension GatewayConnection {
var params: [String: AnyCodable] = [
"sessionKey": AnyCodable(resolvedKey),
"message": AnyCodable(message),
"thinking": AnyCodable(thinking),
"idempotencyKey": AnyCodable(idempotencyKey),
"timeoutMs": AnyCodable(timeoutMs),
]
if let thinking = thinking?.trimmingCharacters(in: .whitespacesAndNewlines),
!thinking.isEmpty
{
params["thinking"] = AnyCodable(thinking)
}
if !attachments.isEmpty {
let encoded = attachments.map { att in

View File

@@ -15,9 +15,9 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>2026.6.1</string>
<string>2026.6.2</string>
<key>CFBundleVersion</key>
<string>2026053100</string>
<string>2026060200</string>
<key>CFBundleIconFile</key>
<string>OpenClaw</string>
<key>CFBundleURLTypes</key>

View File

@@ -387,7 +387,7 @@ actor TalkModeRuntime {
let response = try await GatewayConnection.shared.chatSend(
sessionKey: sessionKey,
message: prompt,
thinking: "low",
thinking: nil,
idempotencyKey: runId,
attachments: [])
guard self.isCurrent(gen) else { return }

View File

@@ -34,7 +34,7 @@ enum VoiceWakeForwarder {
struct ForwardOptions {
var sessionKey: String = "main"
var thinking: String = "low"
var thinking: String?
var deliver: Bool = true
var to: String?
var channel: GatewayAgentChannel = .webchat
@@ -97,7 +97,6 @@ enum VoiceWakeForwarder {
return ForwardOptions(
sessionKey: sessionKey,
thinking: "low",
deliver: true,
to: to,
channel: channel,

View File

@@ -173,9 +173,57 @@ private func makeTestGatewayConnection() -> (GatewayConnection, FakeWebSocketSes
let json = try JSONSerialization.jsonObject(with: payloadData) as? [String: Any]
let params = json?["params"] as? [String: Any]
#expect(params?["thinking"] == nil)
#expect(params?["voiceWakeTrigger"] as? String == "")
}
@Test func `chat send omits thinking when inheriting session default`() async throws {
let recorder = WebSocketMessageRecorder()
let session = GatewayTestWebSocketSession(taskFactory: {
GatewayTestWebSocketTask(sendHook: { task, message, sendIndex in
recorder.append(message)
guard sendIndex > 0,
let data = Self.messageData(message),
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let id = json["id"] as? String
else { return }
task.emitReceiveSuccess(.data(Self.chatSendOkResponseData(id: id)))
})
})
let connection = GatewayConnection(
configProvider: {
(url: URL(string: "ws://127.0.0.1:1")!, token: nil, password: nil)
},
sessionBox: WebSocketSessionBox(session: session))
_ = try await connection.chatSend(
sessionKey: "main",
message: "hello",
thinking: nil,
idempotencyKey: "chat-1",
attachments: [])
await connection.shutdown()
guard let chatMessage = recorder.snapshot().reversed().first(where: { message in
guard let data = Self.messageData(message),
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
else { return false }
return json["method"] as? String == "chat.send"
}) else {
Issue.record("expected chat.send websocket payload")
return
}
guard let payloadData = Self.messageData(chatMessage) else {
Issue.record("unexpected chat.send websocket message type")
return
}
let json = try JSONSerialization.jsonObject(with: payloadData) as? [String: Any]
let params = json?["params"] as? [String: Any]
#expect(params?["thinking"] == nil)
}
private static func messageData(_ message: URLSessionWebSocketTask.Message) -> Data? {
switch message {
case let .string(text):
@@ -186,4 +234,15 @@ private func makeTestGatewayConnection() -> (GatewayConnection, FakeWebSocketSes
nil
}
}
private static func chatSendOkResponseData(id: String) -> Data {
Data("""
{
"type": "res",
"id": "\(id)",
"ok": true,
"payload": { "runId": "chat-1", "status": "ok" }
}
""".utf8)
}
}

View File

@@ -14,7 +14,7 @@ import Testing
@Test func `forward options defaults`() {
let opts = VoiceWakeForwarder.ForwardOptions()
#expect(opts.sessionKey == "main")
#expect(opts.thinking == "low")
#expect(opts.thinking == nil)
#expect(opts.deliver == true)
#expect(opts.to == nil)
#expect(opts.channel == .webchat)
@@ -38,6 +38,7 @@ import Testing
#expect(opts.channel == .telegram)
#expect(opts.to == "telegram:6812765697")
#expect(opts.voiceWakeTrigger == "open claw")
#expect(opts.thinking == nil)
#expect(opts.channel.shouldDeliver(opts.deliver) == true)
}

View File

@@ -1,2 +1,2 @@
bdcf661ec680f79819096950295bdb04805aac9639477058d8855f294f6d8034 plugin-sdk-api-baseline.json
6b8c92cc5a9277f90973370102fa31efb23ffd93008c3ed961d38e4a8a3073b0 plugin-sdk-api-baseline.jsonl
63d49032a9b4dc4874a0ca17be73ecc97a2df5d1f47b4e72db34868423370558 plugin-sdk-api-baseline.json
af79f7d711afa0a8563782b8f5cdd7e46b9aea245f5e7ebc464327a8969ed65e plugin-sdk-api-baseline.jsonl

Binary file not shown.

Before

Width:  |  Height:  |  Size: 146 KiB

After

Width:  |  Height:  |  Size: 251 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 135 KiB

After

Width:  |  Height:  |  Size: 251 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

After

Width:  |  Height:  |  Size: 244 KiB

View File

@@ -93,6 +93,7 @@ openclaw onboard --non-interactive \
`--custom-api-key` is optional in non-interactive mode. If omitted, onboarding checks `CUSTOM_API_KEY`.
OpenClaw marks common vision model IDs as image-capable automatically. Pass `--custom-image-input` for unknown custom vision IDs, or `--custom-text-input` to force text-only metadata.
Use `--custom-compatibility openai-responses` for OpenAI-compatible endpoints that support `/v1/responses` but not `/v1/chat/completions`.
LM Studio also supports a provider-specific key flag in non-interactive mode:

View File

@@ -101,6 +101,8 @@ Automated UK school meal booking via ParentPay. Uses mouse coordinates for relia
**@julianengel** • `files` `r2` `presigned-urls`
Upload to Cloudflare R2/S3 and generate secure presigned download links. Useful for remote OpenClaw instances.
<img src="/assets/showcase/r2-upload.png" alt="R2 upload skill on ClawHub" />
</Card>
<Card title="iOS app via Telegram" icon="mobile">
@@ -269,6 +271,8 @@ Vapi voice assistant to OpenClaw HTTP bridge. Near real-time phone calls with yo
**@obviyus** • `transcription` `multilingual` `skill`
Multi-lingual audio transcription via OpenRouter (Gemini, and more). Available on ClawHub.
<img src="/assets/showcase/openrouter-transcribe.png" alt="OpenRouter transcription skill on ClawHub" />
</Card>
</CardGroup>
@@ -289,6 +293,8 @@ OpenClaw gateway running on Home Assistant OS with SSH tunnel support and persis
**ClawHub**`homeassistant` `skill` `automation`
Control and automate Home Assistant devices via natural language.
<img src="/assets/showcase/homeassistant.png" alt="Home Assistant skill on ClawHub" />
</Card>
<Card title="Nix packaging" icon="snowflake" href="https://github.com/openclaw/nix-openclaw">
@@ -301,6 +307,8 @@ Batteries-included nixified OpenClaw configuration for reproducible deployments.
**ClawHub**`calendar` `caldav` `skill`
Calendar skill using khal and vdirsyncer. Self-hosted calendar integration.
<img src="/assets/showcase/caldav-calendar.png" alt="CalDAV calendar skill on ClawHub" />
</Card>
</CardGroup>

View File

@@ -219,7 +219,7 @@ What you set:
- `--custom-model-id`
- `--custom-api-key` (optional; falls back to `CUSTOM_API_KEY`)
- `--custom-provider-id` (optional)
- `--custom-compatibility <openai|anthropic>` (optional; default `openai`)
- `--custom-compatibility <openai|openai-responses|anthropic>` (optional; default `openai`)
- `--custom-image-input` / `--custom-text-input` (optional; override inferred model input capability)
</Accordion>

View File

@@ -1,12 +1,12 @@
{
"name": "@openclaw/acpx",
"version": "2026.6.1",
"version": "2026.6.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@openclaw/acpx",
"version": "2026.6.1",
"version": "2026.6.2",
"dependencies": {
"@agentclientprotocol/claude-agent-acp": "0.39.0",
"@zed-industries/codex-acp": "0.15.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/acpx",
"version": "2026.6.1",
"version": "2026.6.2",
"description": "OpenClaw ACP runtime backend with plugin-owned session and transport management.",
"repository": {
"type": "git",
@@ -26,10 +26,10 @@
"minHostVersion": ">=2026.4.25"
},
"compat": {
"pluginApi": ">=2026.6.1"
"pluginApi": ">=2026.6.2"
},
"build": {
"openclawVersion": "2026.6.1",
"openclawVersion": "2026.6.2",
"staticAssets": [
{
"source": "./src/runtime-internals/mcp-proxy.mjs",

View File

@@ -215,6 +215,7 @@ describe("AcpxRuntime fresh reset wrapper", () => {
agent: "codex",
mode: "persistent",
model: "gpt-5.4",
sessionOptions: { model: "gpt-5.4" },
});
});
@@ -619,7 +620,7 @@ describe("AcpxRuntime fresh reset wrapper", () => {
);
});
it("does not normalize model startup for non-Codex ACP agents", async () => {
it("passes model startup through sessionOptions for non-Codex ACP agents", async () => {
const baseStore: TestSessionStore = {
load: vi.fn(async () => undefined),
save: vi.fn(async () => {}),
@@ -648,6 +649,7 @@ describe("AcpxRuntime fresh reset wrapper", () => {
agent: "main",
mode: "persistent",
model: "openai/gpt-5.5",
sessionOptions: { model: "openai/gpt-5.5" },
});
});
@@ -694,6 +696,7 @@ describe("AcpxRuntime fresh reset wrapper", () => {
agent: "codex",
mode: "persistent",
model: "gpt-5.5",
sessionOptions: { model: "gpt-5.5" },
});
});
@@ -728,6 +731,7 @@ describe("AcpxRuntime fresh reset wrapper", () => {
mode: "persistent",
model: "gpt-5.4/xhigh",
thinking: "x-high",
sessionOptions: { model: "gpt-5.4/xhigh" },
});
});

View File

@@ -17,6 +17,7 @@ import {
type AcpRuntimeStatus,
type AcpRuntimeTurn,
type AcpRuntimeTurnResult,
type SessionAgentOptions,
} from "acpx/runtime";
import { parseStrictPositiveInteger } from "openclaw/plugin-sdk/number-runtime";
import { redactSensitiveText } from "openclaw/plugin-sdk/security-runtime";
@@ -49,6 +50,8 @@ type AcpxRuntimeTestOptions = Record<string, unknown> & {
openclawProcessCleanup?: AcpxProcessCleanupDeps;
};
type OpenClawRuntimeTurnInput = Parameters<NonNullable<AcpRuntime["startTurn"]>>[0];
type OpenClawRuntimeEnsureInput = Parameters<AcpRuntime["ensureSession"]>[0];
type AcpxDelegateEnsureInput = Parameters<BaseAcpxRuntime["ensureSession"]>[0];
type ResetAwareSessionStore = AcpSessionStore & {
markFresh: (sessionKey: string) => void;
@@ -547,6 +550,16 @@ function codexAcpSessionModelId(override: CodexAcpModelOverride): string {
: override.model;
}
function withAcpxSessionOptions(input: OpenClawRuntimeEnsureInput): AcpxDelegateEnsureInput {
const existingOptions = (input as { sessionOptions?: SessionAgentOptions }).sessionOptions;
const model = input.model?.trim() || existingOptions?.model;
const sessionOptions = model ? { ...existingOptions, model } : existingOptions;
return {
...input,
...(sessionOptions ? { sessionOptions } : {}),
} as AcpxDelegateEnsureInput;
}
function quoteShellArg(value: string): string {
if (/^[A-Za-z0-9_./:=@+-]+$/.test(value)) {
return value;
@@ -942,7 +955,7 @@ export class AcpxRuntime implements AcpRuntime {
this.withCodexWrapperDiagnostics({
command: stableLaunchCommand,
fallbackCode: "ACP_SESSION_INIT_FAILED",
run: () => delegate.ensureSession(input),
run: () => delegate.ensureSession(withAcpxSessionOptions(input)),
}),
});
}
@@ -962,7 +975,7 @@ export class AcpxRuntime implements AcpRuntime {
this.withCodexWrapperDiagnostics({
command: stableLaunchCommand,
fallbackCode: "ACP_SESSION_INIT_FAILED",
run: () => delegate.ensureSession(normalizedInput),
run: () => delegate.ensureSession(withAcpxSessionOptions(normalizedInput)),
}),
),
});

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/admin-http-rpc",
"version": "2026.6.1",
"version": "2026.6.2",
"private": true,
"description": "OpenClaw admin HTTP RPC endpoint",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/alibaba-provider",
"version": "2026.6.1",
"version": "2026.6.2",
"private": true,
"description": "OpenClaw Alibaba Model Studio video provider plugin",
"type": "module",

View File

@@ -1,12 +1,12 @@
{
"name": "@openclaw/amazon-bedrock-mantle-provider",
"version": "2026.6.1",
"version": "2026.6.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@openclaw/amazon-bedrock-mantle-provider",
"version": "2026.6.1",
"version": "2026.6.2",
"dependencies": {
"@anthropic-ai/sdk": "0.100.1",
"@aws/bedrock-token-generator": "1.1.0"

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/amazon-bedrock-mantle-provider",
"version": "2026.6.1",
"version": "2026.6.2",
"description": "OpenClaw Amazon Bedrock Mantle provider plugin for OpenAI-compatible model routing.",
"repository": {
"type": "git",
@@ -24,10 +24,10 @@
"minHostVersion": ">=2026.5.12-beta.1"
},
"compat": {
"pluginApi": ">=2026.6.1"
"pluginApi": ">=2026.6.2"
},
"build": {
"openclawVersion": "2026.6.1",
"openclawVersion": "2026.6.2",
"bundledDist": false
},
"release": {

View File

@@ -1,12 +1,12 @@
{
"name": "@openclaw/amazon-bedrock-provider",
"version": "2026.6.1",
"version": "2026.6.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@openclaw/amazon-bedrock-provider",
"version": "2026.6.1",
"version": "2026.6.2",
"dependencies": {
"@aws-sdk/client-bedrock": "3.1056.0",
"@aws-sdk/client-bedrock-runtime": "3.1056.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/amazon-bedrock-provider",
"version": "2026.6.1",
"version": "2026.6.2",
"description": "OpenClaw Amazon Bedrock provider plugin with model discovery, embeddings, and guardrail support.",
"repository": {
"type": "git",
@@ -28,10 +28,10 @@
"minHostVersion": ">=2026.5.12-beta.1"
},
"compat": {
"pluginApi": ">=2026.6.1"
"pluginApi": ">=2026.6.2"
},
"build": {
"openclawVersion": "2026.6.1",
"openclawVersion": "2026.6.2",
"bundledDist": false
},
"release": {

View File

@@ -1,12 +1,12 @@
{
"name": "@openclaw/anthropic-vertex-provider",
"version": "2026.6.1",
"version": "2026.6.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@openclaw/anthropic-vertex-provider",
"version": "2026.6.1",
"version": "2026.6.2",
"dependencies": {
"@anthropic-ai/vertex-sdk": "0.16.1"
}

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/anthropic-vertex-provider",
"version": "2026.6.1",
"version": "2026.6.2",
"description": "OpenClaw Anthropic Vertex provider plugin for Claude models on Google Vertex AI.",
"repository": {
"type": "git",
@@ -23,10 +23,10 @@
"minHostVersion": ">=2026.5.12-beta.1"
},
"compat": {
"pluginApi": ">=2026.6.1"
"pluginApi": ">=2026.6.2"
},
"build": {
"openclawVersion": "2026.6.1",
"openclawVersion": "2026.6.2",
"bundledDist": false
},
"release": {

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/anthropic-provider",
"version": "2026.6.1",
"version": "2026.6.2",
"private": true,
"description": "OpenClaw Anthropic provider plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/arcee-provider",
"version": "2026.6.1",
"version": "2026.6.2",
"private": true,
"description": "OpenClaw Arcee provider plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/azure-speech",
"version": "2026.6.1",
"version": "2026.6.2",
"private": true,
"description": "OpenClaw Azure Speech plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/bonjour",
"version": "2026.6.1",
"version": "2026.6.2",
"description": "OpenClaw Bonjour/mDNS gateway discovery",
"type": "module",
"dependencies": {

View File

@@ -1,12 +1,12 @@
{
"name": "@openclaw/brave-plugin",
"version": "2026.6.1",
"version": "2026.6.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@openclaw/brave-plugin",
"version": "2026.6.1"
"version": "2026.6.2"
}
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/brave-plugin",
"version": "2026.6.1",
"version": "2026.6.2",
"description": "OpenClaw Brave Search provider plugin for web search.",
"repository": {
"type": "git",
@@ -21,10 +21,10 @@
"allowInvalidConfigRecovery": true
},
"compat": {
"pluginApi": ">=2026.6.1"
"pluginApi": ">=2026.6.2"
},
"build": {
"openclawVersion": "2026.6.1"
"openclawVersion": "2026.6.2"
},
"release": {
"publishToClawHub": true,

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/browser-plugin",
"version": "2026.6.1",
"version": "2026.6.2",
"private": true,
"description": "OpenClaw browser tool plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/byteplus-provider",
"version": "2026.6.1",
"version": "2026.6.2",
"private": true,
"description": "OpenClaw BytePlus provider plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/canvas-plugin",
"version": "2026.6.1",
"version": "2026.6.2",
"private": true,
"description": "OpenClaw Canvas plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/cerebras-provider",
"version": "2026.6.1",
"version": "2026.6.2",
"private": true,
"description": "OpenClaw Cerebras provider plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/chutes-provider",
"version": "2026.6.1",
"version": "2026.6.2",
"private": true,
"description": "OpenClaw Chutes.ai provider plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/clickclack",
"version": "2026.6.1",
"version": "2026.6.2",
"private": true,
"description": "OpenClaw ClickClack channel plugin",
"type": "module",
@@ -18,7 +18,7 @@
"openclaw": "2026.5.28"
},
"peerDependencies": {
"openclaw": ">=2026.6.1"
"openclaw": ">=2026.6.2"
},
"peerDependenciesMeta": {
"openclaw": {

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/cloudflare-ai-gateway-provider",
"version": "2026.6.1",
"version": "2026.6.2",
"private": true,
"description": "OpenClaw Cloudflare AI Gateway provider plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/codex-supervisor",
"version": "2026.6.1",
"version": "2026.6.2",
"private": true,
"description": "OpenClaw Codex app-server fleet supervision plugin.",
"type": "module",

View File

@@ -1,12 +1,12 @@
{
"name": "@openclaw/codex",
"version": "2026.6.1",
"version": "2026.6.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@openclaw/codex",
"version": "2026.6.1",
"version": "2026.6.2",
"dependencies": {
"@openai/codex": "0.135.0",
"typebox": "1.1.39",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/codex",
"version": "2026.6.1",
"version": "2026.6.2",
"description": "OpenClaw Codex app-server harness and model provider plugin with a Codex-managed GPT catalog.",
"repository": {
"type": "git",
@@ -26,10 +26,10 @@
"minHostVersion": ">=2026.5.1-beta.1"
},
"compat": {
"pluginApi": ">=2026.6.1"
"pluginApi": ">=2026.6.2"
},
"build": {
"openclawVersion": "2026.6.1"
"openclawVersion": "2026.6.2"
},
"release": {
"publishToClawHub": true,

View File

@@ -266,6 +266,7 @@ export async function startCodexAttemptThread(params: {
mcpServersFingerprintEvaluated: params.bundleMcpThreadConfig.evaluated,
environmentSelection: startupEnvironmentSelection,
contextEngineProjection: params.contextEngineProjection,
signal: params.signal,
pluginThreadConfig: pluginThreadConfigRequired
? {
enabled: true,

View File

@@ -18,6 +18,8 @@ import {
import {
filterCodexDynamicTools,
resolveCodexDynamicToolsLoading,
resolveCodexDynamicToolsLoadingForModel,
shouldUseDirectCodexDynamicToolsForModel,
} from "./dynamic-tool-profile.js";
import { createCodexDynamicToolBridge } from "./dynamic-tools.js";
import { createCodexTestModel } from "./test-support.js";
@@ -179,6 +181,22 @@ describe("Codex app-server dynamic tool build", () => {
expect(resolveCodexDynamicToolsLoading({}, privateQaCodexEnv)).toBe("direct");
});
it("uses direct dynamic tools for OpenAI nano models without tool_search support", () => {
const tools = [createRuntimeDynamicTool("message"), createRuntimeDynamicTool("web_search")];
const toolBridge = createCodexDynamicToolBridge({
tools,
signal: new AbortController().signal,
loading: resolveCodexDynamicToolsLoadingForModel({}, "openai/gpt-5.4-nano"),
});
expect(shouldUseDirectCodexDynamicToolsForModel("gpt-5.4-nano")).toBe(true);
expect(resolveCodexDynamicToolsLoadingForModel({}, "gpt-5.4-nano")).toBe("direct");
expect(resolveCodexDynamicToolsLoadingForModel({}, "gpt-5.5")).toBe("searchable");
const webSearch = toolBridge.specs.find((tool) => tool.name === "web_search");
expect(webSearch).not.toHaveProperty("deferLoading");
expect(webSearch).not.toHaveProperty("namespace");
});
it("quarantines unreadable tool entries before Codex-specific filtering", async () => {
const messageTool = createRuntimeDynamicTool("message");
const sourceTools = new Proxy([messageTool] as RuntimeDynamicToolForTest[], {

View File

@@ -47,6 +47,33 @@ export function resolveCodexDynamicToolsLoading(
: (config.codexDynamicToolsLoading ?? "searchable");
}
function normalizeCodexModelId(modelId: string | undefined): string {
const normalized = modelId?.trim().toLowerCase();
if (!normalized) {
return "";
}
return normalized.includes("/") ? normalized.split("/").at(-1)! : normalized;
}
export function shouldUseDirectCodexDynamicToolsForModel(modelId: string | undefined): boolean {
return shouldDisableCodexToolSearchForModel(modelId);
}
export function shouldDisableCodexToolSearchForModel(modelId: string | undefined): boolean {
return normalizeCodexModelId(modelId) === "gpt-5.4-nano";
}
export function resolveCodexDynamicToolsLoadingForModel(
config: Pick<CodexPluginConfig, "codexDynamicToolsLoading">,
modelId: string | undefined,
env: CodexDynamicToolProfileEnv = process.env,
): CodexDynamicToolsLoading {
const loading = resolveCodexDynamicToolsLoading(config, env);
return loading === "searchable" && shouldUseDirectCodexDynamicToolsForModel(modelId)
? "direct"
: loading;
}
export function filterCodexDynamicTools<T extends { name: string }>(
tools: T[],
config: Pick<CodexPluginConfig, "codexDynamicToolsExclude">,

View File

@@ -1652,6 +1652,81 @@ describe("CodexAppServerEventProjector", () => {
});
});
it("fails closed when a native tool call finishes without a matching result", async () => {
const trajectoryRecorder = {
filePath: "trajectory.jsonl",
recordEvent: vi.fn(),
flush: vi.fn(async () => undefined),
};
const projector = await createProjector(await createParams(), { trajectoryRecorder });
await projector.handleNotification(
forCurrentTurn("item/started", {
item: {
type: "commandExecution",
id: "cmd-denied",
command: "node scripts/report.js --publish",
cwd: "/workspace",
processId: null,
source: "agent",
status: "inProgress",
commandActions: [],
aggregatedOutput: null,
exitCode: null,
durationMs: null,
},
}),
);
await projector.handleNotification(
turnCompleted([
{
type: "agentMessage",
id: "msg-denied",
text: "The requested publish command was denied before execution.",
},
]),
);
const result = projector.buildResult(buildEmptyToolTelemetry());
expect(String(result.promptError)).toContain("without a matching tool.result");
expect(result.promptErrorSource).toBe("prompt");
expect(result.messagesSnapshot.map((message) => message.role)).toEqual([
"user",
"assistant",
"toolResult",
"assistant",
]);
const toolResultMessage = requireRecord(result.messagesSnapshot[2], "tool result message");
expect(toolResultMessage.toolCallId).toBe("cmd-denied");
expect(toolResultMessage.toolName).toBe("bash");
expect(toolResultMessage.isError).toBe(true);
const toolResultContent = requireArray(toolResultMessage.content, "tool result content");
expect(JSON.stringify(toolResultContent)).toContain("matching tool.result");
expect(trajectoryRecorder.recordEvent).toHaveBeenCalledWith("tool.call", {
threadId: THREAD_ID,
turnId: TURN_ID,
itemId: "cmd-denied",
toolCallId: "cmd-denied",
name: "bash",
arguments: {
command: "node scripts/report.js --publish",
cwd: "/workspace",
},
});
expect(trajectoryRecorder.recordEvent).toHaveBeenCalledWith("tool.result", {
threadId: THREAD_ID,
turnId: TURN_ID,
itemId: "cmd-denied",
toolCallId: "cmd-denied",
name: "bash",
status: "failed",
isError: true,
result: { status: "failed", reason: "missing_tool_result" },
output: expect.stringContaining("without a matching tool.result"),
});
});
it("uses streamed command output when final command snapshots omit aggregated output", async () => {
const onAgentEvent = vi.fn();
const trajectoryRecorder = {

View File

@@ -109,6 +109,8 @@ const CODEX_PROMPT_TOTAL_INPUT_KEYS = [
const MAX_TOOL_OUTPUT_DELTA_MESSAGES_PER_ITEM = 20;
const TOOL_TRANSCRIPT_OUTPUT_MAX_CHARS = 12_000;
const MISSING_TOOL_RESULT_ERROR =
"OpenClaw recorded a native Codex tool.call without a matching tool.result before the turn completed.";
const GENERATED_IMAGE_MEDIA_SUBDIR = "tool-image-generation";
const BYTES_PER_MB = 1024 * 1024;
// Match OpenClaw's default image media cap for generated image tool outputs.
@@ -172,6 +174,10 @@ export class CodexAppServerEventProjector {
private readonly toolTranscriptMessages: AgentMessage[] = [];
private readonly toolTranscriptCallIds = new Set<string>();
private readonly toolTranscriptResultIds = new Set<string>();
private readonly toolTranscriptNamesById = new Map<string, string>();
private readonly toolTrajectoryCallIds = new Set<string>();
private readonly toolTrajectoryResultIds = new Set<string>();
private readonly toolTrajectoryNamesById = new Map<string, string>();
private readonly transcriptToolProgressCallIds = new Set<string>();
private lastNativeToolError: EmbeddedRunAttemptResult["lastToolError"];
private readonly nativeGeneratedMediaUrls = new Set<string>();
@@ -185,6 +191,7 @@ export class CodexAppServerEventProjector {
private completedTurn: CodexTurn | undefined;
private promptError: unknown;
private promptErrorSource: EmbeddedRunAttemptResult["promptErrorSource"] = null;
private synthesizedMissingToolResultError: string | null = null;
private aborted = false;
private tokenUsage: ReturnType<typeof normalizeUsage>;
private guardianReviewCount = 0;
@@ -285,6 +292,12 @@ export class CodexAppServerEventProjector {
this.reasoningItemOrder,
).join("\n\n");
const planText = collectTextValues(this.planTextByItem).join("\n\n");
this.synthesizeMissingToolResults({
failClosed:
!this.completedTurn ||
this.completedTurn.status !== "completed" ||
assistantTexts.length > 0,
});
const lastAssistant =
assistantTexts.length > 0
? this.createAssistantMessage(assistantTexts.join("\n\n"))
@@ -328,6 +341,7 @@ export class CodexAppServerEventProjector {
const turnFailed = this.completedTurn?.status === "failed";
const promptError =
this.promptError ??
this.synthesizedMissingToolResultError ??
(turnFailed ? (this.completedTurn?.error?.message ?? "codex app-server turn failed") : null);
const agentHarnessResultClassification = classifyAgentHarnessTerminalOutcome({
assistantTexts,
@@ -1125,6 +1139,8 @@ export class CodexAppServerEventProjector {
status: ReturnType<typeof itemStatus>;
}): void {
if (params.phase === "start") {
this.toolTrajectoryCallIds.add(params.item.id);
this.toolTrajectoryNamesById.set(params.item.id, params.name);
this.options.trajectoryRecorder?.recordEvent("tool.call", {
threadId: this.threadId,
turnId: this.turnId,
@@ -1135,6 +1151,7 @@ export class CodexAppServerEventProjector {
});
return;
}
this.toolTrajectoryResultIds.add(params.item.id);
const toolResult = itemToolResult(params.item).result;
const output = itemOutputText(params.item, this.toolResultOutputTextByItem);
this.options.trajectoryRecorder?.recordEvent("tool.result", {
@@ -1396,6 +1413,7 @@ export class CodexAppServerEventProjector {
return;
}
this.toolTranscriptCallIds.add(params.id);
this.toolTranscriptNamesById.set(params.id, params.name);
this.toolTranscriptArgumentsById.set(params.id, params.arguments);
if (!shouldEmitTranscriptToolProgress(params.name, params.arguments)) {
this.transcriptToolProgressSuppressedIds.add(params.id);
@@ -1425,6 +1443,61 @@ export class CodexAppServerEventProjector {
);
}
private synthesizeMissingToolResults(params: { failClosed: boolean }): void {
if (!params.failClosed) {
return;
}
const missingTranscriptIds = [...this.toolTranscriptCallIds].filter(
(id) => !this.toolTranscriptResultIds.has(id),
);
const missingTrajectoryIds = [...this.toolTrajectoryCallIds].filter(
(id) => !this.toolTrajectoryResultIds.has(id),
);
if (missingTranscriptIds.length === 0 && missingTrajectoryIds.length === 0) {
return;
}
for (const id of missingTranscriptIds) {
const name = this.toolTranscriptNamesById.get(id) ?? this.toolTrajectoryNamesById.get(id);
if (!name) {
continue;
}
this.recordToolTranscriptResult({
id,
name,
text: formatMissingToolResultError({ id, name }),
isError: true,
});
}
for (const id of missingTrajectoryIds) {
const name = this.toolTrajectoryNamesById.get(id) ?? this.toolTranscriptNamesById.get(id);
if (!name) {
continue;
}
this.toolTrajectoryResultIds.add(id);
const text = formatMissingToolResultError({ id, name });
this.options.trajectoryRecorder?.recordEvent("tool.result", {
threadId: this.threadId,
turnId: this.turnId,
itemId: id,
toolCallId: id,
name,
status: "failed",
isError: true,
result: { status: "failed", reason: "missing_tool_result" },
output: text,
});
}
const missingCount = new Set([...missingTranscriptIds, ...missingTrajectoryIds]).size;
this.synthesizedMissingToolResultError =
missingCount === 1
? MISSING_TOOL_RESULT_ERROR
: `${MISSING_TOOL_RESULT_ERROR} missingToolResultCount=${missingCount}`;
this.promptErrorSource = this.promptErrorSource ?? "prompt";
}
private emitTranscriptToolCallProgress(params: ToolTranscriptCallInput): void {
if (!shouldEmitTranscriptToolProgress(params.name, params.arguments)) {
return;
@@ -1954,6 +2027,10 @@ function itemStatus(item: CodexThreadItem): "completed" | "failed" | "running" |
return "completed";
}
function formatMissingToolResultError(params: { id: string; name: string }): string {
return `${MISSING_TOOL_RESULT_ERROR} toolCallId=${params.id}; toolName=${params.name}`;
}
function isNonSuccessItemStatus(status: ReturnType<typeof itemStatus>): boolean {
return status === "failed" || status === "blocked";
}

View File

@@ -165,7 +165,7 @@ import {
} from "./dynamic-tool-execution.js";
import {
filterCodexDynamicTools,
resolveCodexDynamicToolsLoading,
resolveCodexDynamicToolsLoadingForModel,
} from "./dynamic-tool-profile.js";
import { createCodexDynamicToolBridge } from "./dynamic-tools.js";
import { handleCodexAppServerElicitationRequest } from "./elicitation-bridge.js";
@@ -595,7 +595,7 @@ export async function runCodexAppServerAttempt(
tools,
registeredTools,
signal: runAbortController.signal,
loading: resolveCodexDynamicToolsLoading(pluginConfig),
loading: resolveCodexDynamicToolsLoadingForModel(pluginConfig, params.modelId),
directToolNames: shouldForceMessageTool(params) ? ["message"] : [],
hookContext: {
agentId: sessionAgentId,
@@ -2640,7 +2640,7 @@ export const testing = {
buildDynamicTools,
filterCodexDynamicToolsForAllowlist,
includeForcedCodexDynamicToolAllow,
resolveCodexDynamicToolsLoading,
resolveCodexDynamicToolsLoadingForModel,
resolveCodexAppServerHookChannelId,
buildCodexAppServerPromptTimeoutOutcome,
resolveOpenClawCodingToolsSessionKeys,

View File

@@ -40,6 +40,7 @@ export type CodexAppServerThreadBinding = {
sandbox?: CodexAppServerSandboxMode;
serviceTier?: CodexServiceTier;
dynamicToolsFingerprint?: string;
dynamicToolsContainDeferred?: boolean;
userMcpServersFingerprint?: string;
mcpServersFingerprint?: string;
nativeHookRelayGeneration?: string;
@@ -111,6 +112,10 @@ export async function readCodexAppServerBinding(
typeof parsed.dynamicToolsFingerprint === "string"
? parsed.dynamicToolsFingerprint
: undefined,
dynamicToolsContainDeferred:
typeof parsed.dynamicToolsContainDeferred === "boolean"
? parsed.dynamicToolsContainDeferred
: undefined,
userMcpServersFingerprint:
typeof parsed.userMcpServersFingerprint === "string"
? parsed.userMcpServersFingerprint
@@ -170,6 +175,7 @@ export async function writeCodexAppServerBinding(
sandbox: binding.sandbox,
serviceTier: binding.serviceTier,
dynamicToolsFingerprint: binding.dynamicToolsFingerprint,
dynamicToolsContainDeferred: binding.dynamicToolsContainDeferred,
userMcpServersFingerprint: binding.userMcpServersFingerprint,
mcpServersFingerprint: binding.mcpServersFingerprint,
nativeHookRelayGeneration: binding.nativeHookRelayGeneration,

View File

@@ -63,6 +63,16 @@ function createNamedDynamicTool(
};
}
function createDeferredNamedDynamicTool(
name: string,
): Parameters<typeof startOrResumeThread>[0]["dynamicTools"][number] {
return {
...createNamedDynamicTool(name),
namespace: "openclaw",
deferLoading: true,
};
}
function createPluginAppConfigPatch() {
return {
apps: {
@@ -169,6 +179,42 @@ function createTwoCalendarAppPolicyContext() {
setupRunAttemptTestHooks();
describe("Codex app-server thread lifecycle bindings", () => {
it("does not write a binding when thread start resolves after abort", async () => {
const sessionFile = path.join(tempDir, "session.jsonl");
const workspaceDir = path.join(tempDir, "workspace");
const params = createParams(sessionFile, workspaceDir);
const appServer = createThreadLifecycleAppServerOptions();
const abortController = new AbortController();
let resolveStart: ((value: ReturnType<typeof threadStartResult>) => void) | undefined;
const request = vi.fn(async (method: string) => {
if (method === "thread/start") {
return await new Promise<ReturnType<typeof threadStartResult>>((resolve) => {
resolveStart = resolve;
});
}
throw new Error(`unexpected method: ${method}`);
});
const run = startOrResumeThread({
client: { request } as never,
params,
cwd: workspaceDir,
dynamicTools: [],
appServer,
signal: abortController.signal,
});
await vi.waitFor(() =>
expect(request).toHaveBeenCalledWith("thread/start", expect.any(Object), {
signal: abortController.signal,
}),
);
abortController.abort("test_abort");
resolveStart?.(threadStartResult("thread-after-abort"));
await expect(run).rejects.toThrow("test_abort");
await expect(readCodexAppServerBinding(sessionFile)).resolves.toBeUndefined();
});
it("resumes a bound Codex thread when only dynamic tool descriptions change", async () => {
const sessionFile = path.join(tempDir, "session.jsonl");
const workspaceDir = path.join(tempDir, "workspace");
@@ -207,6 +253,42 @@ describe("Codex app-server thread lifecycle bindings", () => {
expect(request.mock.calls.map(([method]) => method)).toEqual(["thread/start", "thread/resume"]);
});
it("starts a fresh Codex thread when dynamic tools switch from deferred to direct", async () => {
const sessionFile = path.join(tempDir, "session.jsonl");
const workspaceDir = path.join(tempDir, "workspace");
const params = createParams(sessionFile, workspaceDir);
const appServer = createThreadLifecycleAppServerOptions();
let starts = 0;
const request = vi.fn(async (method: string) => {
if (method === "thread/start") {
starts += 1;
return threadStartResult(`thread-${starts}`);
}
if (method === "thread/resume") {
return threadStartResult("thread-existing");
}
throw new Error(`unexpected method: ${method}`);
});
await startOrResumeThread({
client: { request } as never,
params,
cwd: workspaceDir,
dynamicTools: [createDeferredNamedDynamicTool("web_search")],
appServer,
});
const binding = await startOrResumeThread({
client: { request } as never,
params,
cwd: workspaceDir,
dynamicTools: [createNamedDynamicTool("web_search")],
appServer,
});
expect(binding.threadId).toBe("thread-2");
expect(request.mock.calls.map(([method]) => method)).toEqual(["thread/start", "thread/start"]);
});
it("resumes a bound Codex thread when dynamic tools are reordered", async () => {
const sessionFile = path.join(tempDir, "session.jsonl");
const workspaceDir = path.join(tempDir, "workspace");
@@ -453,7 +535,7 @@ describe("Codex app-server thread lifecycle bindings", () => {
client: { request } as never,
params,
cwd: workspaceDir,
dynamicTools: [createMessageDynamicTool("Send and manage messages.")],
dynamicTools: [createDeferredNamedDynamicTool("message")],
appServer,
});
const fingerprint = (await readCodexAppServerBinding(sessionFile))?.dynamicToolsFingerprint;
@@ -468,12 +550,13 @@ describe("Codex app-server thread lifecycle bindings", () => {
client: { request } as never,
params,
cwd: workspaceDir,
dynamicTools: [createMessageDynamicTool("Send and manage messages.")],
dynamicTools: [createDeferredNamedDynamicTool("message")],
appServer,
});
const binding = await readCodexAppServerBinding(sessionFile);
expect(binding?.dynamicToolsFingerprint).toBe(fingerprint);
expect(binding?.dynamicToolsContainDeferred).toBe(true);
expect(binding?.threadId).toBe("thread-1");
expect(request.mock.calls.map(([method]) => method)).toEqual([
"thread/start",

View File

@@ -21,6 +21,7 @@ function createAttemptParams(params: {
bootstrapContextMode?: "full" | "lightweight";
bootstrapContextRunKind?: "default" | "heartbeat" | "cron";
images?: EmbeddedRunAttemptParams["images"];
modelId?: string;
}): EmbeddedRunAttemptParams {
const authProfileProviders =
params.authProfileProviders ??
@@ -30,7 +31,7 @@ function createAttemptParams(params: {
const authProfileType = params.authProfileType ?? "oauth";
return {
provider: params.provider,
modelId: "gpt-5.4",
modelId: params.modelId ?? "gpt-5.4",
prompt: "test prompt",
authProfileId: params.authProfileId,
...(params.bootstrapContextMode ? { bootstrapContextMode: params.bootstrapContextMode } : {}),
@@ -151,7 +152,7 @@ describe("Codex app-server native code mode config", () => {
expect(instructions).not.toContain("Deferred searchable OpenClaw dynamic tools available");
});
it("keeps durable dynamic tool fingerprints independent from presentation mode", () => {
it("keeps durable dynamic tool fingerprints scoped to loading mode", () => {
const inputSchema = {
type: "object",
additionalProperties: false,
@@ -177,7 +178,7 @@ describe("Codex app-server native code mode config", () => {
},
]);
expect(searchableFingerprint).toBe(directFingerprint);
expect(searchableFingerprint).not.toBe(directFingerprint);
});
it("keeps OpenClaw skill catalogs out of developer instructions", () => {
@@ -214,6 +215,25 @@ describe("Codex app-server native code mode config", () => {
expect(request.personality).toBe("none");
});
it("disables Codex tool-search features for nano models", () => {
const request = buildThreadStartParams(
createAttemptParams({ provider: "openai", modelId: "gpt-5.4-nano" }),
{
cwd: "/repo",
dynamicTools: [],
appServer: createAppServerOptions() as never,
developerInstructions: "test instructions",
},
);
expect(request.config).toEqual({
"features.code_mode": true,
"features.code_mode_only": false,
"features.apply_patch_streaming_events": true,
"features.multi_agent": false,
});
});
it("removes Codex model personality on thread/resume", () => {
const request = buildThreadResumeParams(createAttemptParams({ provider: "openai" }), {
threadId: "thread-1",

View File

@@ -20,6 +20,7 @@ import {
resolveCodexContextEngineProjectionMaxChars,
resolveCodexContextEngineProjectionReserveTokens,
} from "./context-engine-projection.js";
import { shouldDisableCodexToolSearchForModel } from "./dynamic-tool-profile.js";
import { invalidInlineImageText, sanitizeInlineImageDataUrl } from "./image-payload-sanitizer.js";
import {
isCodexPluginThreadBindingStale,
@@ -114,6 +115,10 @@ const CODEX_LIGHTWEIGHT_CONTEXT_THREAD_CONFIG: JsonObject = {
project_doc_max_bytes: 0,
};
const CODEX_TOOL_SEARCH_UNSUPPORTED_THREAD_CONFIG: JsonObject = {
"features.multi_agent": false,
};
type CodexThreadLifecycleTimingSpan = {
name: string;
durationMs: number;
@@ -243,6 +248,7 @@ export async function startOrResumeThread(params: {
environmentSelection?: CodexTurnEnvironmentParams[];
pluginThreadConfig?: CodexPluginThreadConfigProvider;
contextEngineProjection?: CodexContextEngineThreadBootstrapProjection;
signal?: AbortSignal;
}): Promise<CodexAppServerThreadLifecycleBinding> {
// Thread lifecycle spans are useful when profiling startup churn, but normal
// turns should not pay Date.now/span-array overhead while resuming threads.
@@ -252,6 +258,9 @@ export async function startOrResumeThread(params: {
const dynamicToolsFingerprint = lifecycleTiming.measureSync("fingerprint_dynamic_tools", () =>
fingerprintDynamicTools(params.dynamicTools),
);
const dynamicToolsContainDeferred = params.dynamicTools.some(
(tool) => tool.deferLoading === true,
);
const contextEngineBinding = lifecycleTiming.measureSync("context_engine_binding", () =>
buildContextEngineBinding(params.params, params.contextEngineProjection),
);
@@ -275,6 +284,22 @@ export async function startOrResumeThread(params: {
let preserveExistingBinding = false;
let rotatedContextEngineBinding = false;
let prebuiltPluginThreadConfig: CodexPluginThreadConfig | undefined;
const throwIfAborted = () => {
if (!params.signal?.aborted) {
return;
}
const reason = params.signal.reason;
if (reason instanceof Error) {
throw reason;
}
const error = new Error(
typeof reason === "string" && reason.length > 0
? reason
: "codex app-server thread lifecycle aborted",
);
error.name = "AbortError";
throw error;
};
if (binding?.threadId && params.nativeCodeModeEnabled === false) {
embeddedAgentLog.debug(
"codex app-server native tool surface disabled for turn; starting transient thread",
@@ -387,6 +412,23 @@ export async function startOrResumeThread(params: {
await clearCodexAppServerBinding(params.params.sessionFile);
binding = undefined;
}
if (binding?.threadId) {
if (
binding.dynamicToolsFingerprint &&
params.dynamicTools.length > 0 &&
binding.dynamicToolsContainDeferred !== dynamicToolsContainDeferred &&
(binding.dynamicToolsContainDeferred !== undefined || !dynamicToolsContainDeferred)
) {
embeddedAgentLog.debug(
"codex app-server dynamic tool loading changed; starting a new thread",
{
threadId: binding.threadId,
},
);
await clearCodexAppServerBinding(params.params.sessionFile);
binding = undefined;
}
}
if (binding?.threadId) {
// `/codex resume <thread>` writes a binding before the next turn can know
// the dynamic tool catalog, so only invalidate fingerprints we actually have.
@@ -446,9 +488,10 @@ export async function startOrResumeThread(params: {
);
const response = assertCodexThreadResumeResponse(
await lifecycleTiming.measure("thread_resume_request", () =>
params.client.request("thread/resume", resumeParams),
params.client.request("thread/resume", resumeParams, { signal: params.signal }),
),
);
throwIfAborted();
const boundAuthProfileId = authProfileId;
const fallbackModelProvider = resolveCodexAppServerModelProvider({
provider: params.params.provider,
@@ -471,6 +514,7 @@ export async function startOrResumeThread(params: {
model: params.params.modelId,
modelProvider: response.modelProvider ?? fallbackModelProvider,
dynamicToolsFingerprint,
dynamicToolsContainDeferred,
userMcpServersFingerprint,
mcpServersFingerprint: nextMcpServersFingerprint,
nativeHookRelayGeneration:
@@ -515,6 +559,7 @@ export async function startOrResumeThread(params: {
model: params.params.modelId,
modelProvider: response.modelProvider ?? fallbackModelProvider,
dynamicToolsFingerprint,
dynamicToolsContainDeferred,
userMcpServersFingerprint,
mcpServersFingerprint: nextMcpServersFingerprint,
nativeHookRelayGeneration:
@@ -570,7 +615,7 @@ export async function startOrResumeThread(params: {
);
const threadStartResponse = await lifecycleTiming.measure("thread_start_request", async () => {
try {
return await params.client.request("thread/start", startParams);
return await params.client.request("thread/start", startParams, { signal: params.signal });
} catch (error) {
if (error instanceof CodexAppServerRpcError) {
throw new CodexThreadStartRequestError(error);
@@ -579,6 +624,7 @@ export async function startOrResumeThread(params: {
}
});
const response = assertCodexThreadStartResponse(threadStartResponse);
throwIfAborted();
const modelProvider = resolveCodexAppServerModelProvider({
provider: params.params.provider,
authProfileId: params.params.authProfileId,
@@ -600,6 +646,7 @@ export async function startOrResumeThread(params: {
model: response.model ?? params.params.modelId,
modelProvider: response.modelProvider ?? modelProvider,
dynamicToolsFingerprint,
dynamicToolsContainDeferred,
userMcpServersFingerprint,
mcpServersFingerprint: nextMcpServersFingerprint,
nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration,
@@ -645,6 +692,7 @@ export async function startOrResumeThread(params: {
model: response.model ?? params.params.modelId,
modelProvider: response.modelProvider ?? modelProvider,
dynamicToolsFingerprint,
dynamicToolsContainDeferred,
userMcpServersFingerprint,
mcpServersFingerprint: nextMcpServersFingerprint,
nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration,
@@ -905,7 +953,14 @@ function buildCodexRuntimeThreadConfigForRun(
config: JsonObject | undefined,
options: { nativeCodeModeEnabled?: boolean; nativeCodeModeOnlyEnabled?: boolean } = {},
): JsonObject {
const runtimeConfig = buildCodexRuntimeThreadConfig(config, options);
const baseConfig = buildCodexRuntimeThreadConfig(config, options);
const runtimeConfig =
mergeCodexThreadConfigs(
baseConfig,
shouldDisableCodexToolSearchForModel(params.modelId)
? CODEX_TOOL_SEARCH_UNSUPPORTED_THREAD_CONFIG
: undefined,
) ?? baseConfig;
if (params.bootstrapContextMode !== "lightweight") {
return runtimeConfig;
}
@@ -1095,9 +1150,7 @@ function fingerprintDynamicToolSpec(tool: JsonValue): JsonValue {
for (const [key, child] of Object.entries(tool).toSorted(([left], [right]) =>
left.localeCompare(right),
)) {
// Tool-search presentation can change per turn without changing the
// durable app-server execution contract for an existing thread.
if (key === "description" || key === "deferLoading" || key === "namespace") {
if (key === "description") {
continue;
}
stable[key] = stabilizeJsonValue(child);

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/comfy-provider",
"version": "2026.6.1",
"version": "2026.6.2",
"private": true,
"description": "OpenClaw ComfyUI provider plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/copilot-proxy",
"version": "2026.6.1",
"version": "2026.6.2",
"private": true,
"description": "OpenClaw Copilot Proxy provider plugin",
"type": "module",

View File

@@ -1,12 +1,12 @@
{
"name": "@openclaw/copilot",
"version": "2026.6.1",
"version": "2026.6.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@openclaw/copilot",
"version": "2026.6.1",
"version": "2026.6.2",
"dependencies": {
"@github/copilot-sdk": "1.0.0-beta.9"
}

View File

@@ -2,7 +2,7 @@
"id": "copilot",
"name": "GitHub Copilot agent runtime",
"description": "Registers the GitHub Copilot agent runtime.",
"version": "2026.6.1",
"version": "2026.6.2",
"activation": {
"onStartup": false,
"onAgentHarnesses": ["copilot"]

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/copilot",
"version": "2026.6.1",
"version": "2026.6.2",
"description": "OpenClaw GitHub Copilot agent runtime plugin (registers a `github-copilot` AgentHarness backed by @github/copilot-sdk over JSON-RPC to the GitHub Copilot CLI)",
"repository": {
"type": "git",
@@ -25,10 +25,10 @@
"minHostVersion": ">=2026.5.28"
},
"compat": {
"pluginApi": ">=2026.6.1"
"pluginApi": ">=2026.6.2"
},
"build": {
"openclawVersion": "2026.6.1",
"openclawVersion": "2026.6.2",
"bundledDist": false
},
"release": {

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/deepgram-provider",
"version": "2026.6.1",
"version": "2026.6.2",
"private": true,
"description": "OpenClaw Deepgram media-understanding provider",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/deepinfra-provider",
"version": "2026.6.1",
"version": "2026.6.2",
"private": true,
"description": "OpenClaw DeepInfra provider plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/deepseek-provider",
"version": "2026.6.1",
"version": "2026.6.2",
"private": true,
"description": "OpenClaw DeepSeek provider plugin",
"type": "module",

View File

@@ -1,12 +1,12 @@
{
"name": "@openclaw/diagnostics-otel",
"version": "2026.6.1",
"version": "2026.6.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@openclaw/diagnostics-otel",
"version": "2026.6.1",
"version": "2026.6.2",
"dependencies": {
"@opentelemetry/api": "1.9.1",
"@opentelemetry/api-logs": "0.218.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/diagnostics-otel",
"version": "2026.6.1",
"version": "2026.6.2",
"description": "OpenClaw diagnostics OpenTelemetry exporter for metrics and traces.",
"repository": {
"type": "git",
@@ -34,10 +34,10 @@
"minHostVersion": ">=2026.4.25"
},
"compat": {
"pluginApi": ">=2026.6.1"
"pluginApi": ">=2026.6.2"
},
"build": {
"openclawVersion": "2026.6.1"
"openclawVersion": "2026.6.2"
},
"release": {
"publishToClawHub": true,

View File

@@ -1,12 +1,12 @@
{
"name": "@openclaw/diagnostics-prometheus",
"version": "2026.6.1",
"version": "2026.6.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@openclaw/diagnostics-prometheus",
"version": "2026.6.1"
"version": "2026.6.2"
}
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/diagnostics-prometheus",
"version": "2026.6.1",
"version": "2026.6.2",
"description": "OpenClaw diagnostics Prometheus exporter for runtime metrics.",
"repository": {
"type": "git",
@@ -21,10 +21,10 @@
"minHostVersion": ">=2026.4.25"
},
"compat": {
"pluginApi": ">=2026.6.1"
"pluginApi": ">=2026.6.2"
},
"build": {
"openclawVersion": "2026.6.1"
"openclawVersion": "2026.6.2"
},
"release": {
"publishToClawHub": true,

View File

@@ -1,12 +1,12 @@
{
"name": "@openclaw/diffs-language-pack",
"version": "2026.6.1",
"version": "2026.6.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@openclaw/diffs-language-pack",
"version": "2026.6.1"
"version": "2026.6.2"
}
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/diffs-language-pack",
"version": "2026.6.1",
"version": "2026.6.2",
"description": "OpenClaw diffs viewer syntax highlighting language pack",
"repository": {
"type": "git",
@@ -22,13 +22,13 @@
"minHostVersion": ">=2026.5.27"
},
"compat": {
"pluginApi": ">=2026.6.1"
"pluginApi": ">=2026.6.2"
},
"assetScripts": {
"build": "node ../../scripts/build-diffs-viewer-runtime.mjs full"
},
"build": {
"openclawVersion": "2026.6.1",
"openclawVersion": "2026.6.2",
"staticAssets": [
{
"source": "./assets/viewer-runtime.js",

View File

@@ -1,12 +1,12 @@
{
"name": "@openclaw/diffs",
"version": "2026.6.1",
"version": "2026.6.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@openclaw/diffs",
"version": "2026.6.1",
"version": "2026.6.2",
"dependencies": {
"@pierre/diffs": "1.2.4",
"@pierre/theme": "1.0.3",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/diffs",
"version": "2026.6.1",
"version": "2026.6.2",
"description": "OpenClaw read-only diff viewer plugin and file renderer for agents.",
"repository": {
"type": "git",
@@ -29,13 +29,13 @@
"minHostVersion": ">=2026.4.30"
},
"compat": {
"pluginApi": ">=2026.6.1"
"pluginApi": ">=2026.6.2"
},
"assetScripts": {
"build": "node ../../scripts/build-diffs-viewer-runtime.mjs curated"
},
"build": {
"openclawVersion": "2026.6.1",
"openclawVersion": "2026.6.2",
"staticAssets": [
{
"source": "./assets/viewer-runtime.js",

View File

@@ -1,22 +1,22 @@
{
"name": "@openclaw/discord",
"version": "2026.6.1",
"version": "2026.6.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@openclaw/discord",
"version": "2026.6.1",
"version": "2026.6.2",
"dependencies": {
"@discordjs/voice": "0.19.2",
"discord-api-types": "0.38.48",
"libopus-wasm": "0.1.0",
"libopus-wasm": "0.2.0",
"typebox": "1.1.39",
"undici": "8.3.0",
"ws": "8.21.0"
},
"peerDependencies": {
"openclaw": ">=2026.6.1"
"openclaw": ">=2026.6.2"
},
"peerDependenciesMeta": {
"openclaw": {
@@ -352,9 +352,9 @@
]
},
"node_modules/libopus-wasm": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/libopus-wasm/-/libopus-wasm-0.1.0.tgz",
"integrity": "sha512-/aurGcAVgy0GcBEUzFaX9pm9qv7zYcy8W5hBXFiK+cyqOXAX4lOS6rlFogkY9CcSIajhjnuXyixsbmziSHCDMQ==",
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/libopus-wasm/-/libopus-wasm-0.2.0.tgz",
"integrity": "sha512-x/2Gu1/C6L3IICY09zyfp984AWiOYjn53u4WfdY3yh+3KTzMN8Xkm77q3lenWMVIk5SnSzjGEkQT+VQMFHLBHQ==",
"license": "MIT",
"engines": {
"node": ">=20"

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/discord",
"version": "2026.6.1",
"version": "2026.6.2",
"description": "OpenClaw Discord channel plugin for channels, DMs, commands, and app events.",
"repository": {
"type": "git",
@@ -10,7 +10,7 @@
"dependencies": {
"@discordjs/voice": "0.19.2",
"discord-api-types": "0.38.48",
"libopus-wasm": "0.1.0",
"libopus-wasm": "0.2.0",
"typebox": "1.1.39",
"undici": "8.3.0",
"ws": "8.21.0"
@@ -20,7 +20,7 @@
"openclaw": "2026.5.28"
},
"peerDependencies": {
"openclaw": ">=2026.6.1"
"openclaw": ">=2026.6.2"
},
"peerDependenciesMeta": {
"openclaw": {
@@ -67,10 +67,10 @@
"allowInvalidConfigRecovery": true
},
"compat": {
"pluginApi": ">=2026.6.1"
"pluginApi": ">=2026.6.2"
},
"build": {
"openclawVersion": "2026.6.1"
"openclawVersion": "2026.6.2"
},
"release": {
"publishToClawHub": true,

View File

@@ -1774,6 +1774,10 @@ export class DiscordVoiceManager {
logVoiceVerbose(`receive stream ended: ${analysis.message}`);
return;
}
if (analysis.isDecodeCorruption && !analysis.countsAsDecryptFailure) {
logVoiceVerbose(`receive decode skipped: ${analysis.message}`);
return;
}
logger.warn(`discord voice: receive error: ${analysis.message}`);
if (analysis.shouldAttemptPassthrough) {
this.enableDaveReceivePassthrough(

View File

@@ -1,3 +1,4 @@
import { OpusError, OpusErrorCode } from "libopus-wasm";
import { describe, expect, it, vi } from "vitest";
import {
analyzeVoiceReceiveError,
@@ -15,6 +16,7 @@ describe("voice receive recovery", () => {
).toEqual({
message: "Failed to decrypt: DecryptionFailed(UnencryptedWhenPassthroughDisabled)",
isAbortLike: false,
isDecodeCorruption: false,
shouldAttemptPassthrough: true,
countsAsDecryptFailure: true,
});
@@ -24,15 +26,60 @@ describe("voice receive recovery", () => {
expect(analyzeVoiceReceiveError(new Error("memory access out of bounds"))).toEqual({
message: "memory access out of bounds",
isAbortLike: false,
isDecodeCorruption: false,
shouldAttemptPassthrough: false,
countsAsDecryptFailure: true,
});
});
it("treats corrupt Opus packets as non-recoverable decode noise", () => {
expect(
analyzeVoiceReceiveError(
new OpusError(OpusErrorCode.InvalidPacket, "not inspected", "decode"),
),
).toEqual({
message: "not inspected",
isAbortLike: false,
isDecodeCorruption: true,
shouldAttemptPassthrough: false,
countsAsDecryptFailure: false,
});
});
it("treats structurally equivalent Opus errors as decode corruption", () => {
const analysis = analyzeVoiceReceiveError({
name: "OpusError",
message: "libopus decode failed (-4): corrupted stream",
code: OpusErrorCode.InvalidPacket,
codeName: "InvalidPacket",
operation: "decode",
});
expect(analysis).toMatchObject({
isAbortLike: false,
isDecodeCorruption: true,
shouldAttemptPassthrough: false,
countsAsDecryptFailure: false,
});
});
it("does not classify corrupt Opus packet text without the Opus error contract", () => {
expect(
analyzeVoiceReceiveError(new Error("libopus decode failed (-4): corrupted stream")),
).toEqual({
message: "libopus decode failed (-4): corrupted stream",
isAbortLike: false,
isDecodeCorruption: false,
shouldAttemptPassthrough: false,
countsAsDecryptFailure: false,
});
});
it("treats premature stream close as an expected receive end", () => {
expect(analyzeVoiceReceiveError(new Error("Premature close"))).toEqual({
message: "Premature close",
isAbortLike: true,
isDecodeCorruption: false,
shouldAttemptPassthrough: false,
countsAsDecryptFailure: false,
});

View File

@@ -1,3 +1,4 @@
import { OpusErrorCode, isOpusError } from "libopus-wasm";
import { formatErrorMessage } from "openclaw/plugin-sdk/ssrf-runtime";
const DECRYPT_FAILURE_WINDOW_MS = 30_000;
@@ -18,6 +19,7 @@ export type VoiceReceiveRecoveryState = {
type VoiceReceiveErrorAnalysis = {
message: string;
isAbortLike: boolean;
isDecodeCorruption: boolean;
shouldAttemptPassthrough: boolean;
countsAsDecryptFailure: boolean;
};
@@ -80,13 +82,23 @@ function isAbortLikeReceiveError(err: unknown): boolean {
);
}
function isOpusDecodeInvalidPacketError(err: unknown): boolean {
return (
isOpusError(err) &&
err.code === OpusErrorCode.InvalidPacket &&
(err.operation === "decode" || err.operation === "decodeFloat")
);
}
export function analyzeVoiceReceiveError(err: unknown): VoiceReceiveErrorAnalysis {
const message = formatErrorMessage(err);
const normalizedMessage = message.toLowerCase();
const shouldAttemptPassthrough = message.includes(DAVE_PASSTHROUGH_DISABLED_MARKER);
const isWasmMemoryAccessFailure = message.toLowerCase().includes(WASM_MEMORY_ACCESS_MARKER);
const isWasmMemoryAccessFailure = normalizedMessage.includes(WASM_MEMORY_ACCESS_MARKER);
return {
message,
isAbortLike: isAbortLikeReceiveError(err),
isDecodeCorruption: isOpusDecodeInvalidPacketError(err),
shouldAttemptPassthrough,
countsAsDecryptFailure:
message.includes(DECRYPT_FAILURE_MARKER) ||

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/document-extract-plugin",
"version": "2026.6.1",
"version": "2026.6.2",
"private": true,
"description": "OpenClaw local document extraction plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/duckduckgo-plugin",
"version": "2026.6.1",
"version": "2026.6.2",
"private": true,
"description": "OpenClaw DuckDuckGo plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/elevenlabs-speech",
"version": "2026.6.1",
"version": "2026.6.2",
"private": true,
"description": "OpenClaw ElevenLabs speech plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/exa-plugin",
"version": "2026.6.1",
"version": "2026.6.2",
"private": true,
"description": "OpenClaw Exa plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/fal-provider",
"version": "2026.6.1",
"version": "2026.6.2",
"private": true,
"description": "OpenClaw fal provider plugin",
"type": "module",

View File

@@ -1,19 +1,19 @@
{
"name": "@openclaw/feishu",
"version": "2026.6.1",
"version": "2026.6.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@openclaw/feishu",
"version": "2026.6.1",
"version": "2026.6.2",
"dependencies": {
"@larksuiteoapi/node-sdk": "1.66.0",
"typebox": "1.1.39",
"zod": "4.4.3"
},
"peerDependencies": {
"openclaw": ">=2026.6.1"
"openclaw": ">=2026.6.2"
},
"peerDependenciesMeta": {
"openclaw": {

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/feishu",
"version": "2026.6.1",
"version": "2026.6.2",
"description": "OpenClaw Feishu/Lark channel plugin for chats and workplace tools (community maintained by @m1heng).",
"repository": {
"type": "git",
@@ -17,7 +17,7 @@
"openclaw": "2026.5.28"
},
"peerDependencies": {
"openclaw": ">=2026.6.1"
"openclaw": ">=2026.6.2"
},
"peerDependenciesMeta": {
"openclaw": {
@@ -51,10 +51,10 @@
"minHostVersion": ">=2026.5.29"
},
"compat": {
"pluginApi": ">=2026.6.1"
"pluginApi": ">=2026.6.2"
},
"build": {
"openclawVersion": "2026.6.1"
"openclawVersion": "2026.6.2"
},
"release": {
"publishToClawHub": true,

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/file-transfer",
"version": "2026.6.1",
"version": "2026.6.2",
"description": "OpenClaw file transfer plugin (file_fetch, dir_list, dir_fetch, file_write)",
"type": "module",
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/firecrawl-plugin",
"version": "2026.6.1",
"version": "2026.6.2",
"private": true,
"description": "OpenClaw Firecrawl plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/fireworks-provider",
"version": "2026.6.1",
"version": "2026.6.2",
"private": true,
"description": "OpenClaw Fireworks provider plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/github-copilot-provider",
"version": "2026.6.1",
"version": "2026.6.2",
"private": true,
"description": "OpenClaw GitHub Copilot provider plugin",
"type": "module",

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