Compare commits

...

88 Commits

Author SHA1 Message Date
Peter Steinberger
27ae826f65 fix(release): accept openclaw qa runtime alias 2026-05-28 11:34:39 +01:00
Peter Steinberger
6cdc963096 chore(release): prepare 2026.5.27 stable 2026-05-28 10:54:09 +01:00
Masato Hoshino
a03cd48b22 fix(whatsapp): strip control characters from outbound document fileName (#77114)
Merged via squash.

Prepared head SHA: 5417a8ee2c
Co-authored-by: masatohoshino <246810661+masatohoshino@users.noreply.github.com>
Co-authored-by: mcaxtr <7562095+mcaxtr@users.noreply.github.com>
Reviewed-by: @mcaxtr
2026-05-28 10:47:59 +01:00
Vincent Koc
5ba6a590d9 fix(media): drain ignored download responses 2026-05-28 10:47:59 +01:00
Vincent Koc
9de88d4986 fix(media): cancel ignored input fetch bodies 2026-05-28 10:47:59 +01:00
Vincent Koc
b317664781 fix(media): cancel oversized fetch responses 2026-05-28 10:47:59 +01:00
amittell
66a5748352 fix(telegram): lower polling keepalive delay (#83304)
* fix(telegram): enable TCP keepalive on getUpdates connections to prevent NAT timeout stalls

Long-polling connections to api.telegram.org stay idle for up to the
getUpdates timeout (~900 s). Most home/office NAT tables expire idle TCP
entries after 60–1800 s (commonly ~1000 s). When the NAT entry is
silently dropped the connection hangs rather than returning an error,
leaving the grammY runner stuck until the 90 s stall watchdog fires and
forces a restart cycle.

Fix: unconditionally set `keepAlive: true` and
`keepAliveInitialDelay: 30_000` (30 s) on the undici Agent `connect`
options built in `buildTelegramConnectOptions`. OS-level TCP keepalive
probes sent every ~75 s (OS default) will:
1. Refresh the NAT table entry before it expires.
2. Surface dead connections immediately with ETIMEDOUT instead of
   hanging forever.

The `return Object.keys(connect).length > 0 ? connect : null` guard is
also removed; `connect` is now always non-empty so it always returns the
object.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
(cherry picked from commit 92e454c0614256201cdf6f0f73c7897d006616d4)

* fix(telegram): stop self-flagging disconnected on poll-cycle start; widen channel connect grace to 300s

(cherry picked from commit 1ca963a05dac0d9d605e9a15dc97fced9cf7725e)

* fix(telegram): catch hung polling startups that preserve inherited connected:true

The widened 300s channel connect grace and the removal of connected:false from
notePollingStart left a path where a polling restart could hang forever
looking healthy. notePollingStart clears lastConnectedAt, lastEventAt, and
lastTransportActivityAt but deliberately omits connected, so server-channels'
patch-merge inherits a connected:true from the previous lifecycle. After grace,
evaluateChannelHealth's stale-socket branch requires lastTransportActivityAt
to be non-null and the connected:false branch is masked, so the channel sits
healthy with no first getUpdates.

Add a post-grace branch to evaluateChannelHealth that flags polling channels
as stale-socket when connected:true is paired with null lastConnectedAt and
null lastTransportActivityAt and a non-null lastStartAt. Scoped to mode:polling
so webhook channels and channels without continuous transport tracking are
not falsely flagged. Align TELEGRAM_POLLING_CONNECT_GRACE_MS in the Telegram
status diagnostic with DEFAULT_CHANNEL_CONNECT_GRACE_MS so openclaw channels
status agrees with the shared health monitor on the grace window. Refresh
the notePollingStart comment to point at the new evaluateChannelHealth branch.

Addresses clawsweeper review on #83304 (P1 connect-grace startup-hang, P2
diagnostic grace drift). Tests cover the new flagged path, the in-grace happy
path, and the prior-successful-connect happy path.

* fix(telegram): clear polling connected state on startup

* fix(gateway): add defense-in-depth health-policy branch for hung polling startups

Defense in depth on top of 87db46c576's notePollingStart connected:false fix.
The primary path (notePollingStart writes connected:false explicitly so
evaluateChannelHealth's existing connected===false branch catches a hung
restart) is unchanged. This adds a defensive post-grace branch that catches
the same hang via a different signature -- inherited connected:true paired
with null lastConnectedAt and null lastTransportActivityAt -- in case a
future code path forgets to clear the inherited connected flag on lifecycle
start. Scoped to mode:polling so webhook channels and channels without
continuous transport tracking are not falsely flagged.

Also bump lastStartAt: Date.now() - 121_000 to 301_000 in the spool-handler
timeout test added by upstream #83505 so it falls past the widened 300s
TELEGRAM_POLLING_CONNECT_GRACE_MS suppression window (mirroring the same
fixup already applied to the two adjacent polling-startup tests).

* revert(telegram,gateway): keep connect grace at 120s

Drop the 120s -> 300s widening from this PR after maintainer feedback that
the extra grace masks real startup bugs. The defense-in-depth checks added
in earlier commits (notePollingStart clearing inherited connected state,
the stale-socket policy branch, the per-snapshot startup grace test) all
work fine at 120s and remain valuable on their own.

Reverts in:
- src/gateway/channel-health-policy.ts: DEFAULT_CHANNEL_CONNECT_GRACE_MS 300 -> 120
- extensions/telegram/src/status-issues.ts: TELEGRAM_POLLING_CONNECT_GRACE_MS 300 -> 120
- extensions/telegram/src/status.test.ts: lastStartAt 301_000 -> 121_000 (3 cases)

The new channel-health-policy.test.ts cases use explicit channelConnectGraceMs:
10_000 in the policy, so they are unaffected by the default constant change.

* fix(telegram): narrow polling keepalive fix

---------

Co-authored-by: Yibei Ou <yibeiou@Yibeis-Mac-mini.local>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-05-28 10:47:59 +01:00
Peter Steinberger
6aea967924 fix(parallels): guard release target harness mismatch 2026-05-28 10:06:48 +01:00
Peter Steinberger
0b92a8e6e3 fix(release): accept openclaw runtime alias 2026-05-28 09:17:37 +01:00
Peter Steinberger
1e5ff65770 docs(skills): refine beta release announcement guidance 2026-05-28 09:03:40 +01:00
Peter Steinberger
10f1026588 docs(skills): add OpenClaw release announcement guide 2026-05-28 09:03:40 +01:00
Peter Steinberger
b135369099 fix(release): pin ClawHub publish workdir 2026-05-28 08:36:04 +01:00
Peter Steinberger
a9aaeeafd9 test(release): satisfy cross-os socket lint 2026-05-28 05:54:34 +01:00
Peter Steinberger
1955319f87 fix(release): speed windows upgrade fallback 2026-05-28 05:53:40 +01:00
Peter Steinberger
15ff646f89 fix(release): close cross-os artifact sockets 2026-05-28 05:42:28 +01:00
Peter Steinberger
e34d4a5924 fix(ci): bound optional performance report publishing 2026-05-28 05:31:56 +01:00
Peter Steinberger
7fdf0060d7 fix(qa): keep embedded abort settle stable in qa 2026-05-28 04:52:58 +01:00
Vincent Koc
91da19643b fix(release): stream cross-os served artifacts
(cherry picked from commit 8e8445905f)
2026-05-28 04:31:44 +01:00
Peter Steinberger
6d97108b6c fix(release): keep configured channels visible in fast status 2026-05-28 03:37:00 +01:00
Peter Steinberger
c185c44754 fix(status): preserve channel rows when status skips secrets 2026-05-28 03:18:42 +01:00
Peter Steinberger
ff613c5ccc fix(release): preserve sdk retry-after cap parsing 2026-05-28 02:53:36 +01:00
Peter Steinberger
8d552cef45 test(release): align retry-after cap expectation 2026-05-28 02:50:54 +01:00
Peter Steinberger
6ebed52d6a fix(provider): parse retry-after numeric headers strictly 2026-05-28 02:47:29 +01:00
Peter Steinberger
986baf9c9f test(release): mirror openai gpt-5.5 fallback 2026-05-28 02:30:13 +01:00
Peter Steinberger
62ed7bcaf2 test(imessage): wait for async media policy check 2026-05-28 02:25:04 +01:00
Peter Steinberger
c3154c7b14 test(release): align settled hook test types 2026-05-28 02:09:39 +01:00
Peter Steinberger
c81a1f94b6 fix(perf): seed config fixture for CLI startup bench 2026-05-28 01:59:30 +01:00
Peter Steinberger
3ff769fe91 chore(release): refresh plugin SDK API baseline 2026-05-28 01:47:32 +01:00
Peter Steinberger
791988fafc docs(changelog): mention Discord recovered warning backport 2026-05-28 01:45:17 +01:00
Peter Steinberger
89e0c80a98 fix(discord): backport recovered tool warning suppression (#87452)
* fix(discord): suppress recovered tool warnings (#87451)

(cherry picked from commit da279041ab)

* test(discord): use reply payload SDK test helper

* build(plugin-sdk): exclude reply payload test helper

* fix(discord): fence recovered tool warning fallback

* fix(discord): keep warning fallback after failed final

* fix(reply): keep settled cleanup unconditional
2026-05-28 01:41:43 +01:00
Peter Steinberger
a33bcd7e79 fix(doctor): drop unused legacy embedded helper 2026-05-28 01:07:25 +01:00
Peter Steinberger
0e84e53c30 ci(release): restore docker runtime assets preflight 2026-05-28 01:00:13 +01:00
Peter Steinberger
40bf4475f7 chore(release): sync plugin beta shrinkwraps 2026-05-28 00:58:48 +01:00
Peter Steinberger
0bce1bcc1c chore(release): prepare 2026.5.27-beta.1 2026-05-28 00:48:22 +01:00
Peter Steinberger
edb630623b docs(changelog): note release branch backports 2026-05-28 00:44:35 +01:00
Peter Steinberger
66aff8611e fix(oauth): bound GitHub Copilot requests 2026-05-28 00:44:35 +01:00
Patrick Erichsen
17deed0a63 fix(cli): respect subcommand version options (#87398)
* fix(cli): respect subcommand version options

* test: stabilize model directive auth status
2026-05-28 00:44:35 +01:00
Gio Della-Libera
46c5d749ab fix(doctor): make restart follow-up actionable (#87361) 2026-05-28 00:44:35 +01:00
Vincent Koc
397a9347f9 fix(codex): report quarantined dynamic tools 2026-05-28 00:44:35 +01:00
Agustin Rivera
9769949449 fix(msteams): block untrusted Teams service URLs (#87334) 2026-05-28 00:44:34 +01:00
Mariano Belinky
bf14fce7c0 fix: migrate legacy memory auto provider 2026-05-28 00:44:34 +01:00
Peter Steinberger
3bc5377717 fix: preserve retry-after fallback 2026-05-28 00:44:34 +01:00
Vincent Koc
4ad2090388 fix(openai): resolve gpt-5.5 without cached catalog 2026-05-28 00:44:34 +01:00
Andy Ye
665c2f2860 fix(sessions): avoid stale restart continuation reuse
Avoid stale restart continuation reuse after a session key has rotated.

Queued restart agent turns now carry the session id they were queued for and fall back to a system wake if the key points at a different session by delivery time. Normal completed-run lifecycle fields stay reusable for fresh sessions, while new-session creation clears stale lifecycle markers.

Closes #86593.

Co-authored-by: Andy Ye <35905412+TurboTheTurtle@users.noreply.github.com>
2026-05-28 00:44:34 +01:00
Peter Steinberger
4295f82411 fix(imessage): backport native approval reactions to .27
Backports native iMessage approval reaction handling to release/2026.5.27 and aligns release workflow tests with the .27 runtime image smoke job.
2026-05-28 00:42:09 +01:00
Fermin Quant
d77f5fe128 fix(cron): stabilize isolated prompt cache affinity
Stabilize isolated cron prompt cache affinity by deriving a stable prompt cache key per cron job/session/model and forwarding it separately from the rotating run session id.

Thread the key through embedded runs, stream resolution, provider options, proxy forwarding, custom streams, and prompt-cache observability. Keep OpenAI-compatible payloads valid by using hyphen-safe keys, clamping upstream prompt_cache_key values, and omitting affinity when cache retention is disabled.

Thanks @ferminquant.

Co-authored-by: Fermin Quant <ferminquant@hotmail.com>
(cherry picked from commit 3f9d2415ac)
2026-05-28 00:35:20 +01:00
Peter Steinberger
88b41f3571 chore(release): refresh generated release docs 2026-05-28 00:23:25 +01:00
Peter Steinberger
02f29c12cf chore(release): sync matrix plugin changelog 2026-05-28 00:20:19 +01:00
Peter Steinberger
c155648052 docs(changelog): note timeout session lock fix 2026-05-28 00:18:53 +01:00
Chunyue Wang
350e497c56 fix(agents): release session lock on timeout abort
Fixes #86816.

Co-authored-by: Chunyue Wang <16864032@qq.com>
2026-05-28 00:17:32 +01:00
Peter Steinberger
4248c4c618 fix(plugin-sdk): preserve approval command actions 2026-05-28 00:16:50 +01:00
Kevin Lin
8abacab27b fix(imessage): suppress duplicate native exec approvals
Fix iMessage native exec approval routing so approval prompts bind to the sent GUID without duplicate sends after RPC timeout. Also keeps chat.db GUID recovery on the local imsg path while avoiding local DB recovery for configured or detected SSH wrappers.

Thanks @kevinslin.
2026-05-28 00:12:24 +01:00
Peter Steinberger
7ce0109232 docs(changelog): refresh 2026.5.27 notes 2026-05-28 00:09:32 +01:00
Peter Steinberger
9e338d1eff ci(release): smoke Docker runtime templates in full validation 2026-05-27 23:57:34 +01:00
Shadow
d5a1f2e7ff fix: add ClawHub plugin display names (#87354) 2026-05-27 23:57:34 +01:00
Peter Steinberger
da6368a150 fix: reflect lazy plugin runtime surfaces 2026-05-27 23:57:33 +01:00
Peter Steinberger
ca43325632 fix(channels): preserve runtime catalog markdown metadata 2026-05-27 23:57:33 +01:00
Vincent Koc
19514f3bf2 fix(matrix): keep fallback tool warnings mention-inert 2026-05-27 23:57:33 +01:00
Vincent Koc
5d22828b40 fix(e2e): isolate kitchen sink log scans 2026-05-27 23:57:33 +01:00
Peter Steinberger
87d410dddb perf(gateway): cache auto-enabled plugin config 2026-05-27 23:57:33 +01:00
Vincent Koc
229e500af7 fix(ci): merge nested shrinkwrap override pins 2026-05-27 23:57:33 +01:00
Andy Ye
7e95088326 fix(gateway): drain probe client close
Closes #87210.

Gateway probe now waits for GatewayClient.stopAndWait() before resolving so callers do not observe a successful probe while the client socket is still draining. If the drain fails, probe falls back to stop().

Adds mocked probe coverage plus a real WebSocket regression test that verifies no client socket handle remains when probeGateway() resolves.

Co-authored-by: Andy Ye <35905412+TurboTheTurtle@users.noreply.github.com>
(cherry picked from commit cc72519053)
2026-05-27 23:55:46 +01:00
Peter Steinberger
34e6037753 fix(agents): resolve Codex runtime models first 2026-05-27 23:37:31 +01:00
xiaotian
e72213365a fix(slack): retain delivered final replies during late cleanup
Fix Slack draft cleanup after final-visible delivery.

Track when Slack has already delivered a visible final reply and stop reusing the draft finalizer for later same-turn final/error payloads. This keeps the first fallback cleanup for transient previews while preventing late cleanup from deleting a visible answer.

Fixes #87363

Co-authored-by: tianxiaochannel-oss88 <tianxiaochannel@gmail.com>
(cherry picked from commit fb1dfd486b)
2026-05-27 23:27:34 +01:00
Peter Steinberger
c3d3d683e3 fix(codex): backport shared app-server startup cleanup to .27 (#87428)
* fix(codex): preserve shared app-server after startup app errors (#87399)

* fix(codex): preserve shared app-server after startup app errors

* fix(codex): align startup cleanup tests with current types

* test(config): isolate installed plugin ledger cache

(cherry picked from commit 7f7eca1ad2)

* fix(ci): align release branch guard policy
2026-05-27 23:25:03 +01:00
Peter Steinberger
099f1ec627 fix(web-search): backport runtime-only provider config fix
Backports #87432 to release/2026.5.27. Fixes #87191 for the release branch by preserving runtime-only Brave and Gemini web search provider config.
2026-05-27 23:19:49 +01:00
Peter Steinberger
089795ca25 fix(agents): bound compaction wake retry timeouts
(cherry picked from commit db549137d3)
2026-05-27 23:00:30 +01:00
alkor2000
999ad1067a fix(agents): clamp compaction steer retry wait to remaining delivery window
The compaction retry loop checked the delivery-timeout deadline before
choosing a fixed backoff delay, then slept that whole delay. When the
remaining window was shorter than the next backoff entry, the final
retry could sleep past the deadline, overrunning the delivery timeout
the retry is meant to stay within. Clamp the wait to the remaining
window (min(scheduledDelay, deadline - now)) and stop retrying once no
time remains, so compaction waiting never exceeds the delivery timeout.

Addresses the near-deadline overrun raised in ClawSweeper review of #86606.

(cherry picked from commit ea2e9ce8bd)
2026-05-27 22:59:22 +01:00
alkor2000
2285b00649 fix(agents): wait for compaction before requester steering fallback
Follow-up to #85489. Active requester steering treated a `compacting`
outcome from queueEmbeddedPiMessageWithOutcome as a terminal wake
failure and fell through to the requester-agent/direct fallback, even
though the active run becomes steerable again as soon as compaction
finishes.

Introduce a shared resolveActiveWakeWithRetries helper used by both the
steer path (maybeSteerSubagentAnnounce) and the generated-completion
active wake (sendSubagentAnnounceDirectly). The helper treats
`compacting` as transient and waits through compaction, retrying the
same wake. Waiting is bounded by the active wake's delivery timeout (not
just the backoff schedule): the backoff schedule controls the gap
between attempts, and once it is exhausted its last delay is reused until
the delivery deadline, so a compaction that finishes after the schedule
but within the delivery timeout still re-steers. The best-effort
transcript-commit retry and the compaction retry share one loop, so a
run that compacts and then reports transcript_commit_wait_unsupported
still gets the best-effort retry. Other wake failures keep their
existing single-attempt fallback.

Fixes #86566

(cherry picked from commit a7b8e6a5a9)
2026-05-27 22:59:14 +01:00
martingarramon
6c175ac1a9 fix(agent-job): preserve grace for pending error diagnostics
Preserve pending agent-job error diagnostics as non-terminal timeout snapshots so the retry grace path can still recover when the lifecycle later starts and completes.

Local proof:
- node scripts/run-vitest.mjs packages/sdk/src/index.test.ts src/gateway/server-methods/server-methods.test.ts src/gateway/server.chat.gateway-server-chat.test.ts src/agents/run-wait.test.ts src/agents/openclaw-tools.sessions.test.ts
- node scripts/run-oxlint.mjs packages/sdk/src/client.ts packages/sdk/src/index.test.ts src/gateway/server-methods/agent-job.ts src/gateway/server-methods/agent.ts src/gateway/server-methods/agent-wait-dedupe.ts src/agents/run-wait.ts src/agents/tools/sessions-send-tool.ts src/gateway/server-methods/server-methods.test.ts src/gateway/server.chat.gateway-server-chat.test.ts src/agents/run-wait.test.ts src/agents/openclaw-tools.sessions.test.ts
- autoreview --mode local: no accepted/actionable findings
- CI run 26536599850: success

Co-authored-by: Martin Garramon <martin@yulicreative.ai>
(cherry picked from commit 039fcbaa4c)
2026-05-27 22:51:46 +01:00
Pavan Kumar Gondhi
bd826ff036 fix(gateway): expire browser tokens after auth rotation
Expire browser-origin Control UI/WebChat device tokens when shared gateway auth rotates by tagging those tokens with the shared-auth generation and enforcing it during verification.

Preserve the issuer tag when a shared-auth-derived device token reconnects through a non-browser client, so reconnect rotation cannot turn it into an untagged long-lived token.

Proof:
- OPENCLAW_VITEST_MAX_WORKERS=1 node scripts/run-vitest.mjs src/gateway/server.shared-auth-rotation.test.ts src/infra/device-pairing.test.ts src/gateway/control-ui.http.test.ts
- GitHub CI run 26535632102: relevant build/runtime/test-type checks green; inherited lint reds match origin/main.
- GitHub CodeQL Critical Quality run 26535631610: network-runtime-boundary green.

Co-authored-by: Pavan Kumar Gondhi <pavangondhi@gmail.com>
(cherry picked from commit c923b07784)
2026-05-27 21:22:23 +01:00
Alix-007
29e23ba19f fix(agents): bound plugin system context
* fix(agents): bound plugin system context

* test(agents): align wrapped system context expectations

* style(agents): format hook context helper

* test(codex): expect plugin system context boundary

---------

Co-authored-by: Alix-007 <267018309+Alix-007@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
(cherry picked from commit f4329fe0d6)
2026-05-27 21:21:20 +01:00
Peter Steinberger
e889eb732b fix(tool-search): align release catalog reuse wrapper 2026-05-27 21:16:25 +01:00
Sebastien Tardif
a0439d5a44 fix(tool-search): reuse unchanged catalogs
Fixes repeated Tool Search catalog registration for unchanged effective tool sets by reusing a fingerprinted catalog snapshot across embedded-agent run cleanup.

The reusable catalog is guarded by catalog-affecting fields, parameters, and executable identity, and reuse now rebinds the current run/session refs before returning. Embedded-agent prep logging only suppresses the catalog line when reuse actually happened.

Verification:
- pnpm test src/agents/tool-search.test.ts -- --reporter=verbose
- pnpm check:changed, Testbox tbx_01ksney4f00wgk9n39yv7jsh4m
- Real behavior proof, GitHub Actions run 26534896284
- CI rerun for unrelated model-picker timeout passed, GitHub Actions run 26534489215
- autoreview clean: no accepted/actionable findings

Closes #86887
Co-authored-by: Sebastien Tardif <sebtardif@ncf.ca>

(cherry picked from commit 60e8e60306)
2026-05-27 21:16:25 +01:00
Peter Steinberger
6da4ff2b2b fix(codex): route workspace memory through tools (#87383) (#87403)
* fix(codex): route workspace memory through tools

* fix(codex): preserve extra memory bootstrap files

* fix(codex): support memory_get-only context routing

* fix(codex): only tool-route canonical workspace memory

* fix(codex): keep memory fallback for sandbox workspaces

(cherry picked from commit d93524d1cc)
2026-05-27 21:11:51 +01:00
Yuval Dinodia
00dabd6e40 fix(codex): preserve shared app-server when spawned helper run fails logically (#72574) (#87375)
* fix(codex): preserve shared app-server when spawned helper run fails logically

* fix(codex): widen spawnedBy param to match EmbeddedRunAttemptParams

* fix(codex): align spawnedBy startup typing

* fix(codex): retire shared client on spawned startup timeout

* fix(codex): narrow spawned thread-start preservation

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
(cherry picked from commit 74f9d6b96d)
2026-05-27 20:54:15 +01:00
狼哥
ac825a47c6 fix(agents): avoid session event queue self-wait (#86123)
Avoids a self-wait in embedded agent session event hooks by skipping the queue drain only for hooks running inside the current session event processing chain. Detached or external hook work still drains the queue before taking the session write lock.

Verification:
- node scripts/run-vitest.mjs run --config test/vitest/vitest.agents-embedded-agent.config.ts src/agents/embedded-agent-runner/run/attempt.session-lock.test.ts
- node scripts/run-oxlint.mjs --tsconfig config/tsconfig/oxlint.core.json src/agents/embedded-agent-runner/run/attempt.session-lock.test.ts src/agents/embedded-agent-runner/run/attempt.session-lock.ts --threads=8
- .agents/skills/autoreview/scripts/autoreview --mode branch --base origin/main
- GitHub CI: https://github.com/openclaw/openclaw/actions/runs/26533883763

Thanks @luoyanglang.

Co-authored-by: luoyanglang <hanwanlonga@gmail.com>
(cherry picked from commit b789e71e57)
2026-05-27 20:47:20 +01:00
keshavbotagent
9cc1a83bcc fix(plugin-state): evict current namespace on plugin row cap
Make plugin-state enforce the plugin-wide live-row fuse by evicting only from the namespace currently being written, preserving sibling namespace rows and still failing atomically when the current namespace cannot free enough rows.

Raise the plugin-wide cap to 6,000 rows, keep Telegram's persistent message-cache namespace at 3,000 entries, and document the updated SDK runtime contract. Harden legacy plugin-state import so capacity pressure cannot archive a source after losing imported keys, with focused regression coverage for Telegram-shaped namespaces and migration rollback.

Also restore the Docker runtime-assets preflight step in full release validation so release workflow contract tests stay aligned.

Verification: focused plugin-state, migration, Telegram, workflow-contract, lint, deprecated-API, diff-check, Blacksmith Testbox, CI, CodeQL, Workflow Sanity, OpenGrep, and autoreview all passed on PR head fee021cfa6.

Co-authored-by: Keshav's Bot <keshavbotagent@gmail.com>
(cherry picked from commit e339586750)
2026-05-27 20:36:43 +01:00
Shubhankar Tripathy
e35638ea7b fix(channels): preserve Telegram SecretRef prompt config
Use read-only Telegram account inspection for prompt-time channel actions, inline buttons, and reaction guidance so unresolved SecretRef tokens retain configured non-secret behavior before runtime snapshot hydration.

Match runtime Telegram account lookup for normalized config keys and multi-account fallback guards, while keeping sends/actions on the existing strict credential resolution path.

Fixes #75433.

Co-authored-by: Shubhankar Tripathy <reach2shubhankar@gmail.com>
(cherry picked from commit 90f30075aa)
2026-05-27 20:28:41 +01:00
Alex Knight
271baa1b47 fix(codex): preserve native hook relay across restarts
Fixes #87331.\n\nPersist Codex native hook relay generations for real app-server resumes, keep a bounded legacy-binding grace path, and rotate generation on fresh-thread fallback so stale hook commands stay rejected.\n\nCo-authored-by: Alex Knight <15041791+amknight@users.noreply.github.com>

(cherry picked from commit 42e9504114)
2026-05-27 20:09:37 +01:00
Peter Steinberger
7e3adcc2eb fix(docker): package runtime workspace templates 2026-05-27 18:56:59 +01:00
Peter Steinberger
be40367a77 docs(skills): add discord mood summaries 2026-05-27 17:51:12 +01:00
Peter Steinberger
c13f91a887 perf(gateway): slim current metadata identity cache 2026-05-27 17:43:41 +01:00
Vincent Koc
82dc834da1 fix(e2e): bound agent turn assertion logs 2026-05-27 17:43:41 +01:00
Vincent Koc
b9dfef712d fix(e2e): zero log tail buffers 2026-05-27 17:43:41 +01:00
Vincent Koc
a2feccfd7c fix(ci): pin aged lru cache lock entry 2026-05-27 17:43:37 +01:00
Vincent Koc
46c711a45d fix(ci): preserve forked shrinkwrap pins 2026-05-27 17:43:33 +01:00
Peter Steinberger
ee882362cb chore(release): prepare 2026.5.27 2026-05-27 16:52:17 +01:00
458 changed files with 11439 additions and 1754 deletions

View File

@@ -0,0 +1,94 @@
---
name: discord-mood
description: "Discord release/beta mood summaries from Discrawl with quotes, no URLs by default, and issue/PR correlation."
---
# Discord Mood
Use this with `$discrawl` when asked what people say, what the vibe/mood is,
or how a beta/release is landing in Discord.
## Workflow
1. Sync when currentness matters or the user asks:
```bash
discrawl sync --update=auto --source discord
discrawl status --json
```
2. Query the relevant window with read-only Discrawl SQL. Start from the release
time, last answer freshness, or user-provided date. Include release/version,
update/install, fast/slow, crash/broken/regression, plugin/LCM, Codex,
WebChat/session, and specific feature terms.
3. Pull nearby channel slices around high-signal hits so quoted messages are not
detached from context.
4. When Discord mentions GitHub issues/PRs, verify live state with `gh api` or
`$openclaw-pr-maintainer` before saying open/closed/merged.
## Output Rules
- Do not show Discord URLs by default. Add links only when the user explicitly
asks for links, wants to act on a specific message, or needs exact audit
evidence.
- Prefer short direct quotes over paraphrase-only summaries. Quote enough to
show tone, not whole messages.
- Attribute quotes compactly: `author, #channel, HH:MM UTC: "quote"`.
- Keep quotes representative and balanced: positive, negative, uncertainty,
and maintainer/triage if present.
- Distill first, details second. Use:
- `freshness`
- `net mood`
- `good`
- `worry`
- `quotes`
- `open items` when issues/PRs are involved
- Use absolute timestamps and counts. Mention known archive gaps.
- Separate runtime sentiment from update/install confidence; these often diverge.
- Avoid hype terms unless quoting users. Keep the agent's synthesis sober.
## Quote Selection
Good quote candidates:
- clear sentiment: "fast", "broken", "smooth", "unresponsive"
- multiple independent users saying the same thing
- specific repro/update/version details
- maintainer comments that frame severity or release risk
Skip:
- jokes unless they capture a broader mood
- long rants without a concrete signal
- bot/status spam unless asked for operational stats
## Common SQL Shape
```sql
select
m.created_at,
coalesce(nullif(mm.display_name,''), nullif(mm.global_name,''), nullif(mm.username,''), m.author_id) as author,
coalesce(nullif(c.name,''), m.channel_id) as channel,
m.id,
replace(replace(substr(m.content,1,1200), char(10), ' '), char(13), ' ') as content
from messages m
left join channels c on c.id=m.channel_id and c.guild_id=m.guild_id
left join members mm on mm.guild_id=m.guild_id and mm.user_id=m.author_id
where m.guild_id='1456350064065904867'
and m.created_at >= '<ISO start>'
and (
lower(m.content) like '%<version>%'
or lower(m.content) like '%release%'
or lower(m.content) like '%update%'
or lower(m.content) like '%install%'
or lower(m.content) like '%fast%'
or lower(m.content) like '%slow%'
or lower(m.content) like '%crash%'
or lower(m.content) like '%broken%'
or lower(m.content) like '%regression%'
)
order by m.created_at asc
limit 200;
```

View File

@@ -0,0 +1,4 @@
interface:
display_name: "Discord Mood"
short_description: "Summarize Discord release mood with quotes"
default_prompt: "Use $discord-mood to sync Discrawl, quote representative Discord feedback without URLs by default, summarize release or beta mood, and verify referenced GitHub issue/PR state."

View File

@@ -0,0 +1,85 @@
---
name: release-openclaw-announcement
description: "Draft or post OpenClaw beta/stable Discord release announcements from changelog, GitHub release, registry, and validation evidence. Use when announcing a beta, stable release, release candidate, or asking what users should test after an OpenClaw release."
---
# OpenClaw Release Announcement
Use with `release-openclaw-maintainer` after a beta or stable release is live.
Use with `openclaw-discord` when actually posting to Discord.
## Evidence First
Before drafting focus areas, read real release evidence:
1. Current GitHub release body for the tag.
2. `CHANGELOG.md` section for the released base version.
3. Commits since the previous shipped version or the operator-specified base.
4. Registry/package metadata for the exact version and current dist-tag.
5. Validation status that is relevant to user confidence.
Do not claim a full changelog audit unless you did it. If you only read the
generated release notes or top changelog section, say that and either audit
properly or draft with that limitation.
For beta focus areas, prioritize user-observable changes over internal test or
CI mechanics:
- install/update paths
- OS/platform-specific behavior
- Gateway startup/restart, config, and runtime behavior
- provider/model/runtime routing
- plugin loading and local plugin development
- channels and media paths
- security/data-loss/user-impact fixes
Do not let late release-branch fixes automatically dominate the announcement.
If the version includes a large delta from the previous shipped version, rank
focus areas by the whole release delta and expected user impact; mention late
fixes in their natural category.
## Required Copy
Every beta announcement must make beta status explicit and include:
- exact version, e.g. `OpenClaw 2026.5.25-beta.1`
- one-sentence risk framing: beta, useful for testing, not stable promotion
- focused test areas derived from evidence, not guesswork
- update command promoted near the top:
```sh
openclaw update --channel beta --yes
openclaw --version
```
- fresh install path:
`Install from https://openclaw.ai`
- GitHub release link
- concise validation note, without making CI the headline
Do not suggest npm install commands in beta announcements unless the operator
explicitly asks for npm-specific copy or troubleshooting text. It is fine to use
registry metadata as evidence; do not turn that into public install guidance.
For stable announcements, use the stable channel wording:
```sh
openclaw update --channel stable --yes
openclaw --version
```
Fresh installs still point to `https://openclaw.ai`.
## Style
- Discord Markdown, no tables.
- Keep it skimmable: short intro, bullets, commands, links.
- Lead with what users can feel or test, not proof plumbing.
- Mention validation only after install/update instructions.
- Be specific about where feedback is useful.
- Do not mention private local proof paths in public announcements.
- Do not overstate unverified platforms, channels, or provider behavior.
## Posting
When asked to post, use the configured Discord workflow from
`openclaw-discord` or the approved OpenClaw relay. Never print tokens.
For public channels, inspect the final body before sending.

View File

@@ -0,0 +1,4 @@
interface:
display_name: "OpenClaw Release Announcement"
short_description: "Draft Discord beta/stable release announcements from evidence."
default_prompt: "Use this skill to draft an OpenClaw beta or stable Discord announcement from changelog, release notes, npm/GitHub release proof, and validation evidence."

View File

@@ -162,6 +162,50 @@ jobs:
provenance: mode=max
push: true
- name: Smoke test amd64 runtime workspace templates
shell: bash
env:
IMAGE_REFS: ${{ steps.tags.outputs.value }}
run: |
set -euo pipefail
mapfile -t image_refs <<< "${IMAGE_REFS}"
image_ref="${image_refs[0]}"
if [[ -z "${image_ref}" ]]; then
echo "::error::No amd64 image ref resolved for runtime template smoke"
exit 1
fi
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
'
# Build arm64 image. Default and slim tags point to the same slim runtime.
build-arm64:
needs: [approve_manual_backfill]
@@ -260,6 +304,50 @@ jobs:
provenance: mode=max
push: true
- name: Smoke test arm64 runtime workspace templates
shell: bash
env:
IMAGE_REFS: ${{ steps.tags.outputs.value }}
run: |
set -euo pipefail
mapfile -t image_refs <<< "${IMAGE_REFS}"
image_ref="${image_refs[0]}"
if [[ -z "${image_ref}" ]]; then
echo "::error::No arm64 image ref resolved for runtime template smoke"
exit 1
fi
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
'
# Create multi-platform manifests
create-manifest:
needs: [approve_manual_backfill, build-amd64, build-arm64]

View File

@@ -225,7 +225,7 @@ jobs:
} >> "$GITHUB_STEP_SUMMARY"
docker_runtime_assets_preflight:
name: Verify Docker runtime-assets prune path
name: Verify Docker runtime image assets
needs: [resolve_target]
if: inputs.rerun_group == 'all'
runs-on: ubuntu-24.04
@@ -250,6 +250,49 @@ jobs:
--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]

View File

@@ -551,25 +551,31 @@ jobs:
retention-days: ${{ matrix.deep_profile == 'true' && 14 || 30 }}
- name: Prepare clawgrit reports checkout
id: clawgrit_reports
if: ${{ steps.kova.outputs.report_json != '' && steps.clawgrit.outputs.present == 'true' }}
env:
CLAWGRIT_REPORTS_TOKEN: ${{ secrets.CLAWGRIT_REPORTS_TOKEN }}
shell: bash
run: |
set -euo pipefail
echo "ready=false" >> "$GITHUB_OUTPUT"
reports_root=".artifacts/clawgrit-reports"
mkdir -p "$reports_root"
git -C "$reports_root" init -b main
git -C "$reports_root" remote add origin "https://x-access-token:${CLAWGRIT_REPORTS_TOKEN}@github.com/openclaw/clawgrit-reports.git"
if git -C "$reports_root" ls-remote --exit-code --heads origin main >/dev/null 2>&1; then
git -C "$reports_root" fetch --depth=1 origin main
if timeout 60s git -C "$reports_root" ls-remote --exit-code --heads origin main >/dev/null 2>&1; then
if ! timeout 120s git -C "$reports_root" fetch --depth=1 origin main; then
echo "::warning::Skipping optional clawgrit report publish because the reports checkout fetch timed out or failed."
exit 0
fi
git -C "$reports_root" checkout -B main FETCH_HEAD
else
git -C "$reports_root" checkout -B main
fi
echo "ready=true" >> "$GITHUB_OUTPUT"
- name: Publish to clawgrit reports
if: ${{ steps.kova.outputs.report_json != '' && steps.clawgrit.outputs.present == 'true' }}
if: ${{ steps.kova.outputs.report_json != '' && steps.clawgrit.outputs.present == 'true' && steps.clawgrit_reports.outputs.ready == 'true' }}
env:
CLAWGRIT_REPORTS_TOKEN: ${{ secrets.CLAWGRIT_REPORTS_TOKEN }}
shell: bash
@@ -642,6 +648,9 @@ jobs:
exit 0
fi
sleep $((attempt * 2))
git -C "$reports_root" fetch --depth=1 origin main
timeout 120s git -C "$reports_root" fetch --depth=1 origin main || {
echo "::warning::Skipping optional clawgrit report rebase because the reports fetch timed out or failed."
exit 0
}
git -C "$reports_root" rebase FETCH_HEAD
done

View File

@@ -2,20 +2,42 @@
Docs: https://docs.openclaw.ai
## Unreleased
## 2026.5.27
### Highlights
- Stronger security and content boundaries: group prompt text is kept out of the system prompt, repeated-dot hostnames are normalized, side-effecting command wrappers and unsafe Node runtime env overrides are blocked, no-auth Tailscale exposure is rejected, and node/device-role approvals now require admin authority. (#87144, #87305, #87292, #87308, #87146) Thanks @eleqtrizit and @pgondhi987.
- More reliable Codex app-server runs: Codex runtime models resolve first, workspace memory is routed through tools, shared app-server clients survive startup and spawned-helper failures, native hook relay generations survive restarts and rotate on fresh fallbacks, and false runtime live switches are avoided. (#87383, #87403, #87375, #72574, #87428) Thanks @yetval.
- Faster Gateway and reply paths: session reads, plugin metadata fingerprints, auth env snapshots, auto-enabled plugin config, tool-search catalogs, and stable metadata caches do less hot-path rediscovery while visible replies no longer inherit hidden cleanup timeouts. (#86439, #87044) Thanks @keshavbotagent.
- Better provider and model coverage: OpenAI-compatible embedding providers are core, DeepInfra catalog browsing loads the full credential-aware model set, Pixverse adds video generation and API region selection, VLLM thinking params are wired, Claude CLI OAuth overlays load for PI auth profiles, and bare direct Anthropic model ids work. (#85269, #84549, #87167) Thanks @dutifulbob, @ats3v, and @joshavant.
- Channel delivery is steadier: Telegram `sendMessage` actions use durable outbound delivery, iMessage suppresses duplicate native exec approval prompts and sends, Slack keeps delivered final replies during late cleanup, Matrix mention previews/finals are stricter, QQBot fallback approval buttons honor slash-command auth, Discord guild requester checks are tighter, recovered Discord tool-warning artifacts stay out of successful replies, and Google Chat stops thread sends in DMs. (#87261, #87154) Thanks @mbelinky and @eleqtrizit.
- Release, package, and CI proof paths are harder to wedge: npm/package inventory honors dist exclusions, shrinkwrap override pins merge correctly, Docker runtime workspace templates are packaged and smoked, release postpublish checks are stricter, beta smoke rejects empty runs, and E2E log/probe waits are bounded.
### Changes
- Memory: add a core OpenAI-compatible embedding provider for local and hosted OpenAI-style endpoints, with config, doctor, and docs support. (#85269) Thanks @dutifulbob.
- Plugin SDK: mark memory-specific embedding provider registration as deprecated compatibility and surface non-bundled usage in plugin compatibility diagnostics. (#85072) Thanks @mbelinky.
- Providers: add the Pixverse video generation provider, API region selection, docs, and external plugin packaging support.
- DeepInfra: load the full model catalog when users browse models during onboarding, preserve configured API-key catalogs, refresh media/video defaults, and keep pricing/default model metadata aligned. (#84549) Thanks @ats3v.
- Plugin SDK: expose plugin approval action metadata and stop exporting Vitest test helpers from the public SDK surface. (#87120) Thanks @RomneyDa.
- Channel SDK: move channel message compatibility into core, remove old channel turn runtime aliases, and preserve runtime catalog markdown metadata for plugins.
- ClawHub: add plugin display metadata so catalog/package listings use cleaner names. (#87354) Thanks @thewilloftheshadow.
- Agents: split the heartbeat runtime template out of docs assets and add compatibility repair for legacy heartbeat template content. (#85416) Thanks @hxy91819.
### Fixes
- Harden hostname normalization for repeated trailing dots [AI]. (#87305) Thanks @pgondhi987.
- fix: block side-effecting command wrappers [AI]. (#87292) Thanks @pgondhi987.
- Block unsafe Node runtime env overrides [AI]. (#87308) Thanks @pgondhi987.
- Telegram: route `sendMessage` action replies through durable outbound delivery so completed agent responses remain retryable when the gateway send path times out. (#87261) Thanks @mbelinky.
- Gateway/security: require `operator.admin` for node and other non-operator device-role pairing approvals, including trusted-proxy sessions, while keeping pairing-only approvals available for operator-role requests. (#87146)
- Security/content boundaries: route untrusted group prompt metadata outside system prompts, normalize repeated trailing hostname dots, block side-effecting command wrappers, reject unsafe Node runtime env overrides, reject no-auth Tailscale exposure, block untrusted Microsoft Teams service URLs, enforce `/allowlist configWrites` origin policy, gate QQBot fallback approval buttons, and require admin for node/device-role approvals. (#87144, #87305, #87292, #87308, #87146, #87154, #87334) Thanks @eleqtrizit and @pgondhi987.
- Codex: resolve Codex runtime models before generic routing, route workspace memory through tools, preserve shared app-server clients after startup and spawned-helper failures, preserve native hook relay generations across restarts and fresh fallbacks, keep raw reasoning/source-reply guards intact, report quarantined dynamic tools, keep the attempt watchdog armed for queued terminal turns, and route Codex OAuth compaction through OpenAI-Codex. (#87383, #87403, #87375, #72574, #87428) Thanks @yetval.
- Agents/runtime: avoid session event queue self-waits, bound compaction wake and steering retries, preserve grace for pending error diagnostics, avoid false Codex runtime live switches, avoid stale restart continuation reuse, preserve session fallback errors, suppress duplicate Claude CLI skill prompts, keep runtime context before active user turns, strip stale Anthropic thinking, quarantine unsupported tool schemas, recover completed write timeouts safely, release retained session write locks on timeout abort, and validate forced plugin harness support before pinning. (#86123, #55424, #86855, #74341, #87278) Thanks @luoyanglang, @cathrynlavery, and @openperf.
- Reply/session delivery: keep visible turn admission unbounded, keep visible fallback delivery on latest targets, preserve bridge hook context, classify direct fallback targets by channel grammar, report approval resolutions in bridge mode, and avoid stale source-reply artifacts. (#87044) Thanks @keshavbotagent.
- Channels: make Telegram `sendMessage` action replies durable and preserve SecretRef prompt config, suppress duplicate iMessage native exec approval prompts and sends, keep iMessage approval polling alive after denied reactions, keep Slack delivered final replies during late cleanup, keep Matrix mention previews/finals mention-inert and normally delivered, ignore filename-embedded Matrix IDs, suppress recovered Discord tool-warning artifacts from successful replies, suppress Google Chat thread sends in DMs, and harden Discord guild requester checks. (#87261, #87452) Thanks @mbelinky.
- Memory: salvage QMD search JSON after nonzero exits and keep workspace memory routing through the Codex tool path where possible. (#87225, #87383, #87403) Thanks @osolmaz.
- Providers/models: forward cached token usage in OpenAI-compatible chat completions, load Claude CLI OAuth overlays for PI auth profiles, send bare direct Anthropic model ids, wire configured VLLM thinking params, honor OpenAI-compatible cache retention, normalize OpenAI Responses replay tool ids, resolve OpenAI `gpt-5.5` without a cached catalog, preserve `retry-after` fallback handling, bound GitHub Copilot auth requests, and load DeepInfra custom/live catalogs consistently. (#82062, #87167, #84549) Thanks @caz0075, @joshavant, and @ats3v.
- Gateway/performance: borrow read-only session metadata and active session working stores, cache current/stable plugin metadata fingerprints, cache auto-enabled plugin config, slim metadata identity caches, trust current metadata lifecycle caches, stabilize isolated cron prompt-cache affinity, persist model auth profile suffixes, drain probe client closes, expire browser tokens after auth rotation, and keep default status fast paths bounded. Thanks @ferminquant.
- CLI/help/config: reject loose or malformed numeric options for gateway timeouts, model limits, directory limits, message options, webhooks, and partial values; respect subcommand version options; route generated/root/plugin help targets correctly; keep skills JSON output flushing naturally; and keep plugin descriptor loading quiet in root help. (#87398) Thanks @Patrick-Erichsen.
- Plugin state/tool search: evict the current namespace when plugin rows hit caps, reuse unchanged tool-search catalogs, align the release catalog reuse wrapper, and keep fallback tool warnings mention-inert.
- Install/package/release: match npm globstar exclusions, honor dist package exclusions in inventory, omit unpacked test helpers, skip Homebrew until macOS packages need it, package Docker runtime workspace templates, smoke Docker runtime templates during full validation, merge nested shrinkwrap override pins, preserve forked shrinkwrap pins, pin aged `lru-cache`, harden postpublish verification, accept main full-validation proof, and reject empty beta smoke runs.
- E2E/QA/Crabbox: bound Telegram, Open WebUI, ClawHub, Matrix, Tool Search, MCP, gateway network, bundled runtime, kitchen-sink, codex media, config reload, and agent-turn assertion waits; prefer Azure for Windows targets; reinitialize invalid changed-gate git dirs; full-sync sparse container runs; and fail empty explicit test requests. (#87186)
## 2026.5.26
@@ -73,7 +95,7 @@ Docs: https://docs.openclaw.ai
- Agents/sessions: handle active-fallback failures in `sessions_send` so fallback routing reports the real failure and does not leave callers with an ambiguous dropped send. (#86638)
- Agents/hooks/subagents: enforce default hook agent allowlists, recover failed subagent lifecycle completions, and keep node task lifecycle cleanup from closing the Gateway listener. (#86101)
- Codex: project newer OpenClaw chat history into resumed app-server threads and keep Codex turn timeouts inside the Codex runtime boundary so timeouts do not poison shared app-server clients or fall through to unrelated provider fallback. (#86677, #86476) Thanks @TurboTheTurtle and @pashpashpash.
- Config/doctor/update: narrow profiled tool-section doctor repair, keep runtime-injected legacy web-search provider config out of user-authored config validation, and keep prerelease tags excluded from stable updater resolution. (#87030, #86818, #86559) Thanks @joshavant, @luoyanglang, and @stevenepalmer.
- Config/doctor/update: narrow profiled tool-section doctor repair, migrate legacy `memorySearch.provider: "auto"` to `openai`, make restart follow-up guidance actionable, keep runtime-injected legacy web-search provider config out of user-authored config validation, and keep prerelease tags excluded from stable updater resolution. (#87030, #86818, #86559, #87361) Thanks @joshavant, @luoyanglang, @stevenepalmer, and @giodl73-repo.
- Doctor/runtime: validate active bundled MCP tool schemas through the same runtime projection path so unsupported MCP input schemas are reported and quarantined instead of poisoning assistant startup.
- CLI/Windows: add a Windows-only stack-size respawn for stack-heavy startup paths, default CLI logs to local timestamps, and validate timeout/banner TTY state more strictly. (#87031, #85387) Thanks @giodl73-repo and @vincentkoc.
- Locking/security: require owner identity proof before stale plugin lock removal, memoize session lock owner arguments, and avoid writing default exec approval stores unless policy state actually changed. (#86814, #86964) Thanks @Alix-007 and @vincentkoc.

View File

@@ -178,6 +178,7 @@ COPY --from=runtime-assets --chown=node:node /app/package.json .
COPY --from=runtime-assets --chown=node:node /app/pnpm-workspace.yaml .
COPY --from=runtime-assets --chown=node:node /app/patches ./patches
COPY --from=runtime-assets --chown=node:node /app/openclaw.mjs .
COPY --from=runtime-assets --chown=node:node /app/src/agents/templates ./src/agents/templates
COPY --from=runtime-assets --chown=node:node /app/${OPENCLAW_BUNDLED_PLUGIN_DIR} ./${OPENCLAW_BUNDLED_PLUGIN_DIR}
COPY --from=runtime-assets --chown=node:node /app/skills ./skills
COPY --from=runtime-assets --chown=node:node /app/docs ./docs

View File

@@ -65,8 +65,8 @@ android {
applicationId = "ai.openclaw.app"
minSdk = 31
targetSdk = 36
versionCode = 2026052601
versionName = "2026.5.26"
versionCode = 2026052701
versionName = "2026.5.27"
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.5.27 - 2026-05-27
Maintenance update for the current OpenClaw release.
## 2026.5.26 - 2026-05-26
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.5.26
OPENCLAW_MARKETING_VERSION = 2026.5.26
OPENCLAW_IOS_VERSION = 2026.5.27
OPENCLAW_MARKETING_VERSION = 2026.5.27
OPENCLAW_BUILD_VERSION = 1
#include? "../build/Version.xcconfig"

View File

@@ -1,3 +1,3 @@
{
"version": "2026.5.26"
"version": "2026.5.27"
}

View File

@@ -15,9 +15,9 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>2026.5.26</string>
<string>2026.5.27</string>
<key>CFBundleVersion</key>
<string>2026052600</string>
<string>2026052700</string>
<key>CFBundleIconFile</key>
<string>OpenClaw</string>
<key>CFBundleURLTypes</key>

View File

@@ -1,4 +1,4 @@
53b7621e99d75b98ecc8f4389d38900f84cf213f95dbcc877f36125d763c660d config-baseline.json
e92bbf45714e418383118098d4ff15d347fa8ffc7e7837b437b522d2b59ce9fe config-baseline.core.json
e058e30260eab3a65ff03e5f4596e3042ca1e8ffc6fa66eb46dbce78b49d410a config-baseline.json
13fb390fd71a8d456cdfd42e6d9e577eba286e4509cc4e1a11c42f2e19255514 config-baseline.core.json
b901fb766edfd9df630690281476fc4032c64772f69d1d8f7b2e0e913a90f229 config-baseline.channel.json
5c214ab364011fd95735755f9fa4298aa4de8ad81144ae8dd08d969bb7ba318b config-baseline.plugin.json
99e3acc957594dba162be46f41e70a77f22adc594a079ad95a16518c1e285bbb config-baseline.plugin.json

View File

@@ -1,2 +1,2 @@
ce09dfd1c6f67d49916da2557fb208744b7d8a4912bde944004f44c0998c8e9d plugin-sdk-api-baseline.json
371bdfb13fda61dda885827ffeb922bd46e97ca30e09fa0d09baab80c58a7d1e plugin-sdk-api-baseline.jsonl
401b98b29b5057b1ef3f598e2efab5672e15b147724ac964a8da508689408282 plugin-sdk-api-baseline.json
b017671bbe8a0e50625b79079aa470383ef66c4d7d060c2e208c36e573b81ea4 plugin-sdk-api-baseline.jsonl

View File

@@ -183,18 +183,23 @@ in every user turn. Codex loads `AGENTS.md` through its own project-doc
discovery. `SOUL.md`, `IDENTITY.md`, `TOOLS.md`, and `USER.md` are forwarded as
Codex developer instructions. `HEARTBEAT.md` content is not injected; heartbeat
turns get a collaboration-mode note pointing to the file when it exists and is
non-empty. `MEMORY.md` and active `BOOTSTRAP.md` content keep the normal
turn-context role for now.
non-empty. `MEMORY.md` content from the configured agent workspace is not pasted
into every native Codex turn; when memory tools are available for that workspace,
Codex turns get a small workspace-memory note and should use `memory_search` or
`memory_get` when durable memory is relevant. If tools are disabled, memory
search is unavailable, or the active workspace differs from the agent memory
workspace, `MEMORY.md` falls back to the normal bounded turn-context path. Active
`BOOTSTRAP.md` content keeps the normal turn-context role for now.
On non-Codex harnesses, bootstrap files continue to be composed into the
OpenClaw prompt according to their existing gates. `HEARTBEAT.md` is omitted on
normal runs when heartbeats are disabled for the default agent or
`agents.defaults.heartbeat.includeSystemPromptSection` is false. Keep injected
files concise, especially `MEMORY.md`. `MEMORY.md` is intended to stay a curated
long-term summary; detailed daily notes belong in `memory/*.md` where
files concise, especially non-Codex `MEMORY.md`. `MEMORY.md` is intended to stay
a curated long-term summary; detailed daily notes belong in `memory/*.md` where
`memory_search` and `memory_get` can retrieve them on demand. Oversized
`MEMORY.md` files increase prompt usage and can be partially injected because of
the bootstrap file limits below.
non-Codex `MEMORY.md` files increase prompt usage and can be partially injected
because of the bootstrap file limits below.
<Note>
`memory/*.md` daily files are **not** part of the normal bootstrap Project Context. On ordinary turns they are accessed on demand via the `memory_search` and `memory_get` tools, so they do not count against the context window unless the model explicitly reads them. Bare `/new` and `/reset` turns are the exception: the runtime can prepend recent daily memory as a one-shot startup-context block for that first turn.
@@ -209,11 +214,13 @@ occurs, OpenClaw can inject a concise system-prompt warning notice; control this
default: `always`). Detailed raw/injected counts stay in diagnostics such as
`/context`, `/status`, doctor, and logs.
For memory files, truncation is not data loss: the file remains intact on disk,
but the model only sees the shortened injected copy until it reads or searches
memory directly. If `MEMORY.md` is repeatedly truncated, distill it into a
shorter durable summary and move detailed history into `memory/*.md`, or
intentionally raise the bootstrap limits.
For memory files, truncation is not data loss: the file remains intact on disk.
On native Codex, `MEMORY.md` is read on demand through memory tools when
available, with bounded prompt fallback when tools cannot run. On other
harnesses, the model only sees the shortened injected copy until it reads or
searches memory directly. If `MEMORY.md` is repeatedly truncated there, distill
it into a shorter durable summary and move detailed history into `memory/*.md`,
or intentionally raise the bootstrap limits.
Sub-agent sessions only inject `AGENTS.md` and `TOOLS.md` (other bootstrap files
are filtered out to keep the sub-agent context small).

View File

@@ -517,7 +517,7 @@ That stages grounded durable candidates into the short-term dreaming store while
- **QMD backend**: probes whether the `qmd` binary is available and startable. If not, prints fix guidance including the npm package and a manual binary path option.
- **Explicit local provider**: checks for a local model file or a recognized remote/downloadable model URL. If missing, suggests switching to a remote provider.
- **Explicit remote provider** (`openai`, `voyage`, etc.): verifies an API key is present in the environment or auth store. Prints actionable fix hints if missing.
- **Legacy auto provider**: treats `memorySearch.provider: "auto"` as OpenAI and checks OpenAI readiness.
- **Legacy auto provider**: treats `memorySearch.provider: "auto"` as OpenAI, checks OpenAI readiness, and `doctor --fix` rewrites it to `provider: "openai"`.
When a cached gateway probe result is available (gateway was healthy at the time of the check), doctor cross-references its result with the CLI-visible config and notes any discrepancy. Doctor does not start a fresh embedding ping on the default path; use the deep memory status command when you want a live provider check.

View File

@@ -420,8 +420,15 @@ files. `SOUL.md`, `IDENTITY.md`, `TOOLS.md`, and `USER.md` are forwarded as
OpenClaw Codex developer instructions because they define the active agent,
available workspace guidance, and user profile. `HEARTBEAT.md` content is not
injected; heartbeat turns get a collaboration-mode pointer to read the file when
it exists and is non-empty. `BOOTSTRAP.md` and `MEMORY.md` when present are
forwarded as OpenClaw turn input reference context.
it exists and is non-empty. `MEMORY.md` content from the configured agent
workspace is not pasted into native Codex turn input when memory tools are
available for that workspace; when it exists, the harness adds a small
workspace-memory pointer and Codex should use `memory_search` or `memory_get`
when durable memory is relevant. If tools are disabled, memory search is
unavailable, or the active workspace differs from the agent memory workspace,
`MEMORY.md` uses the normal bounded turn-context path.
`BOOTSTRAP.md` when present is forwarded as OpenClaw turn input reference
context.
## Environment overrides

View File

@@ -140,39 +140,39 @@ commands.
## Official external packages
| Plugin | Description | Distribution | Surface |
| ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------- |
| [acpx](/plugins/reference/acpx) | Embedded ACP runtime backend with plugin-owned session and transport management. | `@openclaw/acpx`<br />npm; ClawHub | skills |
| [amazon-bedrock](/plugins/reference/amazon-bedrock) | Adds Amazon Bedrock model provider support to OpenClaw. | `@openclaw/amazon-bedrock-provider`<br />npm; ClawHub | providers: amazon-bedrock; contracts: memoryEmbeddingProviders |
| [amazon-bedrock-mantle](/plugins/reference/amazon-bedrock-mantle) | Adds Amazon Bedrock Mantle model provider support to OpenClaw. | `@openclaw/amazon-bedrock-mantle-provider`<br />npm; ClawHub | providers: amazon-bedrock-mantle |
| [anthropic-vertex](/plugins/reference/anthropic-vertex) | Adds Anthropic Vertex model provider support to OpenClaw. | `@openclaw/anthropic-vertex-provider`<br />npm; ClawHub | providers: anthropic-vertex |
| [brave](/plugins/reference/brave) | Adds web search provider support. | `@openclaw/brave-plugin`<br />npm; ClawHub | contracts: webSearchProviders |
| [codex](/plugins/reference/codex) | Codex app-server harness and Codex-managed GPT model catalog. | `@openclaw/codex`<br />npm; ClawHub | providers: codex; contracts: mediaUnderstandingProviders, migrationProviders |
| [diagnostics-otel](/plugins/reference/diagnostics-otel) | OpenClaw diagnostics OpenTelemetry exporter. | `@openclaw/diagnostics-otel`<br />npm; ClawHub: `clawhub:@openclaw/diagnostics-otel` | plugin |
| [diagnostics-prometheus](/plugins/reference/diagnostics-prometheus) | OpenClaw diagnostics Prometheus exporter. | `@openclaw/diagnostics-prometheus`<br />npm; ClawHub: `clawhub:@openclaw/diagnostics-prometheus` | plugin |
| [diffs](/plugins/reference/diffs) | Read-only diff viewer and file renderer for agents. | `@openclaw/diffs`<br />npm; ClawHub | contracts: tools; skills |
| [discord](/plugins/reference/discord) | Adds the Discord channel surface for sending and receiving OpenClaw messages. | `@openclaw/discord`<br />npm; ClawHub | channels: discord; contracts: transcriptSourceProviders |
| [feishu](/plugins/reference/feishu) | Adds the Feishu channel surface for sending and receiving OpenClaw messages. | `@openclaw/feishu`<br />npm; ClawHub | channels: feishu; contracts: tools; skills |
| [google-meet](/plugins/reference/google-meet) | Join Google Meet calls through Chrome or Twilio transports. | `@openclaw/google-meet`<br />npm; ClawHub | contracts: tools |
| [googlechat](/plugins/reference/googlechat) | Adds the Google Chat channel surface for sending and receiving OpenClaw messages. | `@openclaw/googlechat`<br />npm; ClawHub | channels: googlechat |
| [line](/plugins/reference/line) | Adds the LINE channel surface for sending and receiving OpenClaw messages. | `@openclaw/line`<br />npm; ClawHub | channels: line |
| [lobster](/plugins/reference/lobster) | Typed workflow tool with resumable approvals. | `@openclaw/lobster`<br />npm; ClawHub | contracts: tools |
| [matrix](/plugins/reference/matrix) | Adds the Matrix channel surface for sending and receiving OpenClaw messages. | `@openclaw/matrix`<br />ClawHub: `clawhub:@openclaw/matrix`; npm | channels: matrix |
| [memory-lancedb](/plugins/reference/memory-lancedb) | Adds agent-callable tools. | `@openclaw/memory-lancedb`<br />npm; ClawHub | contracts: tools |
| [msteams](/plugins/reference/msteams) | Adds the Microsoft Teams channel surface for sending and receiving OpenClaw messages. | `@openclaw/msteams`<br />npm; ClawHub | channels: msteams |
| [nextcloud-talk](/plugins/reference/nextcloud-talk) | Adds the Nextcloud Talk channel surface for sending and receiving OpenClaw messages. | `@openclaw/nextcloud-talk`<br />npm; ClawHub | channels: nextcloud-talk |
| [nostr](/plugins/reference/nostr) | Adds the Nostr channel surface for sending and receiving OpenClaw messages. | `@openclaw/nostr`<br />npm; ClawHub | channels: nostr |
| [openshell](/plugins/reference/openshell) | Sandbox backend powered by the NVIDIA OpenShell CLI with mirrored local workspaces and SSH-based command execution. | `@openclaw/openshell-sandbox`<br />npm; ClawHub | plugin |
| [pixverse](/plugins/reference/pixverse) | Adds PixVerse video generation provider support to OpenClaw. | `@openclaw/pixverse-provider`<br />npm; ClawHub | contracts: videoGenerationProviders |
| [qqbot](/plugins/reference/qqbot) | Adds the QQ Bot channel surface for sending and receiving OpenClaw messages. | `@openclaw/qqbot`<br />npm; ClawHub | channels: qqbot; contracts: tools; skills |
| [slack](/plugins/reference/slack) | Adds the Slack channel surface for sending and receiving OpenClaw messages. | `@openclaw/slack`<br />npm; ClawHub | channels: slack |
| [synology-chat](/plugins/reference/synology-chat) | Adds the Synology Chat channel surface for sending and receiving OpenClaw messages. | `@openclaw/synology-chat`<br />npm; ClawHub | channels: synology-chat |
| [tlon](/plugins/reference/tlon) | Adds the Tlon channel surface for sending and receiving OpenClaw messages. | `@openclaw/tlon`<br />npm; ClawHub | channels: tlon; skills |
| [twitch](/plugins/reference/twitch) | Adds the Twitch channel surface for sending and receiving OpenClaw messages. | `@openclaw/twitch`<br />npm; ClawHub | channels: twitch |
| [voice-call](/plugins/reference/voice-call) | Adds agent-callable tools. | `@openclaw/voice-call`<br />npm; ClawHub | contracts: tools |
| [whatsapp](/plugins/reference/whatsapp) | Adds the WhatsApp channel surface for sending and receiving OpenClaw messages. | `@openclaw/whatsapp`<br />ClawHub: `clawhub:@openclaw/whatsapp`; npm | channels: whatsapp |
| [zalo](/plugins/reference/zalo) | Adds the Zalo channel surface for sending and receiving OpenClaw messages. | `@openclaw/zalo`<br />npm; ClawHub | channels: zalo |
| [zalouser](/plugins/reference/zalouser) | Adds the Zalo Personal channel surface for sending and receiving OpenClaw messages. | `@openclaw/zalouser`<br />npm; ClawHub | channels: zalouser; contracts: tools |
| Plugin | Description | Distribution | Surface |
| ------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------- |
| [acpx](/plugins/reference/acpx) | OpenClaw ACP runtime backend with plugin-owned session and transport management. | `@openclaw/acpx`<br />npm; ClawHub | skills |
| [amazon-bedrock](/plugins/reference/amazon-bedrock) | OpenClaw Amazon Bedrock provider plugin with model discovery, embeddings, and guardrail support. | `@openclaw/amazon-bedrock-provider`<br />npm; ClawHub | providers: amazon-bedrock; contracts: memoryEmbeddingProviders |
| [amazon-bedrock-mantle](/plugins/reference/amazon-bedrock-mantle) | OpenClaw Amazon Bedrock Mantle provider plugin for OpenAI-compatible model routing. | `@openclaw/amazon-bedrock-mantle-provider`<br />npm; ClawHub | providers: amazon-bedrock-mantle |
| [anthropic-vertex](/plugins/reference/anthropic-vertex) | OpenClaw Anthropic Vertex provider plugin for Claude models on Google Vertex AI. | `@openclaw/anthropic-vertex-provider`<br />npm; ClawHub | providers: anthropic-vertex |
| [brave](/plugins/reference/brave) | OpenClaw Brave Search provider plugin for web search. | `@openclaw/brave-plugin`<br />npm; ClawHub | contracts: webSearchProviders |
| [codex](/plugins/reference/codex) | OpenClaw Codex app-server harness and model provider plugin with a Codex-managed GPT catalog. | `@openclaw/codex`<br />npm; ClawHub | providers: codex; contracts: mediaUnderstandingProviders, migrationProviders |
| [diagnostics-otel](/plugins/reference/diagnostics-otel) | OpenClaw diagnostics OpenTelemetry exporter for metrics and traces. | `@openclaw/diagnostics-otel`<br />npm; ClawHub: `clawhub:@openclaw/diagnostics-otel` | plugin |
| [diagnostics-prometheus](/plugins/reference/diagnostics-prometheus) | OpenClaw diagnostics Prometheus exporter for runtime metrics. | `@openclaw/diagnostics-prometheus`<br />npm; ClawHub: `clawhub:@openclaw/diagnostics-prometheus` | plugin |
| [diffs](/plugins/reference/diffs) | OpenClaw read-only diff viewer plugin and file renderer for agents. | `@openclaw/diffs`<br />npm; ClawHub | contracts: tools; skills |
| [discord](/plugins/reference/discord) | OpenClaw Discord channel plugin for channels, DMs, commands, and app events. | `@openclaw/discord`<br />npm; ClawHub | channels: discord; contracts: transcriptSourceProviders |
| [feishu](/plugins/reference/feishu) | OpenClaw Feishu/Lark channel plugin for chats and workplace tools (community maintained by @m1heng). | `@openclaw/feishu`<br />npm; ClawHub | channels: feishu; contracts: tools; skills |
| [google-meet](/plugins/reference/google-meet) | OpenClaw Google Meet participant plugin for joining calls through Chrome or Twilio transports. | `@openclaw/google-meet`<br />npm; ClawHub | contracts: tools |
| [googlechat](/plugins/reference/googlechat) | OpenClaw Google Chat channel plugin for spaces and direct messages. | `@openclaw/googlechat`<br />npm; ClawHub | channels: googlechat |
| [line](/plugins/reference/line) | OpenClaw LINE channel plugin for LINE Bot API chats. | `@openclaw/line`<br />npm; ClawHub | channels: line |
| [lobster](/plugins/reference/lobster) | Lobster workflow tool plugin for typed pipelines and resumable approvals. | `@openclaw/lobster`<br />npm; ClawHub | contracts: tools |
| [matrix](/plugins/reference/matrix) | OpenClaw Matrix channel plugin for rooms and direct messages. | `@openclaw/matrix`<br />ClawHub: `clawhub:@openclaw/matrix`; npm | channels: matrix |
| [memory-lancedb](/plugins/reference/memory-lancedb) | OpenClaw LanceDB-backed long-term memory plugin with auto-recall, auto-capture, and vector search. | `@openclaw/memory-lancedb`<br />npm; ClawHub | contracts: tools |
| [msteams](/plugins/reference/msteams) | OpenClaw Microsoft Teams channel plugin for bot conversations. | `@openclaw/msteams`<br />npm; ClawHub | channels: msteams |
| [nextcloud-talk](/plugins/reference/nextcloud-talk) | OpenClaw Nextcloud Talk channel plugin for conversations. | `@openclaw/nextcloud-talk`<br />npm; ClawHub | channels: nextcloud-talk |
| [nostr](/plugins/reference/nostr) | OpenClaw Nostr channel plugin for NIP-04 encrypted direct messages. | `@openclaw/nostr`<br />npm; ClawHub | channels: nostr |
| [openshell](/plugins/reference/openshell) | OpenClaw sandbox backend for the NVIDIA OpenShell CLI with mirrored local workspaces and SSH command execution. | `@openclaw/openshell-sandbox`<br />npm; ClawHub | plugin |
| [pixverse](/plugins/reference/pixverse) | OpenClaw PixVerse video generation provider plugin. | `@openclaw/pixverse-provider`<br />npm; ClawHub | contracts: videoGenerationProviders |
| [qqbot](/plugins/reference/qqbot) | OpenClaw QQ Bot channel plugin for group and direct-message workflows. | `@openclaw/qqbot`<br />npm; ClawHub | channels: qqbot; contracts: tools; skills |
| [slack](/plugins/reference/slack) | OpenClaw Slack channel plugin for channels, DMs, commands, and app events. | `@openclaw/slack`<br />npm; ClawHub | channels: slack |
| [synology-chat](/plugins/reference/synology-chat) | Synology Chat channel plugin for OpenClaw channels and direct messages. | `@openclaw/synology-chat`<br />npm; ClawHub | channels: synology-chat |
| [tlon](/plugins/reference/tlon) | OpenClaw Tlon/Urbit channel plugin for chat workflows. | `@openclaw/tlon`<br />npm; ClawHub | channels: tlon; skills |
| [twitch](/plugins/reference/twitch) | OpenClaw Twitch channel plugin for chat and moderation workflows. | `@openclaw/twitch`<br />npm; ClawHub | channels: twitch |
| [voice-call](/plugins/reference/voice-call) | OpenClaw voice-call plugin for Twilio, Telnyx, and Plivo phone calls. | `@openclaw/voice-call`<br />npm; ClawHub | contracts: tools |
| [whatsapp](/plugins/reference/whatsapp) | OpenClaw WhatsApp channel plugin for WhatsApp Web chats. | `@openclaw/whatsapp`<br />ClawHub: `clawhub:@openclaw/whatsapp`; npm | channels: whatsapp |
| [zalo](/plugins/reference/zalo) | OpenClaw Zalo channel plugin for bot and webhook chats. | `@openclaw/zalo`<br />npm; ClawHub | channels: zalo |
| [zalouser](/plugins/reference/zalouser) | OpenClaw Zalo Personal Account plugin via native zca-js integration. | `@openclaw/zalouser`<br />npm; ClawHub | channels: zalouser; contracts: tools |
## Source checkout only

View File

@@ -17,17 +17,17 @@ pnpm plugins:inventory:gen
| Plugin | Description | Distribution | Surface |
| ------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [acpx](/plugins/reference/acpx) | Embedded ACP runtime backend with plugin-owned session and transport management. | `@openclaw/acpx`<br />npm; ClawHub | skills |
| [acpx](/plugins/reference/acpx) | OpenClaw ACP runtime backend with plugin-owned session and transport management. | `@openclaw/acpx`<br />npm; ClawHub | skills |
| [admin-http-rpc](/plugins/reference/admin-http-rpc) | OpenClaw admin HTTP RPC endpoint. | `@openclaw/admin-http-rpc`<br />included in OpenClaw | contracts: gatewayMethodDispatch |
| [alibaba](/plugins/reference/alibaba) | Adds video generation provider support. | `@openclaw/alibaba-provider`<br />included in OpenClaw | contracts: videoGenerationProviders |
| [amazon-bedrock](/plugins/reference/amazon-bedrock) | Adds Amazon Bedrock model provider support to OpenClaw. | `@openclaw/amazon-bedrock-provider`<br />npm; ClawHub | providers: amazon-bedrock; contracts: memoryEmbeddingProviders |
| [amazon-bedrock-mantle](/plugins/reference/amazon-bedrock-mantle) | Adds Amazon Bedrock Mantle model provider support to OpenClaw. | `@openclaw/amazon-bedrock-mantle-provider`<br />npm; ClawHub | providers: amazon-bedrock-mantle |
| [amazon-bedrock](/plugins/reference/amazon-bedrock) | OpenClaw Amazon Bedrock provider plugin with model discovery, embeddings, and guardrail support. | `@openclaw/amazon-bedrock-provider`<br />npm; ClawHub | providers: amazon-bedrock; contracts: memoryEmbeddingProviders |
| [amazon-bedrock-mantle](/plugins/reference/amazon-bedrock-mantle) | OpenClaw Amazon Bedrock Mantle provider plugin for OpenAI-compatible model routing. | `@openclaw/amazon-bedrock-mantle-provider`<br />npm; ClawHub | providers: amazon-bedrock-mantle |
| [anthropic](/plugins/reference/anthropic) | Adds Anthropic model provider support to OpenClaw. | `@openclaw/anthropic-provider`<br />included in OpenClaw | providers: anthropic; contracts: mediaUnderstandingProviders |
| [anthropic-vertex](/plugins/reference/anthropic-vertex) | Adds Anthropic Vertex model provider support to OpenClaw. | `@openclaw/anthropic-vertex-provider`<br />npm; ClawHub | providers: anthropic-vertex |
| [anthropic-vertex](/plugins/reference/anthropic-vertex) | OpenClaw Anthropic Vertex provider plugin for Claude models on Google Vertex AI. | `@openclaw/anthropic-vertex-provider`<br />npm; ClawHub | providers: anthropic-vertex |
| [arcee](/plugins/reference/arcee) | Adds Arcee model provider support to OpenClaw. | `@openclaw/arcee-provider`<br />included in OpenClaw | providers: arcee |
| [azure-speech](/plugins/reference/azure-speech) | Azure AI Speech text-to-speech (MP3, native Ogg/Opus voice notes, PCM telephony). | `@openclaw/azure-speech`<br />included in OpenClaw | contracts: speechProviders |
| [bonjour](/plugins/reference/bonjour) | Advertise the local OpenClaw gateway over Bonjour/mDNS. | `@openclaw/bonjour`<br />included in OpenClaw | plugin |
| [brave](/plugins/reference/brave) | Adds web search provider support. | `@openclaw/brave-plugin`<br />npm; ClawHub | contracts: webSearchProviders |
| [brave](/plugins/reference/brave) | OpenClaw Brave Search provider plugin for web search. | `@openclaw/brave-plugin`<br />npm; ClawHub | contracts: webSearchProviders |
| [browser](/plugins/reference/browser) | Adds agent-callable tools. | `@openclaw/browser-plugin`<br />included in OpenClaw | contracts: tools; skills |
| [byteplus](/plugins/reference/byteplus) | Adds BytePlus, BytePlus Plan model provider support to OpenClaw. | `@openclaw/byteplus-provider`<br />included in OpenClaw | providers: byteplus, byteplus-plan; contracts: videoGenerationProviders |
| [canvas](/plugins/reference/canvas) | Experimental Canvas control and A2UI rendering surfaces for paired nodes. | `@openclaw/canvas-plugin`<br />included in OpenClaw | contracts: tools |
@@ -35,29 +35,29 @@ pnpm plugins:inventory:gen
| [chutes](/plugins/reference/chutes) | Adds Chutes model provider support to OpenClaw. | `@openclaw/chutes-provider`<br />included in OpenClaw | providers: chutes |
| [clickclack](/plugins/reference/clickclack) | Adds the Clickclack channel surface for sending and receiving OpenClaw messages. | `@openclaw/clickclack`<br />included in OpenClaw | channels: clickclack |
| [cloudflare-ai-gateway](/plugins/reference/cloudflare-ai-gateway) | Adds Cloudflare AI Gateway model provider support to OpenClaw. | `@openclaw/cloudflare-ai-gateway-provider`<br />included in OpenClaw | providers: cloudflare-ai-gateway |
| [codex](/plugins/reference/codex) | Codex app-server harness and Codex-managed GPT model catalog. | `@openclaw/codex`<br />npm; ClawHub | providers: codex; contracts: mediaUnderstandingProviders, migrationProviders |
| [codex](/plugins/reference/codex) | OpenClaw Codex app-server harness and model provider plugin with a Codex-managed GPT catalog. | `@openclaw/codex`<br />npm; ClawHub | providers: codex; contracts: mediaUnderstandingProviders, migrationProviders |
| [comfy](/plugins/reference/comfy) | Adds ComfyUI model provider support to OpenClaw. | `@openclaw/comfy-provider`<br />included in OpenClaw | providers: comfy; contracts: imageGenerationProviders, musicGenerationProviders, videoGenerationProviders |
| [copilot-proxy](/plugins/reference/copilot-proxy) | Adds Copilot Proxy model provider support to OpenClaw. | `@openclaw/copilot-proxy`<br />included in OpenClaw | providers: copilot-proxy |
| [deepgram](/plugins/reference/deepgram) | Adds media understanding provider support. Adds realtime transcription provider support. | `@openclaw/deepgram-provider`<br />included in OpenClaw | contracts: mediaUnderstandingProviders, realtimeTranscriptionProviders |
| [deepinfra](/plugins/reference/deepinfra) | Adds DeepInfra model provider support to OpenClaw. | `@openclaw/deepinfra-provider`<br />included in OpenClaw | providers: deepinfra; contracts: imageGenerationProviders, mediaUnderstandingProviders, memoryEmbeddingProviders, speechProviders, videoGenerationProviders |
| [deepseek](/plugins/reference/deepseek) | Adds DeepSeek model provider support to OpenClaw. | `@openclaw/deepseek-provider`<br />included in OpenClaw | providers: deepseek |
| [diagnostics-otel](/plugins/reference/diagnostics-otel) | OpenClaw diagnostics OpenTelemetry exporter. | `@openclaw/diagnostics-otel`<br />npm; ClawHub: `clawhub:@openclaw/diagnostics-otel` | plugin |
| [diagnostics-prometheus](/plugins/reference/diagnostics-prometheus) | OpenClaw diagnostics Prometheus exporter. | `@openclaw/diagnostics-prometheus`<br />npm; ClawHub: `clawhub:@openclaw/diagnostics-prometheus` | plugin |
| [diffs](/plugins/reference/diffs) | Read-only diff viewer and file renderer for agents. | `@openclaw/diffs`<br />npm; ClawHub | contracts: tools; skills |
| [discord](/plugins/reference/discord) | Adds the Discord channel surface for sending and receiving OpenClaw messages. | `@openclaw/discord`<br />npm; ClawHub | channels: discord; contracts: transcriptSourceProviders |
| [diagnostics-otel](/plugins/reference/diagnostics-otel) | OpenClaw diagnostics OpenTelemetry exporter for metrics and traces. | `@openclaw/diagnostics-otel`<br />npm; ClawHub: `clawhub:@openclaw/diagnostics-otel` | plugin |
| [diagnostics-prometheus](/plugins/reference/diagnostics-prometheus) | OpenClaw diagnostics Prometheus exporter for runtime metrics. | `@openclaw/diagnostics-prometheus`<br />npm; ClawHub: `clawhub:@openclaw/diagnostics-prometheus` | plugin |
| [diffs](/plugins/reference/diffs) | OpenClaw read-only diff viewer plugin and file renderer for agents. | `@openclaw/diffs`<br />npm; ClawHub | contracts: tools; skills |
| [discord](/plugins/reference/discord) | OpenClaw Discord channel plugin for channels, DMs, commands, and app events. | `@openclaw/discord`<br />npm; ClawHub | channels: discord; contracts: transcriptSourceProviders |
| [document-extract](/plugins/reference/document-extract) | Extract text and fallback page images from local document attachments. | `@openclaw/document-extract-plugin`<br />included in OpenClaw | contracts: documentExtractors |
| [duckduckgo](/plugins/reference/duckduckgo) | Adds web search provider support. | `@openclaw/duckduckgo-plugin`<br />included in OpenClaw | contracts: webSearchProviders |
| [elevenlabs](/plugins/reference/elevenlabs) | Adds media understanding provider support. Adds realtime transcription provider support. Adds text-to-speech provider support. | `@openclaw/elevenlabs-speech`<br />included in OpenClaw | contracts: mediaUnderstandingProviders, realtimeTranscriptionProviders, speechProviders |
| [exa](/plugins/reference/exa) | Adds web search provider support. | `@openclaw/exa-plugin`<br />included in OpenClaw | contracts: webSearchProviders |
| [fal](/plugins/reference/fal) | Adds fal model provider support to OpenClaw. | `@openclaw/fal-provider`<br />included in OpenClaw | providers: fal; contracts: imageGenerationProviders, musicGenerationProviders, videoGenerationProviders |
| [feishu](/plugins/reference/feishu) | Adds the Feishu channel surface for sending and receiving OpenClaw messages. | `@openclaw/feishu`<br />npm; ClawHub | channels: feishu; contracts: tools; skills |
| [feishu](/plugins/reference/feishu) | OpenClaw Feishu/Lark channel plugin for chats and workplace tools (community maintained by @m1heng). | `@openclaw/feishu`<br />npm; ClawHub | channels: feishu; contracts: tools; skills |
| [file-transfer](/plugins/reference/file-transfer) | Fetch, list, and write files on paired nodes via dedicated node commands. Bypasses bash stdout truncation by using base64 over node.invoke for binaries up to 16 MB. | `@openclaw/file-transfer`<br />included in OpenClaw | contracts: tools |
| [firecrawl](/plugins/reference/firecrawl) | Adds agent-callable tools. Adds web fetch provider support. Adds web search provider support. | `@openclaw/firecrawl-plugin`<br />included in OpenClaw | contracts: tools, webFetchProviders, webSearchProviders |
| [fireworks](/plugins/reference/fireworks) | Adds Fireworks model provider support to OpenClaw. | `@openclaw/fireworks-provider`<br />included in OpenClaw | providers: fireworks |
| [github-copilot](/plugins/reference/github-copilot) | Adds GitHub Copilot model provider support to OpenClaw. | `@openclaw/github-copilot-provider`<br />included in OpenClaw | providers: github-copilot; contracts: memoryEmbeddingProviders |
| [google](/plugins/reference/google) | Adds Google, Google Gemini CLI, Google Vertex model provider support to OpenClaw. | `@openclaw/google-plugin`<br />included in OpenClaw | providers: google, google-gemini-cli, google-vertex; contracts: imageGenerationProviders, mediaUnderstandingProviders, memoryEmbeddingProviders, musicGenerationProviders, realtimeVoiceProviders, speechProviders, videoGenerationProviders, webSearchProviders |
| [google-meet](/plugins/reference/google-meet) | Join Google Meet calls through Chrome or Twilio transports. | `@openclaw/google-meet`<br />npm; ClawHub | contracts: tools |
| [googlechat](/plugins/reference/googlechat) | Adds the Google Chat channel surface for sending and receiving OpenClaw messages. | `@openclaw/googlechat`<br />npm; ClawHub | channels: googlechat |
| [google-meet](/plugins/reference/google-meet) | OpenClaw Google Meet participant plugin for joining calls through Chrome or Twilio transports. | `@openclaw/google-meet`<br />npm; ClawHub | contracts: tools |
| [googlechat](/plugins/reference/googlechat) | OpenClaw Google Chat channel plugin for spaces and direct messages. | `@openclaw/googlechat`<br />npm; ClawHub | channels: googlechat |
| [gradium](/plugins/reference/gradium) | Adds text-to-speech provider support. | `@openclaw/gradium-speech`<br />included in OpenClaw | contracts: speechProviders |
| [groq](/plugins/reference/groq) | Adds Groq model provider support to OpenClaw. | `@openclaw/groq-provider`<br />included in OpenClaw | providers: groq; contracts: mediaUnderstandingProviders |
| [huggingface](/plugins/reference/huggingface) | Adds Hugging Face model provider support to OpenClaw. | `@openclaw/huggingface-provider`<br />included in OpenClaw | providers: huggingface |
@@ -66,15 +66,15 @@ pnpm plugins:inventory:gen
| [irc](/plugins/reference/irc) | Adds the IRC channel surface for sending and receiving OpenClaw messages. | `@openclaw/irc`<br />included in OpenClaw | channels: irc |
| [kilocode](/plugins/reference/kilocode) | Adds Kilocode model provider support to OpenClaw. | `@openclaw/kilocode-provider`<br />included in OpenClaw | providers: kilocode |
| [kimi](/plugins/reference/kimi) | Adds Kimi, Kimi Coding model provider support to OpenClaw. | `@openclaw/kimi-provider`<br />included in OpenClaw | providers: kimi, kimi-coding |
| [line](/plugins/reference/line) | Adds the LINE channel surface for sending and receiving OpenClaw messages. | `@openclaw/line`<br />npm; ClawHub | channels: line |
| [line](/plugins/reference/line) | OpenClaw LINE channel plugin for LINE Bot API chats. | `@openclaw/line`<br />npm; ClawHub | channels: line |
| [litellm](/plugins/reference/litellm) | Adds LiteLLM model provider support to OpenClaw. | `@openclaw/litellm-provider`<br />included in OpenClaw | providers: litellm; contracts: imageGenerationProviders |
| [llm-task](/plugins/reference/llm-task) | Generic JSON-only LLM tool for structured tasks callable from workflows. | `@openclaw/llm-task`<br />included in OpenClaw | contracts: tools |
| [lmstudio](/plugins/reference/lmstudio) | Adds LM Studio model provider support to OpenClaw. | `@openclaw/lmstudio-provider`<br />included in OpenClaw | providers: lmstudio; contracts: memoryEmbeddingProviders |
| [lobster](/plugins/reference/lobster) | Typed workflow tool with resumable approvals. | `@openclaw/lobster`<br />npm; ClawHub | contracts: tools |
| [matrix](/plugins/reference/matrix) | Adds the Matrix channel surface for sending and receiving OpenClaw messages. | `@openclaw/matrix`<br />ClawHub: `clawhub:@openclaw/matrix`; npm | channels: matrix |
| [lobster](/plugins/reference/lobster) | Lobster workflow tool plugin for typed pipelines and resumable approvals. | `@openclaw/lobster`<br />npm; ClawHub | contracts: tools |
| [matrix](/plugins/reference/matrix) | OpenClaw Matrix channel plugin for rooms and direct messages. | `@openclaw/matrix`<br />ClawHub: `clawhub:@openclaw/matrix`; npm | channels: matrix |
| [mattermost](/plugins/reference/mattermost) | Adds the Mattermost channel surface for sending and receiving OpenClaw messages. | `@openclaw/mattermost`<br />included in OpenClaw | channels: mattermost |
| [memory-core](/plugins/reference/memory-core) | Adds memory embedding provider support. Adds agent-callable tools. | `@openclaw/memory-core`<br />included in OpenClaw | contracts: memoryEmbeddingProviders, tools |
| [memory-lancedb](/plugins/reference/memory-lancedb) | Adds agent-callable tools. | `@openclaw/memory-lancedb`<br />npm; ClawHub | contracts: tools |
| [memory-lancedb](/plugins/reference/memory-lancedb) | OpenClaw LanceDB-backed long-term memory plugin with auto-recall, auto-capture, and vector search. | `@openclaw/memory-lancedb`<br />npm; ClawHub | contracts: tools |
| [memory-wiki](/plugins/reference/memory-wiki) | Persistent wiki compiler and Obsidian-friendly knowledge vault for OpenClaw. | `@openclaw/memory-wiki`<br />included in OpenClaw | contracts: tools; skills |
| [microsoft](/plugins/reference/microsoft) | Adds text-to-speech provider support. | `@openclaw/microsoft-speech`<br />included in OpenClaw | contracts: speechProviders |
| [microsoft-foundry](/plugins/reference/microsoft-foundry) | Adds Microsoft Foundry model provider support to OpenClaw. | `@openclaw/microsoft-foundry`<br />included in OpenClaw | providers: microsoft-foundry |
@@ -83,9 +83,9 @@ pnpm plugins:inventory:gen
| [minimax](/plugins/reference/minimax) | Adds MiniMax, MiniMax Portal model provider support to OpenClaw. | `@openclaw/minimax-provider`<br />included in OpenClaw | providers: minimax, minimax-portal; contracts: imageGenerationProviders, mediaUnderstandingProviders, musicGenerationProviders, speechProviders, videoGenerationProviders, webSearchProviders |
| [mistral](/plugins/reference/mistral) | Adds Mistral model provider support to OpenClaw. | `@openclaw/mistral-provider`<br />included in OpenClaw | providers: mistral; contracts: mediaUnderstandingProviders, memoryEmbeddingProviders, realtimeTranscriptionProviders |
| [moonshot](/plugins/reference/moonshot) | Adds Moonshot model provider support to OpenClaw. | `@openclaw/moonshot-provider`<br />included in OpenClaw | providers: moonshot; contracts: mediaUnderstandingProviders, webSearchProviders |
| [msteams](/plugins/reference/msteams) | Adds the Microsoft Teams channel surface for sending and receiving OpenClaw messages. | `@openclaw/msteams`<br />npm; ClawHub | channels: msteams |
| [nextcloud-talk](/plugins/reference/nextcloud-talk) | Adds the Nextcloud Talk channel surface for sending and receiving OpenClaw messages. | `@openclaw/nextcloud-talk`<br />npm; ClawHub | channels: nextcloud-talk |
| [nostr](/plugins/reference/nostr) | Adds the Nostr channel surface for sending and receiving OpenClaw messages. | `@openclaw/nostr`<br />npm; ClawHub | channels: nostr |
| [msteams](/plugins/reference/msteams) | OpenClaw Microsoft Teams channel plugin for bot conversations. | `@openclaw/msteams`<br />npm; ClawHub | channels: msteams |
| [nextcloud-talk](/plugins/reference/nextcloud-talk) | OpenClaw Nextcloud Talk channel plugin for conversations. | `@openclaw/nextcloud-talk`<br />npm; ClawHub | channels: nextcloud-talk |
| [nostr](/plugins/reference/nostr) | OpenClaw Nostr channel plugin for NIP-04 encrypted direct messages. | `@openclaw/nostr`<br />npm; ClawHub | channels: nostr |
| [nvidia](/plugins/reference/nvidia) | Adds NVIDIA model provider support to OpenClaw. | `@openclaw/nvidia-provider`<br />included in OpenClaw | providers: nvidia |
| [oc-path](/plugins/reference/oc-path) | Adds the openclaw path CLI for oc:// workspace file addressing. | `@openclaw/oc-path`<br />included in OpenClaw | plugin |
| [ollama](/plugins/reference/ollama) | Adds Ollama model provider support to OpenClaw. | `@openclaw/ollama-provider`<br />included in OpenClaw | providers: ollama; contracts: memoryEmbeddingProviders, webSearchProviders |
@@ -94,15 +94,15 @@ pnpm plugins:inventory:gen
| [opencode](/plugins/reference/opencode) | Adds OpenCode model provider support to OpenClaw. | `@openclaw/opencode-provider`<br />included in OpenClaw | providers: opencode; contracts: mediaUnderstandingProviders |
| [opencode-go](/plugins/reference/opencode-go) | Adds OpenCode Go model provider support to OpenClaw. | `@openclaw/opencode-go-provider`<br />included in OpenClaw | providers: opencode-go; contracts: mediaUnderstandingProviders |
| [openrouter](/plugins/reference/openrouter) | Adds OpenRouter model provider support to OpenClaw. | `@openclaw/openrouter-provider`<br />included in OpenClaw | providers: openrouter; contracts: imageGenerationProviders, mediaUnderstandingProviders, musicGenerationProviders, speechProviders, videoGenerationProviders |
| [openshell](/plugins/reference/openshell) | Sandbox backend powered by the NVIDIA OpenShell CLI with mirrored local workspaces and SSH-based command execution. | `@openclaw/openshell-sandbox`<br />npm; ClawHub | plugin |
| [openshell](/plugins/reference/openshell) | OpenClaw sandbox backend for the NVIDIA OpenShell CLI with mirrored local workspaces and SSH command execution. | `@openclaw/openshell-sandbox`<br />npm; ClawHub | plugin |
| [perplexity](/plugins/reference/perplexity) | Adds web search provider support. | `@openclaw/perplexity-plugin`<br />included in OpenClaw | contracts: webSearchProviders |
| [pixverse](/plugins/reference/pixverse) | Adds PixVerse video generation provider support to OpenClaw. | `@openclaw/pixverse-provider`<br />npm; ClawHub | contracts: videoGenerationProviders |
| [pixverse](/plugins/reference/pixverse) | OpenClaw PixVerse video generation provider plugin. | `@openclaw/pixverse-provider`<br />npm; ClawHub | contracts: videoGenerationProviders |
| [policy](/plugins/reference/policy) | Adds policy-backed doctor checks for workspace conformance. | `@openclaw/policy`<br />included in OpenClaw | plugin |
| [qa-channel](/plugins/reference/qa-channel) | Adds the QA Channel surface for sending and receiving OpenClaw messages. | `@openclaw/qa-channel`<br />source checkout only | channels: qa-channel |
| [qa-lab](/plugins/reference/qa-lab) | OpenClaw QA lab plugin with private debugger UI and scenario runner. | `@openclaw/qa-lab`<br />source checkout only | plugin |
| [qa-matrix](/plugins/reference/qa-matrix) | Matrix QA transport runner and substrate. | `@openclaw/qa-matrix`<br />source checkout only | plugin |
| [qianfan](/plugins/reference/qianfan) | Adds Qianfan model provider support to OpenClaw. | `@openclaw/qianfan-provider`<br />included in OpenClaw | providers: qianfan |
| [qqbot](/plugins/reference/qqbot) | Adds the QQ Bot channel surface for sending and receiving OpenClaw messages. | `@openclaw/qqbot`<br />npm; ClawHub | channels: qqbot; contracts: tools; skills |
| [qqbot](/plugins/reference/qqbot) | OpenClaw QQ Bot channel plugin for group and direct-message workflows. | `@openclaw/qqbot`<br />npm; ClawHub | channels: qqbot; contracts: tools; skills |
| [qwen](/plugins/reference/qwen) | Adds Qwen, Qwen Cloud, Model Studio, DashScope model provider support to OpenClaw. | `@openclaw/qwen-provider`<br />included in OpenClaw | providers: qwen, qwencloud, modelstudio, dashscope; contracts: mediaUnderstandingProviders, videoGenerationProviders |
| [runway](/plugins/reference/runway) | Adds video generation provider support. | `@openclaw/runway-provider`<br />included in OpenClaw | contracts: videoGenerationProviders |
| [searxng](/plugins/reference/searxng) | Adds web search provider support. | `@openclaw/searxng-plugin`<br />included in OpenClaw | contracts: webSearchProviders |
@@ -110,30 +110,30 @@ pnpm plugins:inventory:gen
| [sglang](/plugins/reference/sglang) | Adds SGLang model provider support to OpenClaw. | `@openclaw/sglang-provider`<br />included in OpenClaw | providers: sglang |
| [signal](/plugins/reference/signal) | Adds the Signal channel surface for sending and receiving OpenClaw messages. | `@openclaw/signal`<br />included in OpenClaw | channels: signal |
| [skill-workshop](/plugins/reference/skill-workshop) | Captures repeatable workflows as workspace skills, with pending review, safe writes, and skill prompt refresh. | `@openclaw/skill-workshop`<br />included in OpenClaw | contracts: tools |
| [slack](/plugins/reference/slack) | Adds the Slack channel surface for sending and receiving OpenClaw messages. | `@openclaw/slack`<br />npm; ClawHub | channels: slack |
| [slack](/plugins/reference/slack) | OpenClaw Slack channel plugin for channels, DMs, commands, and app events. | `@openclaw/slack`<br />npm; ClawHub | channels: slack |
| [stepfun](/plugins/reference/stepfun) | Adds StepFun, StepFun Plan model provider support to OpenClaw. | `@openclaw/stepfun-provider`<br />included in OpenClaw | providers: stepfun, stepfun-plan |
| [synology-chat](/plugins/reference/synology-chat) | Adds the Synology Chat channel surface for sending and receiving OpenClaw messages. | `@openclaw/synology-chat`<br />npm; ClawHub | channels: synology-chat |
| [synology-chat](/plugins/reference/synology-chat) | Synology Chat channel plugin for OpenClaw channels and direct messages. | `@openclaw/synology-chat`<br />npm; ClawHub | channels: synology-chat |
| [synthetic](/plugins/reference/synthetic) | Adds Synthetic model provider support to OpenClaw. | `@openclaw/synthetic-provider`<br />included in OpenClaw | providers: synthetic |
| [tavily](/plugins/reference/tavily) | Adds agent-callable tools. Adds web search provider support. | `@openclaw/tavily-plugin`<br />included in OpenClaw | contracts: tools, webSearchProviders; skills |
| [telegram](/plugins/reference/telegram) | Adds the Telegram channel surface for sending and receiving OpenClaw messages. | `@openclaw/telegram`<br />included in OpenClaw | channels: telegram |
| [tencent](/plugins/reference/tencent) | Adds Tencent TokenHub model provider support to OpenClaw. | `@openclaw/tencent-provider`<br />included in OpenClaw | providers: tencent-tokenhub |
| [tlon](/plugins/reference/tlon) | Adds the Tlon channel surface for sending and receiving OpenClaw messages. | `@openclaw/tlon`<br />npm; ClawHub | channels: tlon; skills |
| [tlon](/plugins/reference/tlon) | OpenClaw Tlon/Urbit channel plugin for chat workflows. | `@openclaw/tlon`<br />npm; ClawHub | channels: tlon; skills |
| [together](/plugins/reference/together) | Adds Together model provider support to OpenClaw. | `@openclaw/together-provider`<br />included in OpenClaw | providers: together; contracts: videoGenerationProviders |
| [tokenjuice](/plugins/reference/tokenjuice) | Compacts exec and bash tool results with tokenjuice reducers. | `@openclaw/tokenjuice`<br />included in OpenClaw | contracts: agentToolResultMiddleware |
| [tts-local-cli](/plugins/reference/tts-local-cli) | Adds text-to-speech provider support. | `@openclaw/tts-local-cli`<br />included in OpenClaw | contracts: speechProviders |
| [twitch](/plugins/reference/twitch) | Adds the Twitch channel surface for sending and receiving OpenClaw messages. | `@openclaw/twitch`<br />npm; ClawHub | channels: twitch |
| [twitch](/plugins/reference/twitch) | OpenClaw Twitch channel plugin for chat and moderation workflows. | `@openclaw/twitch`<br />npm; ClawHub | channels: twitch |
| [venice](/plugins/reference/venice) | Adds Venice model provider support to OpenClaw. | `@openclaw/venice-provider`<br />included in OpenClaw | providers: venice |
| [vercel-ai-gateway](/plugins/reference/vercel-ai-gateway) | Adds Vercel AI Gateway model provider support to OpenClaw. | `@openclaw/vercel-ai-gateway-provider`<br />included in OpenClaw | providers: vercel-ai-gateway |
| [vllm](/plugins/reference/vllm) | Adds vLLM model provider support to OpenClaw. | `@openclaw/vllm-provider`<br />included in OpenClaw | providers: vllm |
| [voice-call](/plugins/reference/voice-call) | Adds agent-callable tools. | `@openclaw/voice-call`<br />npm; ClawHub | contracts: tools |
| [voice-call](/plugins/reference/voice-call) | OpenClaw voice-call plugin for Twilio, Telnyx, and Plivo phone calls. | `@openclaw/voice-call`<br />npm; ClawHub | contracts: tools |
| [volcengine](/plugins/reference/volcengine) | Adds Volcengine, Volcengine Plan model provider support to OpenClaw. | `@openclaw/volcengine-provider`<br />included in OpenClaw | providers: volcengine, volcengine-plan; contracts: speechProviders |
| [voyage](/plugins/reference/voyage) | Adds memory embedding provider support. | `@openclaw/voyage-provider`<br />included in OpenClaw | contracts: memoryEmbeddingProviders |
| [vydra](/plugins/reference/vydra) | Adds Vydra model provider support to OpenClaw. | `@openclaw/vydra-provider`<br />included in OpenClaw | providers: vydra; contracts: imageGenerationProviders, speechProviders, videoGenerationProviders |
| [web-readability](/plugins/reference/web-readability) | Extract readable article content from local HTML web fetch responses. | `@openclaw/web-readability-plugin`<br />included in OpenClaw | contracts: webContentExtractors |
| [webhooks](/plugins/reference/webhooks) | Authenticated inbound webhooks that bind external automation to OpenClaw TaskFlows. | `@openclaw/webhooks`<br />included in OpenClaw | plugin |
| [whatsapp](/plugins/reference/whatsapp) | Adds the WhatsApp channel surface for sending and receiving OpenClaw messages. | `@openclaw/whatsapp`<br />ClawHub: `clawhub:@openclaw/whatsapp`; npm | channels: whatsapp |
| [whatsapp](/plugins/reference/whatsapp) | OpenClaw WhatsApp channel plugin for WhatsApp Web chats. | `@openclaw/whatsapp`<br />ClawHub: `clawhub:@openclaw/whatsapp`; npm | channels: whatsapp |
| [xai](/plugins/reference/xai) | Adds xAI model provider support to OpenClaw. | `@openclaw/xai-plugin`<br />included in OpenClaw | providers: xai; contracts: imageGenerationProviders, mediaUnderstandingProviders, realtimeTranscriptionProviders, speechProviders, tools, videoGenerationProviders, webSearchProviders |
| [xiaomi](/plugins/reference/xiaomi) | Adds Xiaomi model provider support to OpenClaw. | `@openclaw/xiaomi-provider`<br />included in OpenClaw | providers: xiaomi; contracts: speechProviders |
| [zai](/plugins/reference/zai) | Adds Z.AI model provider support to OpenClaw. | `@openclaw/zai-provider`<br />included in OpenClaw | providers: zai; contracts: mediaUnderstandingProviders |
| [zalo](/plugins/reference/zalo) | Adds the Zalo channel surface for sending and receiving OpenClaw messages. | `@openclaw/zalo`<br />npm; ClawHub | channels: zalo |
| [zalouser](/plugins/reference/zalouser) | Adds the Zalo Personal channel surface for sending and receiving OpenClaw messages. | `@openclaw/zalouser`<br />npm; ClawHub | channels: zalouser; contracts: tools |
| [zalo](/plugins/reference/zalo) | OpenClaw Zalo channel plugin for bot and webhook chats. | `@openclaw/zalo`<br />npm; ClawHub | channels: zalo |
| [zalouser](/plugins/reference/zalouser) | OpenClaw Zalo Personal Account plugin via native zca-js integration. | `@openclaw/zalouser`<br />npm; ClawHub | channels: zalouser; contracts: tools |

View File

@@ -1,5 +1,5 @@
---
summary: "Embedded ACP runtime backend with plugin-owned session and transport management."
summary: "OpenClaw ACP runtime backend with plugin-owned session and transport management."
read_when:
- You are installing, configuring, or auditing the acpx plugin
title: "ACPx plugin"
@@ -7,7 +7,7 @@ title: "ACPx plugin"
# ACPx plugin
Embedded ACP runtime backend with plugin-owned session and transport management.
OpenClaw ACP runtime backend with plugin-owned session and transport management.
## Distribution

View File

@@ -1,5 +1,5 @@
---
summary: "Adds Amazon Bedrock Mantle model provider support to OpenClaw."
summary: "OpenClaw Amazon Bedrock Mantle provider plugin for OpenAI-compatible model routing."
read_when:
- You are installing, configuring, or auditing the amazon-bedrock-mantle plugin
title: "Amazon Bedrock Mantle plugin"
@@ -7,7 +7,7 @@ title: "Amazon Bedrock Mantle plugin"
# Amazon Bedrock Mantle plugin
Adds Amazon Bedrock Mantle model provider support to OpenClaw.
OpenClaw Amazon Bedrock Mantle provider plugin for OpenAI-compatible model routing.
## Distribution

View File

@@ -1,5 +1,5 @@
---
summary: "Adds Amazon Bedrock model provider support to OpenClaw."
summary: "OpenClaw Amazon Bedrock provider plugin with model discovery, embeddings, and guardrail support."
read_when:
- You are installing, configuring, or auditing the amazon-bedrock plugin
title: "Amazon Bedrock plugin"
@@ -7,7 +7,7 @@ title: "Amazon Bedrock plugin"
# Amazon Bedrock plugin
Adds Amazon Bedrock model provider support to OpenClaw.
OpenClaw Amazon Bedrock provider plugin with model discovery, embeddings, and guardrail support.
## Distribution

View File

@@ -1,5 +1,5 @@
---
summary: "Adds Anthropic Vertex model provider support to OpenClaw."
summary: "OpenClaw Anthropic Vertex provider plugin for Claude models on Google Vertex AI."
read_when:
- You are installing, configuring, or auditing the anthropic-vertex plugin
title: "Anthropic Vertex plugin"
@@ -7,7 +7,7 @@ title: "Anthropic Vertex plugin"
# Anthropic Vertex plugin
Adds Anthropic Vertex model provider support to OpenClaw.
OpenClaw Anthropic Vertex provider plugin for Claude models on Google Vertex AI.
## Distribution

View File

@@ -1,5 +1,5 @@
---
summary: "Adds web search provider support."
summary: "OpenClaw Brave Search provider plugin for web search."
read_when:
- You are installing, configuring, or auditing the brave plugin
title: "Brave plugin"
@@ -7,7 +7,7 @@ title: "Brave plugin"
# Brave plugin
Adds web search provider support.
OpenClaw Brave Search provider plugin for web search.
## Distribution

View File

@@ -1,5 +1,5 @@
---
summary: "Codex app-server harness and Codex-managed GPT model catalog."
summary: "OpenClaw Codex app-server harness and model provider plugin with a Codex-managed GPT catalog."
read_when:
- You are installing, configuring, or auditing the codex plugin
title: "Codex plugin"
@@ -7,7 +7,7 @@ title: "Codex plugin"
# Codex plugin
Codex app-server harness and Codex-managed GPT model catalog.
OpenClaw Codex app-server harness and model provider plugin with a Codex-managed GPT catalog.
## Distribution

View File

@@ -1,5 +1,5 @@
---
summary: "OpenClaw diagnostics OpenTelemetry exporter."
summary: "OpenClaw diagnostics OpenTelemetry exporter for metrics and traces."
read_when:
- You are installing, configuring, or auditing the diagnostics-otel plugin
title: "Diagnostics OpenTelemetry plugin"
@@ -7,7 +7,7 @@ title: "Diagnostics OpenTelemetry plugin"
# Diagnostics OpenTelemetry plugin
OpenClaw diagnostics OpenTelemetry exporter.
OpenClaw diagnostics OpenTelemetry exporter for metrics and traces.
## Distribution

View File

@@ -1,5 +1,5 @@
---
summary: "OpenClaw diagnostics Prometheus exporter."
summary: "OpenClaw diagnostics Prometheus exporter for runtime metrics."
read_when:
- You are installing, configuring, or auditing the diagnostics-prometheus plugin
title: "Diagnostics Prometheus plugin"
@@ -7,7 +7,7 @@ title: "Diagnostics Prometheus plugin"
# Diagnostics Prometheus plugin
OpenClaw diagnostics Prometheus exporter.
OpenClaw diagnostics Prometheus exporter for runtime metrics.
## Distribution

View File

@@ -1,5 +1,5 @@
---
summary: "Read-only diff viewer and file renderer for agents."
summary: "OpenClaw read-only diff viewer plugin and file renderer for agents."
read_when:
- You are installing, configuring, or auditing the diffs plugin
title: "Diffs plugin"
@@ -7,7 +7,7 @@ title: "Diffs plugin"
# Diffs plugin
Read-only diff viewer and file renderer for agents.
OpenClaw read-only diff viewer plugin and file renderer for agents.
## Distribution

View File

@@ -1,5 +1,5 @@
---
summary: "Adds the Discord channel surface for sending and receiving OpenClaw messages."
summary: "OpenClaw Discord channel plugin for channels, DMs, commands, and app events."
read_when:
- You are installing, configuring, or auditing the discord plugin
title: "Discord plugin"
@@ -7,7 +7,7 @@ title: "Discord plugin"
# Discord plugin
Adds the Discord channel surface for sending and receiving OpenClaw messages.
OpenClaw Discord channel plugin for channels, DMs, commands, and app events.
## Distribution

View File

@@ -1,5 +1,5 @@
---
summary: "Adds the Feishu channel surface for sending and receiving OpenClaw messages."
summary: "OpenClaw Feishu/Lark channel plugin for chats and workplace tools (community maintained by @m1heng)."
read_when:
- You are installing, configuring, or auditing the feishu plugin
title: "Feishu plugin"
@@ -7,7 +7,7 @@ title: "Feishu plugin"
# Feishu plugin
Adds the Feishu channel surface for sending and receiving OpenClaw messages.
OpenClaw Feishu/Lark channel plugin for chats and workplace tools (community maintained by @m1heng).
## Distribution

View File

@@ -1,5 +1,5 @@
---
summary: "Join Google Meet calls through Chrome or Twilio transports."
summary: "OpenClaw Google Meet participant plugin for joining calls through Chrome or Twilio transports."
read_when:
- You are installing, configuring, or auditing the google-meet plugin
title: "Google Meet plugin"
@@ -7,7 +7,7 @@ title: "Google Meet plugin"
# Google Meet plugin
Join Google Meet calls through Chrome or Twilio transports.
OpenClaw Google Meet participant plugin for joining calls through Chrome or Twilio transports.
## Distribution

View File

@@ -1,5 +1,5 @@
---
summary: "Adds the Google Chat channel surface for sending and receiving OpenClaw messages."
summary: "OpenClaw Google Chat channel plugin for spaces and direct messages."
read_when:
- You are installing, configuring, or auditing the googlechat plugin
title: "Google Chat plugin"
@@ -7,7 +7,7 @@ title: "Google Chat plugin"
# Google Chat plugin
Adds the Google Chat channel surface for sending and receiving OpenClaw messages.
OpenClaw Google Chat channel plugin for spaces and direct messages.
## Distribution

View File

@@ -1,5 +1,5 @@
---
summary: "Adds the LINE channel surface for sending and receiving OpenClaw messages."
summary: "OpenClaw LINE channel plugin for LINE Bot API chats."
read_when:
- You are installing, configuring, or auditing the line plugin
title: "LINE plugin"
@@ -7,7 +7,7 @@ title: "LINE plugin"
# LINE plugin
Adds the LINE channel surface for sending and receiving OpenClaw messages.
OpenClaw LINE channel plugin for LINE Bot API chats.
## Distribution

View File

@@ -1,5 +1,5 @@
---
summary: "Typed workflow tool with resumable approvals."
summary: "Lobster workflow tool plugin for typed pipelines and resumable approvals."
read_when:
- You are installing, configuring, or auditing the lobster plugin
title: "Lobster plugin"
@@ -7,7 +7,7 @@ title: "Lobster plugin"
# Lobster plugin
Typed workflow tool with resumable approvals.
Lobster workflow tool plugin for typed pipelines and resumable approvals.
## Distribution

View File

@@ -1,5 +1,5 @@
---
summary: "Adds the Matrix channel surface for sending and receiving OpenClaw messages."
summary: "OpenClaw Matrix channel plugin for rooms and direct messages."
read_when:
- You are installing, configuring, or auditing the matrix plugin
title: "Matrix plugin"
@@ -7,7 +7,7 @@ title: "Matrix plugin"
# Matrix plugin
Adds the Matrix channel surface for sending and receiving OpenClaw messages.
OpenClaw Matrix channel plugin for rooms and direct messages.
## Distribution

View File

@@ -1,5 +1,5 @@
---
summary: "Adds agent-callable tools."
summary: "OpenClaw LanceDB-backed long-term memory plugin with auto-recall, auto-capture, and vector search."
read_when:
- You are installing, configuring, or auditing the memory-lancedb plugin
title: "Memory Lancedb plugin"
@@ -7,7 +7,7 @@ title: "Memory Lancedb plugin"
# Memory Lancedb plugin
Adds agent-callable tools.
OpenClaw LanceDB-backed long-term memory plugin with auto-recall, auto-capture, and vector search.
## Distribution

View File

@@ -1,5 +1,5 @@
---
summary: "Adds the Microsoft Teams channel surface for sending and receiving OpenClaw messages."
summary: "OpenClaw Microsoft Teams channel plugin for bot conversations."
read_when:
- You are installing, configuring, or auditing the msteams plugin
title: "Microsoft Teams plugin"
@@ -7,7 +7,7 @@ title: "Microsoft Teams plugin"
# Microsoft Teams plugin
Adds the Microsoft Teams channel surface for sending and receiving OpenClaw messages.
OpenClaw Microsoft Teams channel plugin for bot conversations.
## Distribution

View File

@@ -1,5 +1,5 @@
---
summary: "Adds the Nextcloud Talk channel surface for sending and receiving OpenClaw messages."
summary: "OpenClaw Nextcloud Talk channel plugin for conversations."
read_when:
- You are installing, configuring, or auditing the nextcloud-talk plugin
title: "Nextcloud Talk plugin"
@@ -7,7 +7,7 @@ title: "Nextcloud Talk plugin"
# Nextcloud Talk plugin
Adds the Nextcloud Talk channel surface for sending and receiving OpenClaw messages.
OpenClaw Nextcloud Talk channel plugin for conversations.
## Distribution

View File

@@ -1,5 +1,5 @@
---
summary: "Adds the Nostr channel surface for sending and receiving OpenClaw messages."
summary: "OpenClaw Nostr channel plugin for NIP-04 encrypted direct messages."
read_when:
- You are installing, configuring, or auditing the nostr plugin
title: "Nostr plugin"
@@ -7,7 +7,7 @@ title: "Nostr plugin"
# Nostr plugin
Adds the Nostr channel surface for sending and receiving OpenClaw messages.
OpenClaw Nostr channel plugin for NIP-04 encrypted direct messages.
## Distribution

View File

@@ -1,5 +1,5 @@
---
summary: "Sandbox backend powered by the NVIDIA OpenShell CLI with mirrored local workspaces and SSH-based command execution."
summary: "OpenClaw sandbox backend for the NVIDIA OpenShell CLI with mirrored local workspaces and SSH command execution."
read_when:
- You are installing, configuring, or auditing the openshell plugin
title: "Openshell plugin"
@@ -7,7 +7,7 @@ title: "Openshell plugin"
# Openshell plugin
Sandbox backend powered by the NVIDIA OpenShell CLI with mirrored local workspaces and SSH-based command execution.
OpenClaw sandbox backend for the NVIDIA OpenShell CLI with mirrored local workspaces and SSH command execution.
## Distribution

View File

@@ -1,5 +1,5 @@
---
summary: "Adds PixVerse video generation provider support to OpenClaw."
summary: "OpenClaw PixVerse video generation provider plugin."
read_when:
- You are installing, configuring, or auditing the pixverse plugin
title: "PixVerse plugin"
@@ -7,7 +7,7 @@ title: "PixVerse plugin"
# PixVerse plugin
Adds PixVerse video generation provider support to OpenClaw.
OpenClaw PixVerse video generation provider plugin.
## Distribution

View File

@@ -1,5 +1,5 @@
---
summary: "Adds the QQ Bot channel surface for sending and receiving OpenClaw messages."
summary: "OpenClaw QQ Bot channel plugin for group and direct-message workflows."
read_when:
- You are installing, configuring, or auditing the qqbot plugin
title: "QQ Bot plugin"
@@ -7,7 +7,7 @@ title: "QQ Bot plugin"
# QQ Bot plugin
Adds the QQ Bot channel surface for sending and receiving OpenClaw messages.
OpenClaw QQ Bot channel plugin for group and direct-message workflows.
## Distribution

View File

@@ -1,5 +1,5 @@
---
summary: "Adds the Slack channel surface for sending and receiving OpenClaw messages."
summary: "OpenClaw Slack channel plugin for channels, DMs, commands, and app events."
read_when:
- You are installing, configuring, or auditing the slack plugin
title: "Slack plugin"
@@ -7,7 +7,7 @@ title: "Slack plugin"
# Slack plugin
Adds the Slack channel surface for sending and receiving OpenClaw messages.
OpenClaw Slack channel plugin for channels, DMs, commands, and app events.
## Distribution

View File

@@ -1,5 +1,5 @@
---
summary: "Adds the Synology Chat channel surface for sending and receiving OpenClaw messages."
summary: "Synology Chat channel plugin for OpenClaw channels and direct messages."
read_when:
- You are installing, configuring, or auditing the synology-chat plugin
title: "Synology Chat plugin"
@@ -7,7 +7,7 @@ title: "Synology Chat plugin"
# Synology Chat plugin
Adds the Synology Chat channel surface for sending and receiving OpenClaw messages.
Synology Chat channel plugin for OpenClaw channels and direct messages.
## Distribution

View File

@@ -1,5 +1,5 @@
---
summary: "Adds the Tlon channel surface for sending and receiving OpenClaw messages."
summary: "OpenClaw Tlon/Urbit channel plugin for chat workflows."
read_when:
- You are installing, configuring, or auditing the tlon plugin
title: "Tlon plugin"
@@ -7,7 +7,7 @@ title: "Tlon plugin"
# Tlon plugin
Adds the Tlon channel surface for sending and receiving OpenClaw messages.
OpenClaw Tlon/Urbit channel plugin for chat workflows.
## Distribution

View File

@@ -1,5 +1,5 @@
---
summary: "Adds the Twitch channel surface for sending and receiving OpenClaw messages."
summary: "OpenClaw Twitch channel plugin for chat and moderation workflows."
read_when:
- You are installing, configuring, or auditing the twitch plugin
title: "Twitch plugin"
@@ -7,7 +7,7 @@ title: "Twitch plugin"
# Twitch plugin
Adds the Twitch channel surface for sending and receiving OpenClaw messages.
OpenClaw Twitch channel plugin for chat and moderation workflows.
## Distribution

View File

@@ -1,5 +1,5 @@
---
summary: "Adds agent-callable tools."
summary: "OpenClaw voice-call plugin for Twilio, Telnyx, and Plivo phone calls."
read_when:
- You are installing, configuring, or auditing the voice-call plugin
title: "Voice Call plugin"
@@ -7,7 +7,7 @@ title: "Voice Call plugin"
# Voice Call plugin
Adds agent-callable tools.
OpenClaw voice-call plugin for Twilio, Telnyx, and Plivo phone calls.
## Distribution

View File

@@ -1,5 +1,5 @@
---
summary: "Adds the WhatsApp channel surface for sending and receiving OpenClaw messages."
summary: "OpenClaw WhatsApp channel plugin for WhatsApp Web chats."
read_when:
- You are installing, configuring, or auditing the whatsapp plugin
title: "WhatsApp plugin"
@@ -7,7 +7,7 @@ title: "WhatsApp plugin"
# WhatsApp plugin
Adds the WhatsApp channel surface for sending and receiving OpenClaw messages.
OpenClaw WhatsApp channel plugin for WhatsApp Web chats.
## Distribution

View File

@@ -1,5 +1,5 @@
---
summary: "Adds the Zalo channel surface for sending and receiving OpenClaw messages."
summary: "OpenClaw Zalo channel plugin for bot and webhook chats."
read_when:
- You are installing, configuring, or auditing the zalo plugin
title: "Zalo plugin"
@@ -7,7 +7,7 @@ title: "Zalo plugin"
# Zalo plugin
Adds the Zalo channel surface for sending and receiving OpenClaw messages.
OpenClaw Zalo channel plugin for bot and webhook chats.
## Distribution

View File

@@ -1,5 +1,5 @@
---
summary: "Adds the Zalo Personal channel surface for sending and receiving OpenClaw messages."
summary: "OpenClaw Zalo Personal Account plugin via native zca-js integration."
read_when:
- You are installing, configuring, or auditing the zalouser plugin
title: "Zalo Personal plugin"
@@ -7,7 +7,7 @@ title: "Zalo Personal plugin"
# Zalo Personal plugin
Adds the Zalo Personal channel surface for sending and receiving OpenClaw messages.
OpenClaw Zalo Personal Account plugin via native zca-js integration.
## Distribution

View File

@@ -524,7 +524,7 @@ two-party event loops that do not go through the shared inbound reply runner.
await store.clear();
```
Keyed stores survive restarts and are isolated by the runtime-bound plugin id. Use `registerIfAbsent(...)` for atomic dedupe claims: it returns `true` when the key was missing or expired and registered, or `false` when a live value already exists without overwriting its value, creation time, or TTL. Limits: `maxEntries` per namespace, 1,000 live rows per plugin, JSON values under 64KB, and optional TTL expiry.
Keyed stores survive restarts and are isolated by the runtime-bound plugin id. Use `registerIfAbsent(...)` for atomic dedupe claims: it returns `true` when the key was missing or expired and registered, or `false` when a live value already exists without overwriting its value, creation time, or TTL. Limits: `maxEntries` per namespace, 6,000 live rows per plugin, JSON values under 64KB, and optional TTL expiry. When a write would exceed the plugin row cap, the runtime may evict the oldest live rows from the namespace being written; sibling namespaces are not evicted for that write, and the write still fails if the namespace cannot free enough rows.
<Warning>
Bundled plugins only in this release.

View File

@@ -19,7 +19,7 @@ OpenClaw assembles its own system prompt on every run. It includes:
with optional per-agent override at
`agents.list[].skillsLimits.maxSkillsPromptChars`.
- Self-update instructions
- Workspace + bootstrap files (`AGENTS.md`, `SOUL.md`, `TOOLS.md`, `IDENTITY.md`, `USER.md`, `HEARTBEAT.md`, `BOOTSTRAP.md` when new, plus `MEMORY.md` when present). Lowercase root `memory.md` is not injected; it is legacy repair input for `openclaw doctor --fix` when paired with `MEMORY.md`. Large files are truncated by `agents.defaults.bootstrapMaxChars` (default: 12000), and total bootstrap injection is capped by `agents.defaults.bootstrapTotalMaxChars` (default: 60000). `memory/*.md` daily files are not part of the normal bootstrap prompt; they remain on-demand via memory tools on ordinary turns, but reset/startup model runs can prepend a one-shot startup-context block with recent daily memory for that first turn. Bare chat `/new` and `/reset` commands are acknowledged without invoking the model. The startup prelude is controlled by `agents.defaults.startupContext`. Post-compaction AGENTS.md excerpts are separate and require explicit `agents.defaults.compaction.postCompactionSections` opt-in.
- Workspace + bootstrap files (`AGENTS.md`, `SOUL.md`, `TOOLS.md`, `IDENTITY.md`, `USER.md`, `HEARTBEAT.md`, `BOOTSTRAP.md` when new, plus `MEMORY.md` when present). Native Codex turns do not paste raw `MEMORY.md` from the configured agent workspace when memory tools are available for that workspace; they include a small memory pointer and use memory tools on demand. If tools are disabled, memory search is unavailable, or the active workspace differs from the agent memory workspace, `MEMORY.md` uses the normal bounded turn-context path. Lowercase root `memory.md` is not injected; it is legacy repair input for `openclaw doctor --fix` when paired with `MEMORY.md`. Large injected files are truncated by `agents.defaults.bootstrapMaxChars` (default: 12000), and total bootstrap injection is capped by `agents.defaults.bootstrapTotalMaxChars` (default: 60000). `memory/*.md` daily files are not part of the normal bootstrap prompt; they remain on-demand via memory tools on ordinary turns, but reset/startup model runs can prepend a one-shot startup-context block with recent daily memory for that first turn. Bare chat `/new` and `/reset` commands are acknowledged without invoking the model. The startup prelude is controlled by `agents.defaults.startupContext`. Post-compaction AGENTS.md excerpts are separate and require explicit `agents.defaults.compaction.postCompactionSections` opt-in.
- Time (UTC + user timezone)
- Reply tags + heartbeat behavior
- Runtime metadata (host/OS/model/thinking)

View File

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

View File

@@ -5,7 +5,7 @@
},
"enabledByDefault": true,
"name": "ACPX Runtime",
"description": "Embedded ACP runtime backend with plugin-owned session and transport management.",
"description": "OpenClaw ACP runtime backend with plugin-owned session and transport management.",
"skills": ["./skills"],
"configSchema": {
"type": "object",

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/alibaba-provider",
"version": "2026.5.26",
"version": "2026.5.27",
"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.5.26",
"version": "2026.5.27",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@openclaw/amazon-bedrock-mantle-provider",
"version": "2026.5.26",
"version": "2026.5.27",
"dependencies": {
"@anthropic-ai/sdk": "0.98.0",
"@aws/bedrock-token-generator": "1.1.0",

View File

@@ -1,5 +1,7 @@
{
"id": "amazon-bedrock-mantle",
"name": "Amazon Bedrock Mantle",
"description": "OpenClaw Amazon Bedrock Mantle provider plugin for OpenAI-compatible model routing.",
"activation": {
"onStartup": false
},

View File

@@ -1,7 +1,7 @@
{
"name": "@openclaw/amazon-bedrock-mantle-provider",
"version": "2026.5.26",
"description": "OpenClaw Amazon Bedrock Mantle (OpenAI-compatible) provider plugin",
"version": "2026.5.27",
"description": "OpenClaw Amazon Bedrock Mantle provider plugin for OpenAI-compatible model routing.",
"repository": {
"type": "git",
"url": "https://github.com/openclaw/openclaw"
@@ -25,10 +25,10 @@
"minHostVersion": ">=2026.5.12-beta.1"
},
"compat": {
"pluginApi": ">=2026.5.26"
"pluginApi": ">=2026.5.27"
},
"build": {
"openclawVersion": "2026.5.26",
"openclawVersion": "2026.5.27",
"bundledDist": false
},
"release": {

View File

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

View File

@@ -1,5 +1,7 @@
{
"id": "amazon-bedrock",
"name": "Amazon Bedrock",
"description": "OpenClaw Amazon Bedrock provider plugin with model discovery, embeddings, and guardrail support.",
"activation": {
"onStartup": false
},

View File

@@ -1,7 +1,7 @@
{
"name": "@openclaw/amazon-bedrock-provider",
"version": "2026.5.26",
"description": "OpenClaw Amazon Bedrock provider plugin",
"version": "2026.5.27",
"description": "OpenClaw Amazon Bedrock provider plugin with model discovery, embeddings, and guardrail support.",
"repository": {
"type": "git",
"url": "https://github.com/openclaw/openclaw"
@@ -27,10 +27,10 @@
"minHostVersion": ">=2026.5.12-beta.1"
},
"compat": {
"pluginApi": ">=2026.5.26"
"pluginApi": ">=2026.5.27"
},
"build": {
"openclawVersion": "2026.5.26",
"openclawVersion": "2026.5.27",
"bundledDist": false
},
"release": {

View File

@@ -1,12 +1,12 @@
{
"name": "@openclaw/anthropic-vertex-provider",
"version": "2026.5.26",
"version": "2026.5.27",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@openclaw/anthropic-vertex-provider",
"version": "2026.5.26",
"version": "2026.5.27",
"dependencies": {
"@anthropic-ai/vertex-sdk": "0.16.1",
"@earendil-works/pi-agent-core": "0.75.5",

View File

@@ -1,5 +1,7 @@
{
"id": "anthropic-vertex",
"name": "Anthropic Vertex",
"description": "OpenClaw Anthropic Vertex provider plugin for Claude models on Google Vertex AI.",
"activation": {
"onStartup": false
},

View File

@@ -1,7 +1,7 @@
{
"name": "@openclaw/anthropic-vertex-provider",
"version": "2026.5.26",
"description": "OpenClaw Anthropic Vertex provider plugin",
"version": "2026.5.27",
"description": "OpenClaw Anthropic Vertex provider plugin for Claude models on Google Vertex AI.",
"repository": {
"type": "git",
"url": "https://github.com/openclaw/openclaw"
@@ -25,10 +25,10 @@
"minHostVersion": ">=2026.5.12-beta.1"
},
"compat": {
"pluginApi": ">=2026.5.26"
"pluginApi": ">=2026.5.27"
},
"build": {
"openclawVersion": "2026.5.26",
"openclawVersion": "2026.5.27",
"bundledDist": false
},
"release": {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,7 @@
{
"id": "brave",
"name": "Brave",
"description": "OpenClaw Brave Search provider plugin for web search.",
"activation": {
"onStartup": false
},

View File

@@ -1,7 +1,7 @@
{
"name": "@openclaw/brave-plugin",
"version": "2026.5.26",
"description": "OpenClaw Brave plugin",
"version": "2026.5.27",
"description": "OpenClaw Brave Search provider plugin for web search.",
"repository": {
"type": "git",
"url": "https://github.com/openclaw/openclaw"
@@ -21,10 +21,10 @@
"allowInvalidConfigRecovery": true
},
"compat": {
"pluginApi": ">=2026.5.26"
"pluginApi": ">=2026.5.27"
},
"build": {
"openclawVersion": "2026.5.26"
"openclawVersion": "2026.5.27"
},
"release": {
"publishToClawHub": true,

View File

@@ -0,0 +1,51 @@
import { describe, expect, it, vi } from "vitest";
import { createBraveWebSearchProvider } from "./brave-web-search-provider.js";
const runtimeMock = vi.hoisted(() => {
const searchConfigs: Array<Record<string, unknown> | undefined> = [];
return {
searchConfigs,
executeBraveSearch: vi.fn(async (_args: unknown, searchConfig?: Record<string, unknown>) => {
searchConfigs.push(searchConfig);
return { results: [] };
}),
};
});
vi.mock("./brave-web-search-provider.runtime.js", () => ({
executeBraveSearch: runtimeMock.executeBraveSearch,
}));
describe("brave web search config merge", () => {
it("keeps plugin webSearch runtime-only after merging it for the tool", async () => {
const provider = createBraveWebSearchProvider();
const tool = provider.createTool({
config: {
plugins: {
entries: {
brave: {
config: {
webSearch: {
apiKey: "brave-test-key",
mode: "llm-context",
},
},
},
},
},
},
searchConfig: { provider: "brave" },
});
await tool?.execute({ query: "OpenClaw docs" });
const [searchConfig] = runtimeMock.searchConfigs;
expect(searchConfig?.brave).toEqual({
apiKey: "brave-test-key",
mode: "llm-context",
});
expect(searchConfig?.apiKey).toBe("brave-test-key");
expect(Object.keys(searchConfig ?? {})).toEqual(["provider", "apiKey"]);
expect(Object.getOwnPropertyDescriptor(searchConfig ?? {}, "brave")?.enumerable).toBe(false);
});
});

View File

@@ -1,10 +1,15 @@
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
import { isDiagnosticFlagEnabled } from "openclaw/plugin-sdk/diagnostic-runtime";
import type {
SearchConfigRecord,
WebSearchProviderPlugin,
WebSearchProviderToolDefinition,
} from "openclaw/plugin-sdk/provider-web-search";
import { createWebSearchProviderContractFields } from "openclaw/plugin-sdk/provider-web-search-config-contract";
import {
createWebSearchProviderContractFields,
mergeScopedSearchConfig,
resolveProviderWebSearchPluginConfig,
} from "openclaw/plugin-sdk/provider-web-search-config-contract";
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
const BRAVE_CREDENTIAL_PATH = "plugins.entries.brave.config.webSearch.apiKey";
@@ -62,22 +67,8 @@ const BraveSearchSchema = {
},
} satisfies Record<string, unknown>;
function resolveProviderWebSearchPluginConfig(
config: unknown,
pluginId: string,
): Record<string, unknown> | undefined {
if (!isRecord(config)) {
return undefined;
}
const plugins = isRecord(config.plugins) ? config.plugins : undefined;
const entries = isRecord(plugins?.entries) ? plugins.entries : undefined;
const entry = isRecord(entries?.[pluginId]) ? entries[pluginId] : undefined;
const pluginConfig = isRecord(entry?.config) ? entry.config : undefined;
return isRecord(pluginConfig?.webSearch) ? pluginConfig.webSearch : undefined;
}
function resolveLegacyTopLevelBraveCredential(
config: unknown,
config: OpenClawConfig | undefined,
): { path: string; value: unknown } | undefined {
if (!isRecord(config)) {
return undefined;
@@ -91,39 +82,13 @@ function resolveLegacyTopLevelBraveCredential(
return { path: "tools.web.search.apiKey", value: search.apiKey };
}
function resolveConfiguredBraveCredential(config: unknown): unknown {
function resolveConfiguredBraveCredential(config: OpenClawConfig | undefined): unknown {
return (
resolveProviderWebSearchPluginConfig(config, "brave")?.apiKey ??
resolveLegacyTopLevelBraveCredential(config)?.value
);
}
function mergeScopedSearchConfig(
searchConfig: Record<string, unknown> | undefined,
key: string,
pluginConfig: Record<string, unknown> | undefined,
options?: { mirrorApiKeyToTopLevel?: boolean },
): Record<string, unknown> | undefined {
if (!pluginConfig) {
return searchConfig;
}
const currentScoped = isRecord(searchConfig?.[key]) ? searchConfig?.[key] : {};
const next: Record<string, unknown> = {
...searchConfig,
[key]: {
...currentScoped,
...pluginConfig,
},
};
if (options?.mirrorApiKeyToTopLevel && pluginConfig.apiKey !== undefined) {
next.apiKey = pluginConfig.apiKey;
}
return next;
}
function resolveBraveMode(searchConfig?: Record<string, unknown>): "web" | "llm-context" {
const brave = isRecord(searchConfig?.brave) ? searchConfig.brave : undefined;
return brave?.mode === "llm-context" ? "llm-context" : "web";

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,12 +1,12 @@
{
"name": "@openclaw/codex",
"version": "2026.5.26",
"version": "2026.5.27",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@openclaw/codex",
"version": "2026.5.26",
"version": "2026.5.27",
"dependencies": {
"@earendil-works/pi-coding-agent": "0.75.5",
"@openai/codex": "0.134.0",

View File

@@ -1,7 +1,7 @@
{
"id": "codex",
"name": "Codex",
"description": "Codex app-server harness and Codex-managed GPT model catalog.",
"description": "OpenClaw Codex app-server harness and model provider plugin with a Codex-managed GPT catalog.",
"providers": ["codex"],
"contracts": {
"mediaUnderstandingProviders": ["codex"],

View File

@@ -1,7 +1,7 @@
{
"name": "@openclaw/codex",
"version": "2026.5.26",
"description": "OpenClaw Codex harness and model provider plugin",
"version": "2026.5.27",
"description": "OpenClaw Codex app-server harness and model provider plugin with a Codex-managed GPT catalog.",
"repository": {
"type": "git",
"url": "https://github.com/openclaw/openclaw"
@@ -27,10 +27,10 @@
"minHostVersion": ">=2026.5.1-beta.1"
},
"compat": {
"pluginApi": ">=2026.5.26"
"pluginApi": ">=2026.5.27"
},
"build": {
"openclawVersion": "2026.5.26"
"openclawVersion": "2026.5.27"
},
"release": {
"publishToClawHub": true,

View File

@@ -5,6 +5,11 @@ import {
embeddedAgentLog,
wrapToolWithBeforeToolCallHook,
} from "openclaw/plugin-sdk/agent-harness-runtime";
import {
onInternalDiagnosticEvent,
waitForDiagnosticEventsDrained,
type DiagnosticEventPayload,
} from "openclaw/plugin-sdk/diagnostic-runtime";
import {
initializeGlobalHookRunner,
resetGlobalHookRunner,
@@ -293,18 +298,33 @@ describe("createCodexDynamicToolBridge", () => {
it("quarantines dynamic tools with unsupported input schemas", async () => {
const warn = vi.spyOn(embeddedAgentLog, "warn").mockImplementation(() => undefined);
const diagnosticEvents: DiagnosticEventPayload[] = [];
const unsubscribeDiagnostics = onInternalDiagnosticEvent((event) =>
diagnosticEvents.push(event),
);
const badExecute = vi.fn();
const bridge = createCodexDynamicToolBridge({
tools: [
createTool({ name: "message" }),
createTool({
name: "dofbot_move_angles",
parameters: { type: "array", items: { type: "number" } },
execute: badExecute,
}),
],
signal: new AbortController().signal,
});
let bridge!: ReturnType<typeof createCodexDynamicToolBridge>;
try {
bridge = createCodexDynamicToolBridge({
tools: [
createTool({ name: "message" }),
createTool({
name: "dofbot_move_angles",
parameters: { type: "array", items: { type: "number" } },
execute: badExecute,
}),
],
signal: new AbortController().signal,
hookContext: {
runId: "run-1",
sessionId: "session-1",
sessionKey: "agent:main:session-1",
},
});
await waitForDiagnosticEventsDrained();
} finally {
unsubscribeDiagnostics();
}
expect(bridge.availableSpecs.map((tool) => tool.name)).toEqual(["message"]);
expect(bridge.specs.map((tool) => tool.name)).toEqual(["message"]);
@@ -325,6 +345,21 @@ describe("createCodexDynamicToolBridge", () => {
],
}),
);
const blockedEvents = diagnosticEvents.filter(
(event): event is Extract<DiagnosticEventPayload, { type: "tool.execution.blocked" }> =>
event.type === "tool.execution.blocked",
);
expect(blockedEvents).toContainEqual(
expect.objectContaining({
type: "tool.execution.blocked",
runId: "run-1",
sessionId: "session-1",
sessionKey: "agent:main:session-1",
toolName: "dofbot_move_angles",
deniedReason: "unsupported_tool_schema",
reason: 'dofbot_move_angles.inputSchema.type must be "object"',
}),
);
const result = await bridge.handleToolCall({
threadId: "thread-1",

View File

@@ -21,6 +21,7 @@ import {
type MessagingToolSourceReplyPayload,
wrapToolWithBeforeToolCallHook,
} from "openclaw/plugin-sdk/agent-harness-runtime";
import { emitTrustedDiagnosticEvent } from "openclaw/plugin-sdk/diagnostic-runtime";
import { normalizeAgentId } from "openclaw/plugin-sdk/routing";
import {
asOptionalRecord as readRecord,
@@ -118,6 +119,7 @@ export function createCodexDynamicToolBridge(params: {
...registeredProjection.quarantinedTools,
]);
warnQuarantinedDynamicTools(quarantinedTools);
emitQuarantinedDynamicToolDiagnostics(quarantinedTools, params.hookContext);
const telemetry: CodexDynamicToolBridge["telemetry"] = {
didSendViaMessagingTool: false,
messagingToolSentTexts: [],
@@ -337,6 +339,23 @@ function warnQuarantinedDynamicTools(tools: readonly CodexDynamicToolSchemaQuara
);
}
function emitQuarantinedDynamicToolDiagnostics(
tools: readonly CodexDynamicToolSchemaQuarantine[],
ctx: CodexDynamicToolHookContext | undefined,
): void {
for (const tool of tools) {
emitTrustedDiagnosticEvent({
type: "tool.execution.blocked",
runId: ctx?.runId,
sessionId: ctx?.sessionId,
sessionKey: ctx?.sessionKey,
toolName: tool.tool,
deniedReason: "unsupported_tool_schema",
reason: tool.violations.join(", "),
});
}
}
function dedupeQuarantinedDynamicTools(
tools: readonly CodexDynamicToolSchemaQuarantine[],
): CodexDynamicToolSchemaQuarantine[] {

View File

@@ -47,6 +47,7 @@ import * as approvalBridge from "./approval-bridge.js";
import * as authBridge from "./auth-bridge.js";
import { resolveCodexAppServerEnvApiKeyCacheKey } from "./auth-bridge.js";
import type { CodexAppServerClientFactory } from "./client-factory.js";
import { CodexAppServerRpcError } from "./client.js";
import {
readCodexPluginConfig,
resolveCodexAppServerRuntimeOptions,
@@ -91,6 +92,7 @@ import {
} from "./sandbox-exec-server.js";
import { createSandboxContext } from "./sandbox-exec-server.test-helpers.js";
import { readCodexAppServerBinding, writeCodexAppServerBinding } from "./session-binding.js";
import * as sharedClientModule from "./shared-client.js";
import { createCodexTestModel } from "./test-support.js";
import {
buildContextEngineBinding,
@@ -581,6 +583,19 @@ function createNamedDynamicTool(
};
}
function setAgentWorkspaceForTest(params: EmbeddedRunAttemptParams, workspaceDir: string): void {
params.config = {
...params.config,
agents: {
...params.config?.agents,
defaults: {
...params.config?.agents?.defaults,
workspace: workspaceDir,
},
},
} as EmbeddedRunAttemptParams["config"];
}
async function buildDynamicToolsForTest(
params: EmbeddedRunAttemptParams,
workspaceDir: string,
@@ -842,6 +857,24 @@ type AppServerRequestHandler = (request: {
}) => Promise<unknown>;
function extractRelayIdFromThreadRequest(params: unknown): string {
const command = extractNativeHookRelayCommandFromThreadRequest(params);
const match = command.match(/--relay-id ([^ ]+)/);
if (!match?.[1]) {
throw new Error(`relay id missing from command: ${command}`);
}
return match[1];
}
function extractGenerationFromThreadRequest(params: unknown): string {
const command = extractNativeHookRelayCommandFromThreadRequest(params);
const match = command.match(/--generation ([^ ]+)/);
if (!match?.[1]) {
throw new Error(`relay generation missing from command: ${command}`);
}
return match[1];
}
function extractNativeHookRelayCommandFromThreadRequest(params: unknown): string {
const config = (params as { config?: Record<string, unknown> }).config;
let command: string | undefined;
for (const key of [
@@ -864,11 +897,10 @@ function extractRelayIdFromThreadRequest(params: unknown): string {
break;
}
}
const match = command?.match(/--relay-id ([^ ]+)/);
if (!match?.[1]) {
throw new Error(`relay id missing from command: ${command}`);
if (!command) {
throw new Error("native hook relay command missing from thread request");
}
return match[1];
return command;
}
describe("runCodexAppServerAttempt", () => {
@@ -6571,7 +6603,11 @@ describe("runCodexAppServerAttempt", () => {
expect(hookContext.sessionId).toBe("session-1");
const threadStart = harness.requests.find((request) => request.method === "thread/start");
const threadStartParams = threadStart?.params as { developerInstructions?: string } | undefined;
expect(threadStartParams?.developerInstructions).toContain("pre system\n\ncustom codex system");
const wrappedPluginSystemContext = (text: string) =>
`---\n\nOpenClaw plugin-injected system context. This block is not workspace file content.\n\n${text}\n\n---`;
expect(threadStartParams?.developerInstructions).toContain(
`${wrappedPluginSystemContext("pre system")}\n\ncustom codex system\n\n${wrappedPluginSystemContext("post system")}`,
);
const turnStart = harness.requests.find((request) => request.method === "turn/start");
const turnStartParams = turnStart?.params as
| { input?: Array<{ text?: string; text_elements?: unknown[]; type?: string }> }
@@ -6707,7 +6743,7 @@ describe("runCodexAppServerAttempt", () => {
expect(secondInputText).toContain("continue from there");
});
it("passes stable workspace files as Codex developer instructions and keeps MEMORY.md as turn context", async () => {
it("passes stable workspace files as Codex developer instructions and routes MEMORY.md through tools", async () => {
const sessionFile = path.join(tempDir, "session.jsonl");
const workspaceDir = path.join(tempDir, "workspace");
const agentsGuidance = "Follow AGENTS guidance.";
@@ -6725,9 +6761,16 @@ describe("runCodexAppServerAttempt", () => {
await fs.writeFile(path.join(workspaceDir, "USER.md"), userProfile);
await fs.writeFile(path.join(workspaceDir, "HEARTBEAT.md"), heartbeatChecklist);
await fs.writeFile(path.join(workspaceDir, "MEMORY.md"), memorySummary);
testing.setOpenClawCodingToolsFactoryForTests(() => [
createRuntimeDynamicTool("memory_search"),
createRuntimeDynamicTool("memory_get"),
]);
const harness = createStartedThreadHarness();
const params = createParams(sessionFile, workspaceDir);
params.disableTools = false;
setAgentWorkspaceForTest(params, workspaceDir);
const run = runCodexAppServerAttempt(createParams(sessionFile, workspaceDir));
const run = runCodexAppServerAttempt(params);
await harness.waitForMethod("turn/start");
await new Promise<void>((resolve) => setImmediate(resolve));
await harness.completeTurn({ threadId: "thread-1", turnId: "turn-1" });
@@ -6780,8 +6823,12 @@ describe("runCodexAppServerAttempt", () => {
expect(inputText).not.toContain(toolGuidance);
expect(inputText).not.toContain(userProfile);
expect(inputText).not.toContain(heartbeatChecklist);
expect(inputText).toContain(memorySummary);
expect(inputText).toContain("Codex loads AGENTS.md natively");
expect(inputText).not.toContain(memorySummary);
expect(inputText).toContain("OpenClaw Workspace Memory");
expect(inputText).toContain("MEMORY.md exists in the active agent workspace");
expect(inputText).toContain("memory_search");
expect(inputText).toContain("memory_get");
expect(inputText).not.toContain("Codex loads AGENTS.md natively");
expect(inputText).not.toContain(agentsGuidance);
expect(inputText).toContain("Current user request:\nhello");
expect(result.systemPromptReport?.systemPrompt.chars).toBe(
@@ -6814,7 +6861,7 @@ describe("runCodexAppServerAttempt", () => {
});
expect(fileStats.get("MEMORY.md")).toMatchObject({
rawChars: memorySummary.length,
injectedChars: memorySummary.length,
injectedChars: 0,
truncated: false,
});
expect(fileStats.get("HEARTBEAT.md")).toMatchObject({
@@ -6829,6 +6876,284 @@ describe("runCodexAppServerAttempt", () => {
});
});
it("injects bounded MEMORY.md when memory tools are unavailable", async () => {
const sessionFile = path.join(tempDir, "session.jsonl");
const workspaceDir = path.join(tempDir, "workspace");
const memorySummary = "Memory summary goes here.";
await fs.mkdir(workspaceDir, { recursive: true });
await fs.writeFile(path.join(workspaceDir, "MEMORY.md"), memorySummary);
const harness = createStartedThreadHarness();
const run = runCodexAppServerAttempt(createParams(sessionFile, workspaceDir));
await harness.waitForMethod("turn/start");
await new Promise<void>((resolve) => setImmediate(resolve));
await harness.completeTurn({ threadId: "thread-1", turnId: "turn-1" });
const result = await run;
const turnStart = harness.requests.find((request) => request.method === "turn/start");
const turnStartParams = turnStart?.params as {
input?: Array<{ text?: string }>;
};
const inputText = turnStartParams.input?.[0]?.text ?? "";
expect(inputText).not.toContain("OpenClaw Workspace Memory");
expect(inputText).not.toContain("memory_search");
expect(inputText).toContain(memorySummary);
const fileStats = new Map(
result.systemPromptReport?.injectedWorkspaceFiles.map((file) => [file.name, file]) ?? [],
);
expect(fileStats.get("MEMORY.md")).toMatchObject({
rawChars: memorySummary.length,
injectedChars: memorySummary.length,
truncated: false,
});
});
it("routes MEMORY.md through memory_get when search is unavailable", async () => {
const sessionFile = path.join(tempDir, "session.jsonl");
const workspaceDir = path.join(tempDir, "workspace");
const memorySummary = "Memory summary goes here.";
await fs.mkdir(workspaceDir, { recursive: true });
await fs.writeFile(path.join(workspaceDir, "MEMORY.md"), memorySummary);
testing.setOpenClawCodingToolsFactoryForTests(() => [createRuntimeDynamicTool("memory_get")]);
const harness = createStartedThreadHarness();
const params = createParams(sessionFile, workspaceDir);
params.disableTools = false;
setAgentWorkspaceForTest(params, workspaceDir);
const run = runCodexAppServerAttempt(params);
await harness.waitForMethod("turn/start");
await new Promise<void>((resolve) => setImmediate(resolve));
await harness.completeTurn({ threadId: "thread-1", turnId: "turn-1" });
const result = await run;
const turnStart = harness.requests.find((request) => request.method === "turn/start");
const turnStartParams = turnStart?.params as {
input?: Array<{ text?: string }>;
};
const inputText = turnStartParams.input?.[0]?.text ?? "";
expect(inputText).toContain("OpenClaw Workspace Memory");
expect(inputText).toContain("memory_get");
expect(inputText).not.toContain("memory_search");
expect(inputText).not.toContain(memorySummary);
const fileStats = new Map(
result.systemPromptReport?.injectedWorkspaceFiles.map((file) => [file.name, file]) ?? [],
);
expect(fileStats.get("MEMORY.md")).toMatchObject({
rawChars: memorySummary.length,
injectedChars: 0,
truncated: false,
});
});
it("reports MEMORY.md as truncated when no-tool fallback exceeds the bootstrap budget", async () => {
const sessionFile = path.join(tempDir, "session.jsonl");
const workspaceDir = path.join(tempDir, "workspace");
const soulGuidance = "Soul guidance ".repeat(80);
const memorySummary = "Memory summary goes here.";
await fs.mkdir(workspaceDir, { recursive: true });
await fs.writeFile(path.join(workspaceDir, "SOUL.md"), soulGuidance);
await fs.writeFile(path.join(workspaceDir, "MEMORY.md"), memorySummary);
const harness = createStartedThreadHarness();
const params = createParams(sessionFile, workspaceDir);
params.config = {
agents: {
defaults: {
bootstrapMaxChars: 1000,
bootstrapTotalMaxChars: 1000,
},
},
} as EmbeddedRunAttemptParams["config"];
const run = runCodexAppServerAttempt(params);
await harness.waitForMethod("turn/start");
await new Promise<void>((resolve) => setImmediate(resolve));
await harness.completeTurn({ threadId: "thread-1", turnId: "turn-1" });
const result = await run;
const fileStats = new Map(
result.systemPromptReport?.injectedWorkspaceFiles.map((file) => [file.name, file]) ?? [],
);
expect(fileStats.get("MEMORY.md")).toMatchObject({
rawChars: memorySummary.length,
injectedChars: 0,
truncated: true,
});
});
it("keeps MEMORY.md out of the Codex workspace context budget", async () => {
const sessionFile = path.join(tempDir, "session.jsonl");
const workspaceDir = path.join(tempDir, "workspace");
const memorySummary = "Memory summary ".repeat(300);
const hookContext = "Hook context survives the memory budget.";
const hookPath = path.join(workspaceDir, "ZZZ.md");
await fs.mkdir(workspaceDir, { recursive: true });
await fs.writeFile(path.join(workspaceDir, "MEMORY.md"), memorySummary);
registerInternalHook("agent:bootstrap", (event) => {
const context = event.context as {
bootstrapFiles: Array<{ content: string; missing: boolean; name?: string; path: string }>;
};
context.bootstrapFiles = [
...context.bootstrapFiles,
{
name: "ZZZ.md",
path: hookPath,
content: hookContext,
missing: false,
},
];
});
const harness = createStartedThreadHarness();
const params = createParams(sessionFile, workspaceDir);
params.disableTools = false;
params.config = {
agents: {
defaults: {
workspace: workspaceDir,
bootstrapMaxChars: 1000,
bootstrapTotalMaxChars: 2000,
},
},
} as EmbeddedRunAttemptParams["config"];
testing.setOpenClawCodingToolsFactoryForTests(() => [
createRuntimeDynamicTool("memory_search"),
createRuntimeDynamicTool("memory_get"),
]);
const run = runCodexAppServerAttempt(params);
await harness.waitForMethod("turn/start");
await new Promise<void>((resolve) => setImmediate(resolve));
await harness.completeTurn({ threadId: "thread-1", turnId: "turn-1" });
const result = await run;
const turnStart = harness.requests.find((request) => request.method === "turn/start");
const turnStartParams = turnStart?.params as {
input?: Array<{ text?: string }>;
};
const inputText = turnStartParams.input?.[0]?.text ?? "";
expect(inputText).toContain("OpenClaw Workspace Memory");
expect(inputText).not.toContain(memorySummary);
expect(inputText).toContain(hookContext);
const fileStats = new Map(
result.systemPromptReport?.injectedWorkspaceFiles.map((file) => [file.name, file]) ?? [],
);
expect(fileStats.get("MEMORY.md")).toMatchObject({
rawChars: memorySummary.trimEnd().length,
injectedChars: 0,
truncated: false,
});
expect(fileStats.get("ZZZ.md")).toMatchObject({
rawChars: hookContext.length,
injectedChars: hookContext.length,
truncated: false,
});
});
it("keeps extra MEMORY.md bootstrap files in Codex workspace context", async () => {
const sessionFile = path.join(tempDir, "session.jsonl");
const workspaceDir = path.join(tempDir, "workspace");
const rootMemory = "Root memory should stay tool-routed.";
const nestedMemory = "Nested package memory remains prompt context.";
const nestedMemoryPath = path.join(workspaceDir, "packages/pkg/MEMORY.md");
await fs.mkdir(path.dirname(nestedMemoryPath), { recursive: true });
await fs.writeFile(path.join(workspaceDir, "MEMORY.md"), rootMemory);
await fs.writeFile(nestedMemoryPath, nestedMemory);
registerInternalHook("agent:bootstrap", (event) => {
const context = event.context as {
bootstrapFiles: Array<{ content: string; missing: boolean; name?: string; path: string }>;
};
context.bootstrapFiles = [
...context.bootstrapFiles,
{
name: "MEMORY.md",
path: nestedMemoryPath,
content: nestedMemory,
missing: false,
},
];
});
testing.setOpenClawCodingToolsFactoryForTests(() => [
createRuntimeDynamicTool("memory_search"),
createRuntimeDynamicTool("memory_get"),
]);
const harness = createStartedThreadHarness();
const params = createParams(sessionFile, workspaceDir);
params.disableTools = false;
setAgentWorkspaceForTest(params, workspaceDir);
const run = runCodexAppServerAttempt(params);
await harness.waitForMethod("turn/start");
await new Promise<void>((resolve) => setImmediate(resolve));
await harness.completeTurn({ threadId: "thread-1", turnId: "turn-1" });
const result = await run;
const turnStart = harness.requests.find((request) => request.method === "turn/start");
const turnStartParams = turnStart?.params as {
input?: Array<{ text?: string }>;
};
const inputText = turnStartParams.input?.[0]?.text ?? "";
expect(inputText).toContain("OpenClaw Workspace Memory");
expect(inputText).not.toContain(rootMemory);
expect(inputText).toContain(nestedMemory);
const files = result.systemPromptReport?.injectedWorkspaceFiles ?? [];
const rootMemoryStats = files.find(
(file) => file.path === path.join(workspaceDir, "MEMORY.md"),
);
const nestedMemoryStats = files.find((file) => file.path === nestedMemoryPath);
expect(rootMemoryStats).toMatchObject({
rawChars: rootMemory.length,
injectedChars: 0,
truncated: false,
});
expect(nestedMemoryStats).toMatchObject({
rawChars: nestedMemory.length,
injectedChars: nestedMemory.length,
truncated: false,
});
});
it("injects MEMORY.md when active workspace is not the memory tool workspace", async () => {
const sessionFile = path.join(tempDir, "session.jsonl");
const workspaceDir = path.join(tempDir, "workspace");
const memorySummary = "Memory summary goes here.";
await fs.mkdir(workspaceDir, { recursive: true });
await fs.writeFile(path.join(workspaceDir, "MEMORY.md"), memorySummary);
testing.setOpenClawCodingToolsFactoryForTests(() => [
createRuntimeDynamicTool("memory_search"),
createRuntimeDynamicTool("memory_get"),
]);
const harness = createStartedThreadHarness();
const params = createParams(sessionFile, workspaceDir);
params.disableTools = false;
setAgentWorkspaceForTest(params, path.join(tempDir, "memory-workspace"));
const run = runCodexAppServerAttempt(params);
await harness.waitForMethod("turn/start");
await new Promise<void>((resolve) => setImmediate(resolve));
await harness.completeTurn({ threadId: "thread-1", turnId: "turn-1" });
const result = await run;
const turnStart = harness.requests.find((request) => request.method === "turn/start");
const turnStartParams = turnStart?.params as {
input?: Array<{ text?: string }>;
};
const inputText = turnStartParams.input?.[0]?.text ?? "";
expect(inputText).not.toContain("OpenClaw Workspace Memory");
expect(inputText).toContain(memorySummary);
const fileStats = new Map(
result.systemPromptReport?.injectedWorkspaceFiles.map((file) => [file.name, file]) ?? [],
);
expect(fileStats.get("MEMORY.md")).toMatchObject({
rawChars: memorySummary.length,
injectedChars: memorySummary.length,
truncated: false,
});
});
it("reports hook-supplied bootstrap files that only expose path and content", async () => {
const sessionFile = path.join(tempDir, "session.jsonl");
const workspaceDir = path.join(tempDir, "workspace");
@@ -7953,6 +8278,202 @@ describe("runCodexAppServerAttempt", () => {
).toBeUndefined();
});
it("persists and reuses Codex native hook relay generations for resumed threads", async () => {
const sessionFile = path.join(tempDir, "session.jsonl");
const workspaceDir = path.join(tempDir, "workspace");
const firstHarness = createStartedThreadHarness();
const firstRun = runCodexAppServerAttempt(createParams(sessionFile, workspaceDir), {
nativeHookRelay: {
enabled: true,
events: ["pre_tool_use"],
},
});
await firstHarness.waitForMethod("turn/start");
const firstStartRequest = firstHarness.requests.find(
(request) => request.method === "thread/start",
);
const firstRelayId = extractRelayIdFromThreadRequest(firstStartRequest?.params);
const firstGeneration = extractGenerationFromThreadRequest(firstStartRequest?.params);
await firstHarness.completeTurn({ threadId: "thread-1", turnId: "turn-1" });
await firstRun;
expect((await readCodexAppServerBinding(sessionFile))?.nativeHookRelayGeneration).toBe(
firstGeneration,
);
const secondHarness = createResumeHarness();
const secondParams = createParams(sessionFile, workspaceDir);
secondParams.runId = "run-2";
const secondRun = runCodexAppServerAttempt(secondParams, {
nativeHookRelay: {
enabled: true,
events: ["pre_tool_use"],
},
});
await secondHarness.waitForMethod("turn/start");
const resumeRequest = secondHarness.requests.find(
(request) => request.method === "thread/resume",
);
expect(extractRelayIdFromThreadRequest(resumeRequest?.params)).toBe(firstRelayId);
expect(extractGenerationFromThreadRequest(resumeRequest?.params)).toBe(firstGeneration);
await secondHarness.completeTurn({ threadId: "thread-existing", turnId: "turn-1" });
await secondRun;
testing.flushPendingCodexNativeHookRelayUnregistersForTests();
});
it("accepts a stale first hook generation when resuming a pre-generation binding", async () => {
const sessionFile = path.join(tempDir, "session.jsonl");
const workspaceDir = path.join(tempDir, "workspace");
await writeCodexAppServerBinding(sessionFile, {
threadId: "thread-existing",
cwd: workspaceDir,
model: "gpt-5.4-codex",
modelProvider: "openai",
dynamicToolsFingerprint: "[]",
});
const harness = createResumeHarness();
const run = runCodexAppServerAttempt(createParams(sessionFile, workspaceDir), {
nativeHookRelay: {
enabled: true,
events: ["pre_tool_use"],
},
});
await harness.waitForMethod("turn/start");
const resumeRequest = harness.requests.find((request) => request.method === "thread/resume");
const relayId = extractRelayIdFromThreadRequest(resumeRequest?.params);
const currentGeneration = extractGenerationFromThreadRequest(resumeRequest?.params);
expect(currentGeneration).not.toBe("legacy-generation-from-running-thread");
await expect(
invokeNativeHookRelay({
provider: "codex",
relayId,
generation: "legacy-generation-from-running-thread",
event: "pre_tool_use",
requireGeneration: true,
rawPayload: {
hook_event_name: "PreToolUse",
tool_name: "Bash",
tool_use_id: "first-tool-after-restart",
tool_input: { command: "pwd" },
},
}),
).resolves.toMatchObject({ exitCode: 0 });
await harness.completeTurn({ threadId: "thread-existing", turnId: "turn-1" });
await run;
expect((await readCodexAppServerBinding(sessionFile))?.nativeHookRelayGeneration).toBe(
currentGeneration,
);
testing.flushPendingCodexNativeHookRelayUnregistersForTests();
});
it("rotates native hook relay generations when an existing binding starts a fresh thread", async () => {
const sessionFile = path.join(tempDir, "session.jsonl");
const workspaceDir = path.join(tempDir, "workspace");
await writeCodexAppServerBinding(sessionFile, {
threadId: "thread-existing",
cwd: workspaceDir,
model: "gpt-5.4-codex",
modelProvider: "openai",
userMcpServersFingerprint: "stale-user-mcp-fingerprint",
nativeHookRelayGeneration: "generation-from-stale-thread",
});
const harness = createStartedThreadHarness();
const run = runCodexAppServerAttempt(createParams(sessionFile, workspaceDir), {
nativeHookRelay: {
enabled: true,
events: ["pre_tool_use"],
},
});
await harness.waitForMethod("turn/start");
const startRequest = harness.requests.find((request) => request.method === "thread/start");
const relayId = extractRelayIdFromThreadRequest(startRequest?.params);
const currentGeneration = extractGenerationFromThreadRequest(startRequest?.params);
expect(currentGeneration).not.toBe("generation-from-stale-thread");
await expect(
invokeNativeHookRelay({
provider: "codex",
relayId,
generation: "generation-from-stale-thread",
event: "pre_tool_use",
requireGeneration: true,
rawPayload: {
hook_event_name: "PreToolUse",
tool_name: "Bash",
tool_use_id: "stale-thread-tool",
tool_input: { command: "pwd" },
},
}),
).rejects.toThrow("native hook relay bridge stale registration");
await harness.completeTurn({ threadId: "thread-1", turnId: "turn-1" });
await run;
expect((await readCodexAppServerBinding(sessionFile))?.nativeHookRelayGeneration).toBe(
currentGeneration,
);
testing.flushPendingCodexNativeHookRelayUnregistersForTests();
});
it("rotates native hook relay generations when resume fails over to a fresh thread", async () => {
const sessionFile = path.join(tempDir, "session.jsonl");
const workspaceDir = path.join(tempDir, "workspace");
await writeCodexAppServerBinding(sessionFile, {
threadId: "thread-existing",
cwd: workspaceDir,
model: "gpt-5.4-codex",
modelProvider: "openai",
nativeHookRelayGeneration: "generation-from-failed-resume",
});
const harness = createStartedThreadHarness(async (method) => {
if (method === "thread/resume") {
throw new Error("resume failed");
}
return undefined;
});
const run = runCodexAppServerAttempt(createParams(sessionFile, workspaceDir), {
nativeHookRelay: {
enabled: true,
events: ["pre_tool_use"],
},
});
await harness.waitForMethod("turn/start");
const startRequest = harness.requests.find((request) => request.method === "thread/start");
const relayId = extractRelayIdFromThreadRequest(startRequest?.params);
const currentGeneration = extractGenerationFromThreadRequest(startRequest?.params);
expect(currentGeneration).not.toBe("generation-from-failed-resume");
await expect(
invokeNativeHookRelay({
provider: "codex",
relayId,
generation: "generation-from-failed-resume",
event: "pre_tool_use",
requireGeneration: true,
rawPayload: {
hook_event_name: "PreToolUse",
tool_name: "Bash",
tool_use_id: "failed-resume-stale-tool",
tool_input: { command: "pwd" },
},
}),
).rejects.toThrow("native hook relay bridge stale registration");
await harness.completeTurn({ threadId: "thread-1", turnId: "turn-1" });
await run;
expect((await readCodexAppServerBinding(sessionFile))?.nativeHookRelayGeneration).toBe(
currentGeneration,
);
testing.flushPendingCodexNativeHookRelayUnregistersForTests();
});
it("builds deterministic opaque Codex native hook relay ids", () => {
const relayId = testing.buildCodexNativeHookRelayId({
agentId: "dev-codex",
@@ -11959,6 +12480,136 @@ describe("runCodexAppServerAttempt", () => {
expect(request.mock.calls.map(([method]) => method)).toEqual(["thread/start", "thread/start"]);
});
it("does not retire the shared Codex client when a spawned helper run fails with a logical thread/start error", async () => {
const clearSpy = vi.spyOn(sharedClientModule, "clearSharedCodexAppServerClientIfCurrent");
clearSpy.mockClear();
let failedClient: unknown;
setCodexAppServerClientFactoryForTest(async () => {
const c = {
request: vi.fn(async (method: string) => {
if (method === "thread/start") {
throw new CodexAppServerRpcError(
{ message: "401 authentication_error: Invalid bearer token" },
"thread/start",
);
}
return {};
}),
addNotificationHandler: vi.fn(() => () => undefined),
addRequestHandler: vi.fn(() => () => undefined),
};
failedClient = c;
return c as never;
});
const params = createParams(
path.join(tempDir, "session.jsonl"),
path.join(tempDir, "workspace"),
);
params.spawnedBy = "agent:main:session-parent";
await expect(runCodexAppServerAttempt(params)).rejects.toThrow("Invalid bearer token");
const calledWithFailedClient = clearSpy.mock.calls.some(([arg]) => arg === failedClient);
expect(calledWithFailedClient).toBe(false);
clearSpy.mockRestore();
});
it("retires the shared Codex client when a spawned helper run times out during thread/start", async () => {
const clearSpy = vi.spyOn(sharedClientModule, "clearSharedCodexAppServerClientIfCurrent");
clearSpy.mockClear();
let failedClient: unknown;
setCodexAppServerClientFactoryForTest(async () => {
const c = {
request: vi.fn(async (method: string) => {
if (method === "thread/start") {
return await new Promise<never>(() => undefined);
}
return {};
}),
addNotificationHandler: vi.fn(() => () => undefined),
addRequestHandler: vi.fn(() => () => undefined),
};
failedClient = c;
return c as never;
});
const params = createParams(
path.join(tempDir, "session.jsonl"),
path.join(tempDir, "workspace"),
);
params.spawnedBy = "agent:main:session-parent";
params.timeoutMs = 1;
await expect(runCodexAppServerAttempt(params, { startupTimeoutFloorMs: 1 })).rejects.toThrow(
"codex app-server startup timed out",
);
const calledWithFailedClient = clearSpy.mock.calls.some(([arg]) => arg === failedClient);
expect(calledWithFailedClient).toBe(true);
clearSpy.mockRestore();
});
it("retires the shared Codex client when a spawned helper hits a thread/start connection close", async () => {
const clearSpy = vi.spyOn(sharedClientModule, "clearSharedCodexAppServerClientIfCurrent");
clearSpy.mockClear();
let failedClient: unknown;
setCodexAppServerClientFactoryForTest(async () => {
const c = {
request: vi.fn(async (method: string) => {
if (method === "thread/start") {
throw new Error("codex app-server client is closed");
}
return {};
}),
addNotificationHandler: vi.fn(() => () => undefined),
addRequestHandler: vi.fn(() => () => undefined),
};
failedClient = c;
return c as never;
});
const params = createParams(
path.join(tempDir, "session.jsonl"),
path.join(tempDir, "workspace"),
);
params.spawnedBy = "agent:main:session-parent";
await expect(runCodexAppServerAttempt(params)).rejects.toThrow(
"codex app-server client is closed",
);
const calledWithFailedClient = clearSpy.mock.calls.some(([arg]) => arg === failedClient);
expect(calledWithFailedClient).toBe(true);
clearSpy.mockRestore();
});
it("does not retire the shared Codex client when a top-level run fails with a logical thread/start error", async () => {
const clearSpy = vi.spyOn(sharedClientModule, "clearSharedCodexAppServerClientIfCurrent");
clearSpy.mockClear();
let failedClient: unknown;
setCodexAppServerClientFactoryForTest(async () => {
const c = {
request: vi.fn(async (method: string) => {
if (method === "thread/start") {
throw new CodexAppServerRpcError(
{ message: "401 authentication_error: Invalid bearer token" },
"thread/start",
);
}
return {};
}),
addNotificationHandler: vi.fn(() => () => undefined),
addRequestHandler: vi.fn(() => () => undefined),
};
failedClient = c;
return c as never;
});
const params = createParams(
path.join(tempDir, "session.jsonl"),
path.join(tempDir, "workspace"),
);
await expect(runCodexAppServerAttempt(params)).rejects.toThrow("Invalid bearer token");
const calledWithFailedClient = clearSpy.mock.calls.some(([arg]) => arg === failedClient);
expect(calledWithFailedClient).toBe(false);
clearSpy.mockRestore();
});
it("passes configured app-server policy, sandbox, service tier, and model on resume", async () => {
const sessionFile = path.join(tempDir, "session.jsonl");
const workspaceDir = path.join(tempDir, "workspace");

View File

@@ -34,7 +34,9 @@ import {
runAgentHarnessLlmOutputHook,
runHarnessContextEngineMaintenance,
registerNativeHookRelay,
buildBootstrapContextForFiles,
resolveBootstrapContextForRun,
resolveBootstrapFilesForRun,
setActiveEmbeddedRun,
supportsModelTools,
runAgentCleanupStep,
@@ -46,7 +48,11 @@ import {
type NativeHookRelayEvent,
type NativeHookRelayRegistrationHandle,
} from "openclaw/plugin-sdk/agent-harness-runtime";
import { markAuthProfileBlockedUntil, resolveAgentDir } from "openclaw/plugin-sdk/agent-runtime";
import {
markAuthProfileBlockedUntil,
resolveAgentDir,
resolveAgentWorkspaceDir,
} from "openclaw/plugin-sdk/agent-runtime";
import {
createDiagnosticTraceContextFromActiveScope,
emitTrustedDiagnosticEvent,
@@ -250,6 +256,7 @@ const CODEX_WORKSPACE_DEVELOPER_CONTEXT_BASENAMES = new Set([
...CODEX_TURN_SCOPED_WORKSPACE_DEVELOPER_CONTEXT_BASENAMES,
]);
const CODEX_HEARTBEAT_CONTEXT_BASENAME = "heartbeat.md";
const CODEX_MEMORY_TOOL_NAMES = new Set(["memory_search", "memory_get"]);
const CODEX_NATIVE_HOOK_RELAY_EVENTS_WITH_APP_SERVER_APPROVALS =
CODEX_NATIVE_HOOK_RELAY_EVENTS.filter((event) => event !== "permission_request");
const CODEX_BOOTSTRAP_CONTEXT_ORDER = new Map<string, number>([
@@ -277,6 +284,10 @@ type CodexWorkspaceBootstrapContext = CodexBootstrapContext & {
developerInstructionFiles?: EmbeddedContextFile[];
turnScopedDeveloperInstructionFiles?: EmbeddedContextFile[];
heartbeatReferenceFiles?: EmbeddedContextFile[];
memoryReferenceFiles?: EmbeddedContextFile[];
memoryToolRoutedBootstrapFiles?: CodexBootstrapFile[];
memoryToolNames?: string[];
memoryToolRouted?: boolean;
promptContext?: string;
developerInstructions?: string;
turnScopedDeveloperInstructions?: string;
@@ -1334,12 +1345,14 @@ export async function runCodexAppServerAttempt(
historyMessages =
(await readMirroredSessionHistoryMessages(activeSessionFile)) ?? historyMessages;
}
const memoryToolNames = getCodexWorkspaceMemoryToolNames(toolBridge.availableSpecs);
const workspaceBootstrapContext = await buildCodexWorkspaceBootstrapContext({
params,
resolvedWorkspace,
effectiveWorkspace,
sessionKey: contextSessionKey,
sessionAgentId,
memoryToolNames,
});
const baseDeveloperInstructions = joinPresentSections(
buildDeveloperInstructions(params, {
@@ -1351,6 +1364,10 @@ export async function runCodexAppServerAttempt(
params,
skillsPrompt: params.skillsSnapshot?.prompt,
workspacePromptContext: workspaceBootstrapContext.promptContext,
workspaceMemoryReference: renderCodexWorkspaceMemoryReference({
files: workspaceBootstrapContext.memoryReferenceFiles ?? [],
toolNames: workspaceBootstrapContext.memoryToolNames,
}),
});
let promptText = params.prompt;
let developerInstructions = baseDeveloperInstructions;
@@ -1516,13 +1533,18 @@ export async function runCodexAppServerAttempt(
timeoutMs: params.timeoutMs,
timeoutFloorMs: options.startupTimeoutFloorMs,
});
try {
emitCodexAppServerEvent(params, {
stream: "codex_app_server.lifecycle",
data: { phase: "startup" },
});
const buildNativeHookRelayFinalConfigPatch = (
decision: { action: "resume"; binding: CodexAppServerThreadBinding } | { action: "start" },
) => {
nativeHookRelay?.unregister();
nativeHookRelay = createCodexNativeHookRelay({
options: options.nativeHookRelay,
generation:
decision.action === "resume" ? decision.binding.nativeHookRelayGeneration : undefined,
generationMismatchGraceMs:
decision.action === "resume" && !decision.binding.nativeHookRelayGeneration
? CODEX_NATIVE_HOOK_RELAY_TTL_GRACE_MS
: undefined,
events: nativeHookRelayEvents,
agentId: sessionAgentId,
sessionId: params.sessionId,
@@ -1535,15 +1557,24 @@ export async function runCodexAppServerAttempt(
turnStartTimeoutMs: params.timeoutMs,
signal: runAbortController.signal,
});
const nativeHookRelayConfig = nativeHookRelay
? buildCodexNativeHookRelayConfig({
relay: nativeHookRelay,
events: nativeHookRelayEvents,
hookTimeoutSec: options.nativeHookRelay?.hookTimeoutSec,
})
: options.nativeHookRelay?.enabled === false
? buildCodexNativeHookRelayDisabledConfig()
: undefined;
return {
configPatch: nativeHookRelay
? buildCodexNativeHookRelayConfig({
relay: nativeHookRelay,
events: nativeHookRelayEvents,
hookTimeoutSec: options.nativeHookRelay?.hookTimeoutSec,
})
: options.nativeHookRelay?.enabled === false
? buildCodexNativeHookRelayDisabledConfig()
: undefined,
nativeHookRelayGeneration: nativeHookRelay?.generation,
};
};
try {
emitCodexAppServerEvent(params, {
stream: "codex_app_server.lifecycle",
data: { phase: "startup" },
});
const threadConfig = mergeCodexThreadConfigs(
bundleMcpThreadConfig?.configPatch as JsonObject | undefined,
);
@@ -1696,7 +1727,7 @@ export async function runCodexAppServerAttempt(
appServer: pluginAppServer,
developerInstructions: promptBuild.developerInstructions,
config: threadConfig,
finalConfigPatch: nativeHookRelayConfig,
buildFinalConfigPatch: buildNativeHookRelayFinalConfigPatch,
nativeCodeModeEnabled: nativeToolSurfaceEnabled,
nativeCodeModeOnlyEnabled: appServer.codeModeOnly,
userMcpServersEnabled: nativeToolSurfaceEnabled,
@@ -1819,7 +1850,9 @@ export async function runCodexAppServerAttempt(
} catch (error) {
nativeHookRelay?.unregister();
await releaseSandboxExecEnvironment();
clearSharedCodexAppServerClientIfCurrent(startupClientForCleanup);
if (runAbortController.signal.aborted || shouldClearSharedClientAfterStartupRace(error)) {
clearSharedCodexAppServerClientIfCurrent(startupClientForCleanup);
}
params.abortSignal?.removeEventListener("abort", abortFromUpstream);
throw error;
}
@@ -3945,6 +3978,8 @@ function createCodexNativeHookRelay(params: {
gatewayTimeoutMs?: number;
}
| undefined;
generation?: string;
generationMismatchGraceMs?: number;
events: readonly NativeHookRelayEvent[];
agentId: string | undefined;
sessionId: string;
@@ -3967,6 +4002,10 @@ function createCodexNativeHookRelay(params: {
sessionId: params.sessionId,
sessionKey: params.sessionKey,
}),
...(params.generation ? { generation: params.generation } : {}),
...(params.generationMismatchGraceMs
? { generationMismatchGraceMs: params.generationMismatchGraceMs }
: {}),
...(params.agentId ? { agentId: params.agentId } : {}),
sessionId: params.sessionId,
...(params.sessionKey ? { sessionKey: params.sessionKey } : {}),
@@ -5573,9 +5612,17 @@ async function buildCodexWorkspaceBootstrapContext(params: {
effectiveWorkspace: string;
sessionKey: string;
sessionAgentId: string;
memoryToolNames: readonly string[];
}): Promise<CodexWorkspaceBootstrapContext> {
try {
const bootstrapContext = await resolveBootstrapContextForRun({
const memoryToolsAvailable =
params.memoryToolNames.length > 0 &&
canRouteCodexWorkspaceMemoryThroughTools({
config: params.params.config,
agentId: params.params.agentId ?? params.sessionAgentId,
workspaceDir: params.effectiveWorkspace,
});
const bootstrapFiles = await resolveBootstrapFilesForRun({
workspaceDir: params.resolvedWorkspace,
config: params.params.config,
sessionKey: params.sessionKey,
@@ -5585,14 +5632,45 @@ async function buildCodexWorkspaceBootstrapContext(params: {
contextMode: params.params.bootstrapContextMode,
runKind: params.params.bootstrapContextRunKind,
});
const contextFiles = bootstrapContext.contextFiles.map((file) =>
const memoryToolRoutedBootstrapFiles = memoryToolsAvailable
? selectCodexWorkspaceMemoryReferenceFiles({
bootstrapFiles,
workspaceDir: params.resolvedWorkspace,
})
: [];
const memoryReferenceFiles = memoryToolRoutedBootstrapFiles.map((file) =>
remapCodexContextFilePath({
file: toCodexEmbeddedContextFile(file),
sourceWorkspaceDir: params.resolvedWorkspace,
targetWorkspaceDir: params.effectiveWorkspace,
}),
);
const contextFiles = buildBootstrapContextForFiles(
memoryToolsAvailable
? bootstrapFiles.filter(
(file) =>
!isCodexWorkspaceRootMemoryBootstrapFile({
file,
workspaceDir: params.resolvedWorkspace,
}),
)
: bootstrapFiles,
{
config: params.params.config,
agentId: params.params.agentId ?? params.sessionAgentId,
warn: (message) => embeddedAgentLog.warn(message),
},
).map((file) =>
remapCodexContextFilePath({
file,
sourceWorkspaceDir: params.resolvedWorkspace,
targetWorkspaceDir: params.effectiveWorkspace,
}),
);
const promptContextFiles = selectCodexWorkspacePromptContextFiles(contextFiles);
const promptContextFiles = selectCodexWorkspacePromptContextFiles(contextFiles, {
excludeMemory: memoryToolsAvailable,
memoryWorkspaceDir: params.effectiveWorkspace,
});
const developerInstructionFiles = shouldInjectCodexOpenClawPromptContext(params.params)
? selectCodexWorkspaceInheritedDeveloperInstructionFiles(contextFiles)
: [];
@@ -5603,12 +5681,16 @@ async function buildCodexWorkspaceBootstrapContext(params: {
: [];
const heartbeatReferenceFiles = selectCodexWorkspaceHeartbeatReferenceFiles(contextFiles);
return {
...bootstrapContext,
bootstrapFiles,
contextFiles,
promptContextFiles,
developerInstructionFiles,
turnScopedDeveloperInstructionFiles,
heartbeatReferenceFiles,
memoryReferenceFiles,
memoryToolRoutedBootstrapFiles,
memoryToolNames: [...params.memoryToolNames],
memoryToolRouted: memoryToolsAvailable,
promptContext: renderCodexWorkspaceBootstrapPromptContext(promptContextFiles),
developerInstructions:
renderCodexWorkspaceThreadDeveloperInstructions(developerInstructionFiles),
@@ -5665,6 +5747,9 @@ function buildCodexSystemPromptReport(params: {
...(params.workspaceBootstrapContext.developerInstructionFiles ?? []),
...(params.workspaceBootstrapContext.turnScopedDeveloperInstructionFiles ?? []),
],
memoryToolRoutedBootstrapFiles:
params.workspaceBootstrapContext.memoryToolRoutedBootstrapFiles ?? [],
memoryToolRouted: params.workspaceBootstrapContext.memoryToolRouted === true,
}),
skills: {
promptChars: skillsPrompt.length,
@@ -5782,11 +5867,19 @@ function buildCodexBootstrapInjectionStats(params: {
bootstrapFiles: CodexBootstrapFile[];
injectedFiles: EmbeddedContextFile[];
developerInstructionFiles?: EmbeddedContextFile[];
memoryToolRoutedBootstrapFiles?: CodexBootstrapFile[];
memoryToolRouted?: boolean;
}): CodexSystemPromptReport["injectedWorkspaceFiles"] {
const injectedIndex = indexCodexContextFileContent(params.injectedFiles);
const developerInstructionIndex = indexCodexContextFileContent(
params.developerInstructionFiles ?? [],
);
const memoryToolRoutedIndex = new Set(
(params.memoryToolRoutedBootstrapFiles ?? []).map((file) => {
const fileName = readNonEmptyString(file.name);
return readNonEmptyString(file.path) ?? fileName ?? "";
}),
);
return params.bootstrapFiles.map((file) => {
const fileName = readNonEmptyString(file.name);
const pathValue = readNonEmptyString(file.path) ?? fileName ?? "";
@@ -5798,6 +5891,10 @@ function buildCodexBootstrapInjectionStats(params: {
readCodexIndexedContextFileContent(developerInstructionIndex, pathValue, fileName);
let injectedChars = injected?.length ?? 0;
let truncated = !file.missing && injectedChars < rawChars;
if (params.memoryToolRouted === true && memoryToolRoutedIndex.has(pathValue)) {
injectedChars = 0;
truncated = false;
}
if (injected === undefined) {
if (CODEX_NATIVE_PROJECT_DOC_BASENAMES.has(baseName)) {
injectedChars = rawChars;
@@ -5894,6 +5991,7 @@ function buildCodexOpenClawPromptContext(params: {
params: EmbeddedRunAttemptParams;
skillsPrompt?: string;
workspacePromptContext?: string;
workspaceMemoryReference?: string;
}): string | undefined {
if (!shouldInjectCodexOpenClawPromptContext(params.params)) {
return undefined;
@@ -5905,6 +6003,7 @@ function buildCodexOpenClawPromptContext(params: {
params.workspacePromptContext?.trim()
? ["## OpenClaw Workspace Context", "", params.workspacePromptContext.trim()].join("\n")
: undefined,
params.workspaceMemoryReference?.trim(),
].filter(isNonEmptyString);
if (sections.length === 0) {
return undefined;
@@ -5968,6 +6067,7 @@ function renderCodexWorkspaceBootstrapPromptContext(
function selectCodexWorkspacePromptContextFiles(
contextFiles: EmbeddedContextFile[],
options: { excludeMemory?: boolean; memoryWorkspaceDir?: string } = {},
): EmbeddedContextFile[] {
return contextFiles
.filter((file) => {
@@ -5977,6 +6077,13 @@ function selectCodexWorkspacePromptContextFiles(
!CODEX_NATIVE_PROJECT_DOC_BASENAMES.has(baseName) &&
!CODEX_WORKSPACE_DEVELOPER_CONTEXT_BASENAMES.has(baseName) &&
baseName !== CODEX_HEARTBEAT_CONTEXT_BASENAME &&
!(
options.excludeMemory === true &&
isCodexWorkspaceRootMemoryContextFile({
file,
workspaceDir: options.memoryWorkspaceDir,
})
) &&
!isMissingCodexBootstrapContextFile(file)
);
})
@@ -6087,10 +6194,117 @@ function renderCodexWorkspaceHeartbeatReference(files: EmbeddedContextFile[]): s
return lines.join("\n").trim();
}
function selectCodexWorkspaceMemoryReferenceFiles(params: {
bootstrapFiles: CodexBootstrapFile[];
workspaceDir: string;
}): CodexBootstrapFile[] {
return params.bootstrapFiles
.filter((file) => {
return (
isCodexWorkspaceRootMemoryBootstrapFile({
file,
workspaceDir: params.workspaceDir,
}) &&
!file.missing &&
(file.content ?? "").trim().length > 0
);
})
.toSorted(compareCodexBootstrapFiles);
}
function renderCodexWorkspaceMemoryReference(params: {
files: EmbeddedContextFile[];
toolNames?: readonly string[];
}): string | undefined {
if (params.files.length === 0) {
return undefined;
}
const toolNames = params.toolNames?.length
? params.toolNames
: Array.from(CODEX_MEMORY_TOOL_NAMES);
const lines = [
"## OpenClaw Workspace Memory",
"",
`MEMORY.md exists in the active agent workspace. OpenClaw does not paste its contents into native Codex turns; use ${toolNames.join(" or ")} when durable memory is relevant and the tools are available.`,
"",
];
for (const file of params.files) {
lines.push(`- ${file.path}`);
}
return lines.join("\n").trim();
}
function getCodexWorkspaceMemoryToolNames(tools: readonly { name: string }[]): string[] {
const availableToolNames = new Set(tools.map((tool) => normalizeCodexDynamicToolName(tool.name)));
return Array.from(CODEX_MEMORY_TOOL_NAMES).filter((name) => availableToolNames.has(name));
}
function canRouteCodexWorkspaceMemoryThroughTools(params: {
config: EmbeddedRunAttemptParams["config"] | undefined;
agentId: string;
workspaceDir: string;
}): boolean {
if (!params.config) {
return false;
}
return isSameCodexWorkspacePath(
resolveAgentWorkspaceDir(params.config, params.agentId),
params.workspaceDir,
);
}
function isMissingCodexBootstrapContextFile(file: EmbeddedContextFile): boolean {
return file.content.trimStart().startsWith("[MISSING] Expected at:");
}
function toCodexEmbeddedContextFile(file: CodexBootstrapFile): EmbeddedContextFile {
return {
path: readNonEmptyString(file.path) ?? readNonEmptyString(file.name) ?? "",
content: file.content ?? "",
};
}
function isCodexWorkspaceRootMemoryBootstrapFile(params: {
file: CodexBootstrapFile;
workspaceDir: string;
}): boolean {
return isCodexWorkspaceRootMemoryPath({
filePath: readNonEmptyString(params.file.path) ?? readNonEmptyString(params.file.name) ?? "",
workspaceDir: params.workspaceDir,
});
}
function isCodexWorkspaceRootMemoryContextFile(params: {
file: EmbeddedContextFile;
workspaceDir?: string;
}): boolean {
if (!params.workspaceDir) {
return false;
}
return isCodexWorkspaceRootMemoryPath({
filePath: params.file.path,
workspaceDir: params.workspaceDir,
});
}
function isCodexWorkspaceRootMemoryPath(params: {
filePath: string;
workspaceDir: string;
}): boolean {
const filePath = params.filePath.trim();
if (!filePath) {
return false;
}
const absolutePath = path.isAbsolute(filePath)
? path.resolve(filePath)
: path.resolve(params.workspaceDir, filePath);
return absolutePath === path.join(path.resolve(params.workspaceDir), "MEMORY.md");
}
function isSameCodexWorkspacePath(left: string, right: string): boolean {
return path.resolve(left) === path.resolve(right);
}
function remapCodexContextFilePath(params: {
file: EmbeddedContextFile;
sourceWorkspaceDir: string;
@@ -6135,6 +6349,13 @@ function compareCodexContextFiles(left: EmbeddedContextFile, right: EmbeddedCont
return leftPath.localeCompare(rightPath);
}
function compareCodexBootstrapFiles(left: CodexBootstrapFile, right: CodexBootstrapFile): number {
return compareCodexContextFiles(
toCodexEmbeddedContextFile(left),
toCodexEmbeddedContextFile(right),
);
}
function normalizeCodexContextFilePath(filePath: string): string {
return filePath.trim().replaceAll("\\", "/").toLowerCase();
}
@@ -6339,6 +6560,15 @@ function waitForCodexNotificationDispatchTurn(): Promise<void> {
});
}
function shouldClearSharedClientAfterStartupRace(error: unknown): boolean {
return (
error instanceof Error &&
(error.message === "codex app-server startup timed out" ||
error.message === "codex app-server startup aborted" ||
error.message.endsWith(" timed out"))
);
}
function handleApprovalRequest(params: {
method: string;
params: JsonValue | undefined;

View File

@@ -60,6 +60,7 @@ describe("codex app-server session binding", () => {
modelProvider: "openai",
dynamicToolsFingerprint: "tools-v1",
userMcpServersFingerprint: "user-mcp-v1",
nativeHookRelayGeneration: "generation-v1",
});
const binding = await readCodexAppServerBinding(sessionFile);
@@ -72,6 +73,7 @@ describe("codex app-server session binding", () => {
expect(binding?.modelProvider).toBe("openai");
expect(binding?.dynamicToolsFingerprint).toBe("tools-v1");
expect(binding?.userMcpServersFingerprint).toBe("user-mcp-v1");
expect(binding?.nativeHookRelayGeneration).toBe("generation-v1");
const bindingStat = await fs.stat(resolveCodexAppServerBindingPath(sessionFile));
expect(bindingStat.isFile()).toBe(true);
});

View File

@@ -42,6 +42,7 @@ export type CodexAppServerThreadBinding = {
dynamicToolsFingerprint?: string;
userMcpServersFingerprint?: string;
mcpServersFingerprint?: string;
nativeHookRelayGeneration?: string;
pluginAppsFingerprint?: string;
pluginAppsInputFingerprint?: string;
pluginAppPolicyContext?: PluginAppPolicyContext;
@@ -116,6 +117,11 @@ export async function readCodexAppServerBinding(
: undefined,
mcpServersFingerprint:
typeof parsed.mcpServersFingerprint === "string" ? parsed.mcpServersFingerprint : undefined,
nativeHookRelayGeneration:
typeof parsed.nativeHookRelayGeneration === "string" &&
parsed.nativeHookRelayGeneration.trim()
? parsed.nativeHookRelayGeneration
: undefined,
pluginAppsFingerprint:
typeof parsed.pluginAppsFingerprint === "string" ? parsed.pluginAppsFingerprint : undefined,
pluginAppsInputFingerprint:
@@ -166,6 +172,7 @@ export async function writeCodexAppServerBinding(
dynamicToolsFingerprint: binding.dynamicToolsFingerprint,
userMcpServersFingerprint: binding.userMcpServersFingerprint,
mcpServersFingerprint: binding.mcpServersFingerprint,
nativeHookRelayGeneration: binding.nativeHookRelayGeneration,
pluginAppsFingerprint: binding.pluginAppsFingerprint,
pluginAppsInputFingerprint: binding.pluginAppsInputFingerprint,
pluginAppPolicyContext: binding.pluginAppPolicyContext,

View File

@@ -1,5 +1,6 @@
import {
embeddedAgentLog,
formatErrorMessage,
isActiveHarnessContextEngine,
type EmbeddedRunAttemptParams,
} from "openclaw/plugin-sdk/agent-harness-runtime";
@@ -7,7 +8,11 @@ import { buildCodexUserMcpServersThreadConfigPatch } from "openclaw/plugin-sdk/c
import { listRegisteredPluginAgentPromptGuidance } from "openclaw/plugin-sdk/plugin-runtime";
import { CODEX_GPT5_HEARTBEAT_PROMPT_OVERLAY } from "../../prompt-overlay.js";
import { isModernCodexModel } from "../../provider.js";
import { isCodexAppServerConnectionClosedError, type CodexAppServerClient } from "./client.js";
import {
CodexAppServerRpcError,
isCodexAppServerConnectionClosedError,
type CodexAppServerClient,
} from "./client.js";
import { codexSandboxPolicyForTurn, type CodexAppServerRuntimeOptions } from "./config.js";
import {
resolveCodexContextEngineProjectionMaxChars,
@@ -56,6 +61,26 @@ export type CodexAppServerThreadLifecycleBinding = CodexAppServerThreadBinding &
lifecycle: CodexAppServerThreadLifecycle;
};
class CodexThreadStartRequestError extends Error {
constructor(cause: unknown) {
super(formatErrorMessage(cause), { cause });
this.name = "CodexThreadStartRequestError";
}
}
export function isCodexThreadStartRequestError(error: unknown): boolean {
return error instanceof CodexThreadStartRequestError;
}
export type CodexThreadFinalConfigPatchDecision =
| { action: "resume"; binding: CodexAppServerThreadBinding }
| { action: "start" };
export type CodexThreadFinalConfigPatchResult = {
configPatch?: JsonObject;
nativeHookRelayGeneration?: string;
};
export type CodexContextEngineThreadBootstrapProjection = {
mode: "thread_bootstrap";
epoch: string;
@@ -202,6 +227,10 @@ export async function startOrResumeThread(params: {
developerInstructions?: string;
config?: JsonObject;
finalConfigPatch?: JsonObject;
buildFinalConfigPatch?: (
decision: CodexThreadFinalConfigPatchDecision,
) => CodexThreadFinalConfigPatchResult;
nativeHookRelayGeneration?: string;
nativeCodeModeEnabled?: boolean;
nativeCodeModeOnlyEnabled?: boolean;
userMcpServersEnabled?: boolean;
@@ -387,10 +416,17 @@ export async function startOrResumeThread(params: {
} else {
try {
const authProfileId = params.params.authProfileId ?? binding.authProfileId;
const finalConfigPatch = params.buildFinalConfigPatch?.({
action: "resume",
binding,
}) ?? {
configPatch: params.finalConfigPatch,
nativeHookRelayGeneration: params.nativeHookRelayGeneration,
};
const resumeConfig = mergeCodexThreadConfigs(
params.config,
userMcpServersConfigPatch,
params.finalConfigPatch,
finalConfigPatch.configPatch,
);
const resumeParams = lifecycleTiming.measureSync("thread_resume_params", () =>
buildThreadResumeParams(params.params, {
@@ -433,6 +469,8 @@ export async function startOrResumeThread(params: {
dynamicToolsFingerprint,
userMcpServersFingerprint,
mcpServersFingerprint: nextMcpServersFingerprint,
nativeHookRelayGeneration:
finalConfigPatch.nativeHookRelayGeneration ?? binding.nativeHookRelayGeneration,
pluginAppsFingerprint: binding.pluginAppsFingerprint,
pluginAppsInputFingerprint: binding.pluginAppsInputFingerprint,
pluginAppPolicyContext: binding.pluginAppPolicyContext,
@@ -475,6 +513,8 @@ export async function startOrResumeThread(params: {
dynamicToolsFingerprint,
userMcpServersFingerprint,
mcpServersFingerprint: nextMcpServersFingerprint,
nativeHookRelayGeneration:
finalConfigPatch.nativeHookRelayGeneration ?? binding.nativeHookRelayGeneration,
pluginAppsFingerprint: binding.pluginAppsFingerprint,
pluginAppsInputFingerprint: binding.pluginAppsInputFingerprint,
pluginAppPolicyContext: binding.pluginAppPolicyContext,
@@ -500,12 +540,16 @@ export async function startOrResumeThread(params: {
params.pluginThreadConfig?.build(),
)))
: undefined;
const finalConfigPatch = params.buildFinalConfigPatch?.({ action: "start" }) ?? {
configPatch: params.finalConfigPatch,
nativeHookRelayGeneration: params.nativeHookRelayGeneration,
};
const config = lifecycleTiming.measureSync("merge_thread_config", () =>
mergeCodexThreadConfigs(
params.config,
userMcpServersConfigPatch,
pluginThreadConfig?.configPatch,
params.finalConfigPatch,
finalConfigPatch.configPatch,
),
);
const startParams = lifecycleTiming.measureSync("thread_start_params", () =>
@@ -520,11 +564,17 @@ export async function startOrResumeThread(params: {
environmentSelection: params.environmentSelection,
}),
);
const response = assertCodexThreadStartResponse(
await lifecycleTiming.measure("thread_start_request", () =>
params.client.request("thread/start", startParams),
),
);
const threadStartResponse = await lifecycleTiming.measure("thread_start_request", async () => {
try {
return await params.client.request("thread/start", startParams);
} catch (error) {
if (error instanceof CodexAppServerRpcError) {
throw new CodexThreadStartRequestError(error);
}
throw error;
}
});
const response = assertCodexThreadStartResponse(threadStartResponse);
const modelProvider = resolveCodexAppServerModelProvider({
provider: params.params.provider,
authProfileId: params.params.authProfileId,
@@ -548,6 +598,7 @@ export async function startOrResumeThread(params: {
dynamicToolsFingerprint,
userMcpServersFingerprint,
mcpServersFingerprint: nextMcpServersFingerprint,
nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration,
pluginAppsFingerprint: pluginThreadConfig?.fingerprint,
pluginAppsInputFingerprint: pluginThreadConfig?.inputFingerprint,
pluginAppPolicyContext: pluginThreadConfig?.policyContext,
@@ -592,6 +643,7 @@ export async function startOrResumeThread(params: {
dynamicToolsFingerprint,
userMcpServersFingerprint,
mcpServersFingerprint: nextMcpServersFingerprint,
nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration,
pluginAppsFingerprint: pluginThreadConfig?.fingerprint,
pluginAppsInputFingerprint: pluginThreadConfig?.inputFingerprint,
pluginAppPolicyContext: pluginThreadConfig?.policyContext,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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