Compare commits

..

82 Commits

Author SHA1 Message Date
Peter Steinberger
4149dc0af1 fix: keep private SDK declarations local 2026-05-28 03:16:45 +01:00
Dallin Romney
39973675c5 test: guard private sdk declaration leaks
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-27 18:57:27 -07:00
Dallin Romney
a974fd2f77 fix(ci): stabilize sdk boundary checks 2026-05-27 18:34:17 -07:00
Dallin Romney
59a4ab4daa fix(canvas): use focused number runtime helpers 2026-05-27 18:34:17 -07:00
Dallin Romney
ca61f668d5 test: simplify packed sdk type smoke 2026-05-27 18:34:17 -07:00
Dallin Romney
3cda9be785 refactor: move packed sdk smoke to fixture 2026-05-27 18:34:17 -07:00
Dallin Romney
ed2c6bee69 fix: align package inventory with flat sdk declarations 2026-05-27 18:34:17 -07:00
Dallin Romney
2815ac066b refactor: flatten plugin sdk declarations 2026-05-27 18:34:17 -07:00
Andy
d2319d718c fix(status): keep default JSON scan lean
Default `openclaw status --json` stays on the lean health-probe path while preserving the JSON task summary, local update/install metadata, explicit probe timeouts, and configured gateway handshake timeouts. Deeper memory, registry, remote git, and local status-RPC diagnostics remain behind `status --json --all`.

Also keeps generated diffs viewer output in its built form and ignores it in oxfmt so `pnpm build` leaves a clean tree.

Proof:
- `node scripts/run-vitest.mjs src/commands/status.scan.fast-json.test.ts src/commands/status-json-payload.test.ts src/commands/status.scan.shared.test.ts`
- `OPENCLAW_LOCAL_CHECK=0 node scripts/run-oxlint-shards.mjs --threads=8`
- `node scripts/run-tsgo.mjs -p test/tsconfig/tsconfig.core.test.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/core-test.tsbuildinfo`
- `node scripts/run-tsgo.mjs -p test/tsconfig/tsconfig.extensions.test.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/extensions-test.tsbuildinfo`
- `.agents/skills/autoreview/scripts/autoreview --mode branch --base origin/main`
- GitHub checks green for head `47a63f87ea7c2351994fdb71e8cc18041aa0b64e`

Thanks @andyylin.

Co-authored-by: Andy <andyylin@users.noreply.github.com>
2026-05-28 02:28:49 +01:00
Vincent Koc
5846878924 fix(auth): honor OAuth login cancellation 2026-05-28 03:12:40 +02:00
Vincent Koc
a20c091411 test(reply): avoid redundant settled hook return unions 2026-05-28 02:55:01 +02:00
Vincent Koc
069f33b410 test(openai): type malformed context window fixture 2026-05-28 02:55:01 +02:00
Vincent Koc
28a719f3da fix(agents): allow steering yielded subagents 2026-05-28 02:55:01 +02:00
Peter Steinberger
7c7fb7df67 chore(release): refresh plugin sdk baseline 2026-05-28 01:51:27 +01:00
Peter Steinberger
cee2a50fe6 chore(release): prepare 2026.5.28 2026-05-28 01:48:07 +01:00
Peter Steinberger
0e262d20e7 fix(discord): fence tool warning fallback delivery (#87465)
* 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:39:14 +01:00
Vincent Koc
748510b7a3 fix(doctor): validate tool schemas for configured agents 2026-05-28 02:17:43 +02:00
Peter Steinberger
45e6af5e57 fix: reject partial numeric runtime values 2026-05-27 20:10:01 -04:00
Peter Steinberger
d1aa3cb925 fix: reject partial numeric command values 2026-05-27 20:10:01 -04:00
WarrenJones
65e2120f8c fix(hooks): pass media metadata to received hook
Forward canonical inbound media metadata to plugin message_received hooks so plugins can inspect the same mediaPath, mediaUrl, mediaType, mediaPaths, mediaUrls, and mediaTypes fields already available to inbound_claim.

Verification:
- node scripts/run-vitest.mjs src/hooks/message-hook-mappers.test.ts
- /Users/steipete/Projects/agent-scripts/skills/autoreview/scripts/autoreview --mode branch --base origin/main

Refs: https://github.com/openclaw/openclaw/pull/87297
Co-authored-by: WarrenJones <8704779+WarrenJones@users.noreply.github.com>
2026-05-28 01:06:00 +01:00
Martin Kessler
d00e764e66 fix(heartbeat): stop pending final replay
Stop heartbeat runs from directly returning non-ack durable pending final text. Heartbeats now only clear ack-only pending state and otherwise continue the heartbeat turn, so stale prior final answers cannot be replayed through a later heartbeat/default route.

Keep the isolated heartbeat active-run guard so an immediate/manual heartbeat cannot overwrite an isolated heartbeat session that is still running.

Proof:
- node scripts/run-vitest.mjs src/auto-reply/reply/get-reply.fast-path.test.ts src/infra/heartbeat-runner.skips-busy-session-lane.test.ts
- git diff --check
- autoreview --mode local
- autoreview --mode branch --base origin/main
- GitHub CI 26543804437, CodeQL 26543804438, Critical Quality 26543804441, OpenGrep PR Diff 26543804440 rerun job 78197443511, Real behavior proof 26544027357

Refs #74257.

Co-authored-by: kesslerio <martin@kessler.io>
2026-05-28 00:58:57 +01:00
Peter Steinberger
c86667c5cf test(discord): use reply payload SDK test helper (#87454)
* test(discord): use reply payload SDK test helper

* build(plugin-sdk): exclude reply payload test helper
2026-05-28 00:57:22 +01:00
Peter Steinberger
ff0990d800 fix: accept uncommitted autoreview mode 2026-05-28 00:55:08 +01:00
Edward Abrams
05db911775 fix(outbound): thread session keys into outbound hooks (#73706)
Thread the canonical outbound session key into plugin message_sending and message_sent hook contexts, and align native command redirect routed delivery with the agent runtime session key. This lets plugins correlate agent_end with outbound delivery hooks without seeing missing or divergent session keys.

Verification:
- gh pr checks 73706 --repo openclaw/openclaw --watch=false
- Real behavior proof: https://github.com/openclaw/openclaw/actions/runs/26526635074/job/78131933497

Thanks @zeroaltitude.

Co-authored-by: Edward Abrams <zeroaltitude@gmail.com>
2026-05-28 00:43:27 +01:00
Vincent Koc
c9151ba902 fix(provider): bound local service startup 2026-05-28 01:38:35 +02:00
Peter Steinberger
1f1cdd84ea chore: forward gateway profiling env 2026-05-28 00:35:35 +01:00
Peter Steinberger
da279041ab fix(discord): suppress recovered tool warnings (#87451) 2026-05-28 00:32:28 +01:00
Fermin Quant
3f9d2415ac 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>
2026-05-28 00:31:19 +01:00
Alix-007
8b7a4826a1 fix(agents): keep hook context prompt-local (#86875)
Fixes embedded agent prompt handling so before_prompt_build prepend/append context stays prompt-local: visible transcripts keep the user prompt, provider/model prompts keep hook context, and runtime/system context stays separate.

Local verification:
- git diff --check
- fnm exec --using v22.22.2 pnpm exec oxfmt --check src/agents/embedded-agent-runner/tool-result-context-guard.ts src/agents/embedded-agent-runner/tool-result-context-guard.test.ts
- fnm exec --using v22.22.2 node scripts/run-oxlint.mjs --tsconfig config/tsconfig/oxlint.core.json src/agents/embedded-agent-runner/tool-result-context-guard.ts src/agents/embedded-agent-runner/tool-result-context-guard.test.ts
- fnm exec --using v22.22.2 pnpm tsgo:test:src
- autoreview clean: no accepted/actionable findings

CI verification:
- GitHub CI run 26544578760 passed on rebased head 9715d3a01a

Co-authored-by: Alix-007 <267018309+Alix-007@users.noreply.github.com>
2026-05-28 00:29:31 +01:00
alkor2000
603aa8a2ed fix(doctor): rewrite non-canonical api_key auth profiles
Rewrites non-canonical api_key fields in auth-profiles.json to canonical key via openclaw doctor --fix, with backups, while preserving canonical key/keyRef credentials and active-agent auth stores.

Fixes #57389.

Co-authored-by: alkor2000 <200923177@qq.com>
2026-05-28 00:29:28 +01:00
lukeboyett
b5bd6e8828 fix(sessions): preserve Matrix room-id case in session keys (#75670) (#87366)
* fix(sessions): preserve Matrix room-id case in session keys (#75670)

Matrix room IDs (and thread event IDs) are opaque, case-sensitive per the
Matrix spec, but session-key canonicalization lowercased them. That forked
one room into duplicate sessions and produced 403 M_FORBIDDEN on recovery /
delivery paths that reconstruct the target from the (lowercased) session key,
even though deliveryContext.to stayed correct.

Introduce a generic, opt-in case-preservation registry (CASE_PRESERVING_PEERS)
consulted at all three lowercasing sites:
- construction: normalizeSessionPeerId
- store canonicalization: normalizeSessionKeyPreservingOpaquePeerIds
- gateway send: explicit request.sessionKey

Signal group preservation is encoded to match prior behavior exactly (segment
span, unscoped, thread suffix still lowercased). Matrix channel/group enrolls
the opaque tail (room id with embedded :server + any 🧵<event> suffix).
Exact mixed-case keys now win over folded legacy aliases in
resolveSessionStoreEntry and delivery-info lookup; existing lowercased rows
collapse on the next write. Matrix DM/MXID and non-enrolled channels keep the
default lowercase behavior.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(sessions): guard Matrix folded alias delivery proof

* test(agents): cover cold OpenAI gpt-5.5 fallback

* fix(sessions): preserve non-opaque alias freshness

* fix(sessions): prevent Matrix cross-room thread recovery

* build(protocol): refresh tools effective Swift models

* test(codex): include effective cwd in startup fixture

* test(codex): align startup failure cleanup expectation

* fix(sessions): keep Signal folded aliases fresh

* fix(sessions): preserve unscoped Matrix room keys

* fix(sessions): recover legacy Matrix thread aliases

* fix(sessions): preserve Matrix keys in state migrations

* fix(sessions): keep Matrix structural alias freshness

* fix(sessions): preserve unscoped Matrix migration keys

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-28 00:26:49 +01:00
Vincent Koc
92043f7547 test(gateway): retry live exec read probe wording 2026-05-28 01:20:56 +02:00
Peter Steinberger
59c3ee7c45 fix(imessage): continue polling after denied reactions
(cherry picked from commit 6cc534af9b859301f9ff814bdd8672fa910141e3)
2026-05-28 00:17:52 +01:00
Chunyue Wang
65fb56513f fix(agents): release session lock on timeout abort
Fixes #86816.

Co-authored-by: Chunyue Wang <16864032@qq.com>
2026-05-28 00:16:40 +01:00
Vincent Koc
c20a055341 fix(provider): honor Codex response timeouts 2026-05-28 01:03:21 +02:00
Vincent Koc
da5fe990d8 fix(codex): report quarantined dynamic tools 2026-05-28 00:56:30 +02:00
Kevin Lin
40bca6d8bb 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-27 23:55:28 +01:00
Andy Ye
d8641a661b 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-27 23:55:24 +01:00
Andy Ye
cc72519053 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>
2026-05-27 23:55:14 +01:00
Peter Steinberger
550a9b459a test(ci): bound image tool iMessage fixtures 2026-05-27 18:50:49 -04:00
simplyclever914
169effacc2 feat(status): show active subagent details
Show active subagent detail rows in /status with labels and elapsed runtime while keeping completed-subagent summary behavior. Thanks @simplyclever914.
2026-05-27 23:49:46 +01:00
Sarah Fortune
6ac3561c69 fix(codex): format skills command output (#87400) 2026-05-27 15:43:05 -07:00
Paul Frederiksen
77fe36bb98 Improve stale Codex auth recovery guidance
Fixes #83935.

Summary:
- clear stale legacy openai-codex auto route pins only when the canonical OpenAI provider is still using the Codex harness for the same model
- preserve usable Codex auth profiles while clearing stale route state
- keep explicit/custom OpenAI API route pins intact

Verification:
- git diff --check
- pnpm exec oxfmt --check --threads=1 src/auto-reply/reply/model-selection.ts src/auto-reply/reply/model-selection.test.ts src/auto-reply/reply/agent-runner-execution.ts src/auto-reply/reply/agent-runner-execution.test.ts
- fnm exec --using 24.15.0 node scripts/run-tsgo.mjs -p test/tsconfig/tsconfig.core.test.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/core-test.tsbuildinfo
- .agents/skills/autoreview/scripts/autoreview --mode local
- CI: https://github.com/openclaw/openclaw/actions/runs/26542490863

Co-authored-by: Paul Frederiksen <paul@paulfrederiksen.com>
2026-05-27 23:35:48 +01:00
samzong
316fd5b625 [Fix] Warm provider auth off main thread (#86281)
* fix(agents): warm provider auth off main thread

Signed-off-by: samzong <samzong.lu@gmail.com>

* fix(agents): keep provider auth warm read-only

* fix(ci): unblock provider auth landing

* ci: serialize gateway watch artifact check

* fix(ci): stabilize diffs viewer asset generation

* fix(agents): avoid stale plugin auth warm results

* fix(agents): keep partial auth warm cache

---------

Signed-off-by: samzong <samzong.lu@gmail.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-27 23:24:55 +01:00
Peter Steinberger
5cef288d65 fix(agents): resolve Codex runtime models first
* fix(agents): resolve Codex runtime models first

* test(agents): align Codex runtime resolution fixtures
2026-05-27 23:23:22 +01:00
Gio Della-Libera
f3e285126a fix(doctor): make restart follow-up actionable (#87361) 2026-05-27 23:23:19 +01:00
Vincent Koc
53ad531df9 fix(crabbox): preserve sparse run artifacts 2026-05-28 00:20:39 +02:00
Peter Steinberger
78c5eeab01 docs(changelog): require contributor thanks 2026-05-27 23:20:03 +01:00
Peter Steinberger
5d437de70f fix(web-search): preserve runtime-only provider config
Fixes #87191. Keeps Brave and Gemini runtime-injected web search provider config readable by providers without re-exposing legacy tools.web.search provider objects to config validation.
2026-05-27 23:17:07 +01:00
xiaotian
fb1dfd486b 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>
2026-05-27 23:16:17 +01:00
Peter Steinberger
cf47580a45 test(ci): align startup and model fixtures 2026-05-27 18:09:03 -04:00
Peter Steinberger
efbd00f282 fix: preserve retry-after fallback 2026-05-27 18:03:13 -04:00
Peter Steinberger
f24844d801 fix: reject partial numeric parsing 2026-05-27 18:00:19 -04:00
Peter Steinberger
db549137d3 fix(agents): bound compaction wake retry timeouts 2026-05-27 22:57:51 +01:00
alkor2000
ea2e9ce8bd 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.
2026-05-27 22:57:51 +01:00
alkor2000
a7b8e6a5a9 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
2026-05-27 22:57:51 +01:00
Mariano
7299c56953 Fix sub-agent cwd/workspace separation (#87218)
Merged via squash.

Prepared head SHA: f47b073830
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Reviewed-by: @mbelinky
2026-05-27 23:55:24 +02:00
martingarramon
039fcbaa4c 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>
2026-05-27 22:51:11 +01:00
Kevin Lin
bb752c2b47 Revert "feat: expose plugin approval action metadata" (#87419)
This reverts commit 0c867eef75.

# Conflicts:
#	docs/.generated/plugin-sdk-api-baseline.sha256
2026-05-27 14:48:06 -07:00
Vincent Koc
dfcf211232 test(agents): clarify live subagent steering prompt 2026-05-27 23:45:35 +02:00
Vincent Koc
5ad8036bda test(openai): stabilize live audio transcription 2026-05-27 23:45:35 +02:00
Vincent Koc
7b967c5701 fix(oauth): bound GitHub Copilot requests 2026-05-27 23:27:27 +02:00
Patrick Erichsen
b4e5038692 fix(cli): respect subcommand version options (#87398)
* fix(cli): respect subcommand version options

* test: stabilize model directive auth status
2026-05-27 16:26:11 -05:00
Vincent Koc
67277088eb fix(oauth): bound Codex token requests 2026-05-27 23:20:15 +02:00
Peter Steinberger
5f68291f4f fix(agents): move session write lock into owned session runtime (#87409)
* fix(agents): move session write lock into owned session runtime

* test(agents): use typed tool-call fixtures
2026-05-27 22:17:35 +01:00
Vincent Koc
21d9609866 fix(gateway): quarantine unsupported effective tool schemas 2026-05-27 23:15:24 +02:00
Mariano Belinky
a7d2d9c6df fix: migrate legacy memory auto provider 2026-05-27 23:03:32 +02:00
Vincent Koc
09d2682cd8 fix(openai): resolve gpt-5.5 without cached catalog 2026-05-27 22:57:30 +02:00
Vincent Koc
00004ca798 fix(cli): wait for respawn child shutdown 2026-05-27 22:57:30 +02:00
Peter Steinberger
7f7eca1ad2 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
2026-05-27 21:50:41 +01:00
Dallin Romney
87944c0d80 chore(ui): mark generated locale artifacts (#87406) 2026-05-27 13:48:21 -07:00
Vincent Koc
f39f1a4712 fix(e2e): bound MCP channel harness buffers 2026-05-27 22:34:08 +02:00
Vincent Koc
1eb27da55d fix(testing): bound openclaw instance logs 2026-05-27 22:29:36 +02:00
Peter Steinberger
9ff071f646 test(config): clear install record cache in validation fixture 2026-05-27 16:23:01 -04:00
GarlicGo
2900c1c25c fix(inbound-meta): include seconds in timestamps
Include second-level precision in inbound metadata and auto-reply envelope timestamps, matching the timestamp helper contract used by providers and channel adapters.

Docs now show the weekday plus seconds form in date-time and timezone examples.

Verification:
- node scripts/run-vitest.mjs src/auto-reply/envelope.test.ts src/auto-reply/reply/inbound-meta.test.ts
- pnpm docs:list >/tmp/openclaw-docs-list-87360.log
- git diff --check origin/main...HEAD
- pnpm format:docs:check
- pnpm lint:docs
- pnpm lint:extensions:bundled
- pnpm lint
- PR CI green on 495bb6c10f

Fixes #87257

Co-authored-by: GarlicGo <582149912@qq.com>
2026-05-27 21:18:08 +01:00
Alix-007
f4329fe0d6 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>
2026-05-27 21:16:15 +01:00
Peter Steinberger
b257b988a1 perf(plugins): trust install records cache between reloads 2026-05-27 21:13:39 +01:00
Pavan Kumar Gondhi
c923b07784 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>
2026-05-27 21:13:20 +01:00
Vincent Koc
d9051151d7 fix(gateway): scope assistant idempotency dedupe 2026-05-27 22:09:31 +02:00
Vincent Koc
4ff944c0e8 fix(ci): stabilize model picker and release checks 2026-05-27 22:05:38 +02:00
Peter Steinberger
171675b54b docs: clarify backport target 2026-05-27 21:05:25 +01:00
Peter Steinberger
d30ba7f961 fix(ci): satisfy codex extension lint 2026-05-27 16:05:06 -04:00
640 changed files with 17941 additions and 5650 deletions

View File

@@ -50,8 +50,9 @@ Dirty local work:
```
Use this only when the patch is actually unstaged/staged/untracked in the
current checkout. For committed, pushed, or PR work, point the helper at the commit
or branch diff instead; do not force `--mode local` / `--uncommitted` just
current checkout. `--mode uncommitted` is accepted as an alias for `--mode local`.
For committed, pushed, or PR work, point the helper at the commit
or branch diff instead; do not force dirty modes just
because the helper docs mention dirty work first. A clean local review
only proves there is no local patch.
@@ -164,6 +165,7 @@ If installed from `agent-scripts`, path is:
The helper:
- chooses dirty local changes first
- accepts `--mode uncommitted` as an alias for `--mode local`
- otherwise uses current PR base if `gh pr view` works
- otherwise uses `origin/main` for non-main branches
- supports `--engine codex`, `claude`, `droid`, and `copilot`; default is `AUTOREVIEW_ENGINE` or `codex`; Codex should remain the default when nothing is set

View File

@@ -238,6 +238,7 @@ def is_dirty(repo: Path) -> bool:
def choose_target(repo: Path, mode: str, base_ref: str | None) -> tuple[str, str | None]:
mode = "local" if mode == "uncommitted" else mode
branch = current_branch(repo)
if mode == "local" or (mode == "auto" and is_dirty(repo)):
return "local", None
@@ -889,7 +890,7 @@ def finish_parallel_tests(proc: subprocess.Popen, started: float) -> int:
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Bundle-driven AI code review.")
parser.add_argument("--mode", choices=["auto", "local", "branch", "commit"], default="auto")
parser.add_argument("--mode", choices=["auto", "local", "uncommitted", "branch", "commit"], default="auto")
parser.add_argument("--base")
parser.add_argument("--commit", default="HEAD")
parser.add_argument("--engine", choices=ENGINES, default=os.environ.get("AUTOREVIEW_ENGINE", "codex"))

View File

@@ -46,6 +46,8 @@ preserving issue/PR refs and thanks.
- `### Fixes`: user-facing fixes first, grouped by impact and surface
6. Preserve attribution:
- keep `#issue`, `(#PR)`, `Fixes #...`, and `Thanks @...`
- every human-authored merged PR represented by a user-facing entry needs
its PR ref and `Thanks @author`, even when the PR had no linked issue
- do not add GHSA references, advisory IDs, or security advisory slugs to
changelog entries or GitHub release-note text unless explicitly requested
- never thank bots, `@openclaw`, `@clawsweeper`, or `@steipete`

3
.gitattributes vendored
View File

@@ -1,3 +1,6 @@
* text=auto eol=lf
CLAUDE.md -text
src/gateway/server-methods/CLAUDE.md -text
ui/src/i18n/.i18n/* linguist-generated
ui/src/i18n/locales/*.ts linguist-generated
ui/src/i18n/locales/en.ts -linguist-generated

View File

@@ -30,6 +30,7 @@
"docker-compose.yml",
"dist/",
"docs/_layouts/",
"extensions/diffs/assets/viewer-runtime.js",
"**/*.json",
"node_modules/",
"patches/",

View File

@@ -211,6 +211,7 @@ Skills own workflows; root owns hard policy and routing.
- Lockfiles/shrinkwrap are security surface: review `pnpm-lock.yaml`, `npm-shrinkwrap.json`, `package-lock.json`; root/plugin npm packages ship shrinkwrap, not package-lock.
- Carbon pins owner-only: do not change `@buape/carbon` unless Shadow (`@thewilloftheshadow`, verified by `gh`) asks.
- Releases/publish/version bumps need explicit approval. Use `$release-openclaw-maintainer`.
- Backport means apply to newest open `release/` branch unless user names another target.
- GHSA/advisories: `$openclaw-ghsa-maintainer` / `$security-triage`. Secret scanning: `$openclaw-secret-scanning-maintainer`.
- Beta tag/version match: `vYYYY.M.D-beta.N` -> npm `YYYY.M.D-beta.N --tag beta`.

View File

@@ -2,6 +2,32 @@
Docs: https://docs.openclaw.ai
## 2026.5.28
### Highlights
- Agent and Codex runtime recovery is steadier: subagents keep cwd/workspace separation, hook context stays prompt-local, session locks release on timeout abort, stale restart continuations are avoided, and Codex app-server/helper failures no longer tear down shared runtime state. (#87218, #86875, #87409, #87399, #87375)
- Channel delivery and session identity got safer across outbound plugin hooks, Matrix room ids, iMessage reactions/approvals, Slack final replies, Discord recovered tool warnings, and Microsoft Teams service URL trust checks. (#73706, #75670, #87366, #87451, #87334)
- CLI, auth, doctor, and provider paths fail faster and recover more clearly: malformed numeric/version options are rejected, OAuth and local service startup requests are bounded, legacy `api_key` auth profiles migrate to canonical form, and restart guidance is actionable. (#87398, #86281, #87361)
- Plugin and Gateway hot paths do less repeated work while preserving cache correctness for install records, config JSON parsing, tool search catalogs, session stores, manifest model rows, auto-enabled plugin config, browser tokens, and viewer assets. (#86699)
- Release, QA, and E2E validation now bound more log, artifact, harness, and cross-OS waits so failing lanes produce proof instead of hanging or false-greening.
### Changes
- Status: show active subagent details in status output.
- Diffs: split the default language pack and expand default Diffs language coverage while keeping the host floor aligned. (#87370, #87372) Thanks @RomneyDa.
- ClawHub: add plugin display names plus skill verification and trust surfaces. (#87354, #86699) Thanks @thewilloftheshadow and @Patrick-Erichsen.
- Docs: clarify Codex computer-use setup, paste-token stdin auth setup, macOS gateway sleep troubleshooting, native Codex hook relay recovery, container model auth, install deployment cards, device-token admin gating, and backport targets. (#87313, #63050) Thanks @bdjben, @liaoandi, and @thewilloftheshadow.
### Fixes
- Agents/Codex: keep spawned agent cwd/workspace state separated, keep hook context prompt-local, release session locks on timeout abort, avoid session event queue self-wait, preserve shared app-server state across startup or helper failures, keep native hook relay alive across restarts, route workspace memory through tools, resolve Codex runtime models first, report quarantined dynamic tools, format `skills` command output, and bound compaction/steering retries. (#87218, #86875, #86123, #87399, #87375, #87383, #87400) Thanks @mbelinky, @Alix-007, @luoyanglang, @yetval, and @sjf.
- Channels: thread canonical session keys into outbound hooks, preserve Matrix room-id case, keep fallback tool warnings mention-inert, retain delivered Slack final replies during late cleanup, continue iMessage polling after denied reactions, suppress duplicate native exec approvals, preserve Telegram SecretRef prompt config, suppress Discord recovered tool warnings, and block untrusted Teams service URLs. (#73706, #75670, #87366, #87451, #87334) Thanks @zeroaltitude, @lukeboyett, @xiaotian, and @eleqtrizit.
- CLI/auth/doctor/providers: reject malformed numeric/timeout/subcommand-version inputs, wait for respawn child shutdown, bound Codex and GitHub Copilot OAuth/token requests, warm provider auth off the main thread, honor Codex response timeouts, bound local service startup, resolve GPT-5.5 without cached catalog, migrate legacy memory auto-provider config, rewrite non-canonical `api_key` auth profiles, and make doctor restart follow-ups actionable. (#87398, #86281, #87361) Thanks @Patrick-Erichsen, @samzong, @giodl73-repo, and @alkor2000.
- Gateway/security/session state: expire browser tokens after auth rotation, scope assistant idempotency dedupe, drain probe client closes, avoid stale restart continuation reuse, preserve retry-after fallbacks, bound webchat image and artifact transcript scans, include seconds in inbound metadata timestamps, and evict current plugin-state namespaces at row caps.
- Performance: trust install-record caches between reloads, prefer native JSON parsing, reuse unchanged tool-search catalogs, skip unchanged store serialization, add precomputed session patch writers, reduce store clone allocations, cache manifest model catalog rows and auto-enabled plugin config, and slim current metadata identity caches.
- Docker/release/QA: package runtime workspace templates, stream cross-OS served artifacts, preserve sparse Crabbox run artifacts, bound OpenClaw instance logs, plugin gauntlet relay logs, MCP channel buffers, kitchen-sink scans, agent-turn assertions, and release scenario logs, and keep release/google live guards current.
## 2026.5.27
### Highlights
@@ -22,12 +48,15 @@ Docs: https://docs.openclaw.ai
### Fixes
- Security/CLI/runtime: harden hostname normalization for repeated trailing dots, block side-effecting command wrappers, reject unsafe Node runtime env overrides, reject loose numeric CLI and gateway options, require admin approval for node device-role pairing, and reject no-auth Tailscale exposure. (#87305, #87292, #87308, #87146) Thanks @pgondhi987.
- Doctor: validate runtime tool schemas for every configured embedded agent while skipping ACP-only profiles, so bad non-default plugin or MCP tools are reported before assistant turns.
- 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.
- Matrix/auto-reply: keep draft previews mention-inert, preserve final mention delivery, send mention finals normally, await shared DM notices, ignore filename-embedded MXIDs, and suppress reasoning-prefixed `NO_REPLY` responses.
- Agents/providers: add OpenAI-compatible cache retention, forward cached token usage in chat completions, preserve runtime context before active user turns, strip stale Anthropic thinking, load Claude CLI OAuth for Pi auth profiles, avoid false Codex runtime live switches, and quarantine unsupported tool schemas. (#82062, #87167, #86855)
- Gateway/performance: cache plugin metadata fingerprints and stable plugin index fingerprints, borrow read-only session metadata safely, keep the active session working store hot, keep status on a bounded fast path, and preserve model auth profile suffixes. (#86439)
- Package/install/release: align npm package exclusions and inventory, omit unpacked test helpers, skip Homebrew until macOS packages need it, cap tsdown heap in containers, bound install/release smoke waits, and harden post-publish verification.
- Codex/Auth: bound ChatGPT OAuth token exchange and refresh requests, and honor cancellation across Codex and Anthropic OAuth login flows.
- QA/E2E/CI: bound Telegram, kitchen-sink, Open WebUI, ClawHub, MCP, Discord, realtime, labeler, and GitHub API waits; fail empty explicit test, live-media, gateway CPU, plugin gauntlet, and beta-smoke runs instead of false-greening.
- Agents/Codex: keep spawned agent bootstrap files rooted in the agent workspace while running task commands, transcripts, and compaction from the requested cwd. (#87218) Thanks @mbelinky.
## 2026.5.26
@@ -61,7 +90,7 @@ Docs: https://docs.openclaw.ai
- Voice: share activation-name matching and consult-transcript screening through the realtime voice SDK so Discord, browser voice, and meeting surfaces can reuse one implementation.
- Cron: default `cron.maxConcurrentRuns` to 8 so scheduled automations and their isolated agent turns can make progress in parallel without explicit configuration.
- QA-Lab: add `qa coverage --match <query>` so focused proof selection can discover matching scenarios from existing metadata before running live or remote lanes.
- Discord/model picker: surface an alpha-bucket select (e.g. `AG (12) · HN (18) · OZ (5)`) when the provider list or a provider's model list exceeds 25 items, so configs with `provider/*` wildcards stay one click from the right page instead of paginating through prev/next; falls back to numeric chunks when every item shares the same first letter.
- Discord/model picker: surface an alpha-bucket select (e.g. `AG (12) · HN (18) · OZ (5)`) when the provider list or a provider's model list exceeds 25 items, so configs with `provider/*` wildcards stay one click from the right page instead of paginating through prev/next; falls back to numeric chunks when every item shares the same first letter. (#86181) Thanks @rendrag-git.
- Control UI: add an ephemeral Activity tab for sanitized live tool activity summaries without persisting raw telemetry. Fixes #12831. Thanks @BunsDev.
- Build: include `ui:build` in the `full` and `ciArtifacts` profiles of `scripts/build-all.mjs` so `pnpm build` always rebuilds `dist/control-ui` after `tsdown` cleans `dist`, removing the second-command requirement and the missing-asset failure mode for source/runtime installs and CI artifact uploads. (#85206)
- iOS: improve Talk mode with direct realtime voice sessions, compact toolbar status, and responsive voice waveform feedback. (#86355) Thanks @ngutman.

View File

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

View File

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

View File

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

View File

@@ -2177,6 +2177,7 @@ public struct SessionsPatchParams: Codable, Sendable {
public let model: AnyCodable?
public let spawnedby: AnyCodable?
public let spawnedworkspacedir: AnyCodable?
public let spawnedcwd: AnyCodable?
public let spawndepth: AnyCodable?
public let subagentrole: AnyCodable?
public let subagentcontrolscope: AnyCodable?
@@ -2202,6 +2203,7 @@ public struct SessionsPatchParams: Codable, Sendable {
model: AnyCodable?,
spawnedby: AnyCodable?,
spawnedworkspacedir: AnyCodable?,
spawnedcwd: AnyCodable?,
spawndepth: AnyCodable?,
subagentrole: AnyCodable?,
subagentcontrolscope: AnyCodable?,
@@ -2226,6 +2228,7 @@ public struct SessionsPatchParams: Codable, Sendable {
self.model = model
self.spawnedby = spawnedby
self.spawnedworkspacedir = spawnedworkspacedir
self.spawnedcwd = spawnedcwd
self.spawndepth = spawndepth
self.subagentrole = subagentrole
self.subagentcontrolscope = subagentcontrolscope
@@ -2252,6 +2255,7 @@ public struct SessionsPatchParams: Codable, Sendable {
case model
case spawnedby = "spawnedBy"
case spawnedworkspacedir = "spawnedWorkspaceDir"
case spawnedcwd = "spawnedCwd"
case spawndepth = "spawnDepth"
case subagentrole = "subagentRole"
case subagentcontrolscope = "subagentControlScope"
@@ -4990,25 +4994,51 @@ public struct ToolsEffectiveGroup: Codable, Sendable {
}
}
public struct ToolsEffectiveNotice: Codable, Sendable {
public let id: String
public let severity: AnyCodable
public let message: String
public init(
id: String,
severity: AnyCodable,
message: String)
{
self.id = id
self.severity = severity
self.message = message
}
private enum CodingKeys: String, CodingKey {
case id
case severity
case message
}
}
public struct ToolsEffectiveResult: Codable, Sendable {
public let agentid: String
public let profile: String
public let groups: [ToolsEffectiveGroup]
public let notices: [ToolsEffectiveNotice]?
public init(
agentid: String,
profile: String,
groups: [ToolsEffectiveGroup])
groups: [ToolsEffectiveGroup],
notices: [ToolsEffectiveNotice]?)
{
self.agentid = agentid
self.profile = profile
self.groups = groups
self.notices = notices
}
private enum CodingKeys: String, CodingKey {
case agentid = "agentId"
case profile
case groups
case notices
}
}
@@ -5952,7 +5982,6 @@ public struct PluginApprovalRequestParams: Codable, Sendable {
public let toolname: String?
public let toolcallid: String?
public let alloweddecisions: [String]?
public let actions: [[String: AnyCodable]]?
public let agentid: String?
public let sessionkey: String?
public let turnsourcechannel: String?
@@ -5961,7 +5990,6 @@ public struct PluginApprovalRequestParams: Codable, Sendable {
public let turnsourcethreadid: AnyCodable?
public let timeoutms: Int?
public let twophase: Bool?
public let keeppendingwithoutroute: Bool?
public init(
pluginid: String?,
@@ -5971,7 +5999,6 @@ public struct PluginApprovalRequestParams: Codable, Sendable {
toolname: String?,
toolcallid: String?,
alloweddecisions: [String]?,
actions: [[String: AnyCodable]]?,
agentid: String?,
sessionkey: String?,
turnsourcechannel: String?,
@@ -5979,8 +6006,7 @@ public struct PluginApprovalRequestParams: Codable, Sendable {
turnsourceaccountid: String?,
turnsourcethreadid: AnyCodable?,
timeoutms: Int?,
twophase: Bool?,
keeppendingwithoutroute: Bool?)
twophase: Bool?)
{
self.pluginid = pluginid
self.title = title
@@ -5989,7 +6015,6 @@ public struct PluginApprovalRequestParams: Codable, Sendable {
self.toolname = toolname
self.toolcallid = toolcallid
self.alloweddecisions = alloweddecisions
self.actions = actions
self.agentid = agentid
self.sessionkey = sessionkey
self.turnsourcechannel = turnsourcechannel
@@ -5998,7 +6023,6 @@ public struct PluginApprovalRequestParams: Codable, Sendable {
self.turnsourcethreadid = turnsourcethreadid
self.timeoutms = timeoutms
self.twophase = twophase
self.keeppendingwithoutroute = keeppendingwithoutroute
}
private enum CodingKeys: String, CodingKey {
@@ -6009,7 +6033,6 @@ public struct PluginApprovalRequestParams: Codable, Sendable {
case toolname = "toolName"
case toolcallid = "toolCallId"
case alloweddecisions = "allowedDecisions"
case actions
case agentid = "agentId"
case sessionkey = "sessionKey"
case turnsourcechannel = "turnSourceChannel"
@@ -6018,7 +6041,6 @@ public struct PluginApprovalRequestParams: Codable, Sendable {
case turnsourcethreadid = "turnSourceThreadId"
case timeoutms = "timeoutMs"
case twophase = "twoPhase"
case keeppendingwithoutroute = "keepPendingWithoutRoute"
}
}

View File

@@ -10,6 +10,7 @@ const rootEntries = [
"src/entry.ts!",
"src/cli/daemon-cli.ts!",
"src/agents/code-mode.worker.ts!",
"src/agents/model-provider-auth.worker.ts!",
"src/infra/kysely-node-sqlite.ts!",
"src/infra/warning-filter.ts!",
"src/infra/command-explainer/index.ts!",
@@ -156,12 +157,7 @@ const config = {
],
},
ui: {
entry: [
"index.html!",
"src/main.ts!",
"vite.config.ts!",
"vitest*.ts!",
],
entry: ["index.html!", "src/main.ts!", "vite.config.ts!", "vitest*.ts!"],
project: ["src/**/*.{ts,tsx}!"],
},
"packages/sdk": {

View File

@@ -1,4 +1,4 @@
de712076969bd63086959bf61c20a7581e5cae5b6982ffe83eefcc5b47ad8700 config-baseline.json
13fb390fd71a8d456cdfd42e6d9e577eba286e4509cc4e1a11c42f2e19255514 config-baseline.core.json
a69acd971a7d54d3086f26c52fde4084eaeef350f71b918fb8e7338f329bff95 config-baseline.json
ee4c0f0fb15cda02268f2e83d0c5e1c8d0ec0a2c1b2fdb89cdfce308dadb2b8b config-baseline.core.json
b901fb766edfd9df630690281476fc4032c64772f69d1d8f7b2e0e913a90f229 config-baseline.channel.json
1c6b972bd2c4caf936729c2a898a70b010dfedec0700eedb2140d6ebbf4fd3d3 config-baseline.plugin.json
1b763a5524aca2d7ecf1eea38f845ad1ffed5c1b37e85e62f6a7902a3ee0f920 config-baseline.plugin.json

View File

@@ -1,2 +1,2 @@
fb007e48bdcd035d025fd3442f753268e40c6947ddd11a25405b6a259ff25c45 plugin-sdk-api-baseline.json
d1bb1847131b36ccc4f3813c68ac6ef7621247284c9366652ebcdd29aa327b3f plugin-sdk-api-baseline.jsonl
7039b60f2cea732a90db633328952faaddd919f0d098b303b29d554e64184073 plugin-sdk-api-baseline.json
1a78f4df81562af070c5379c6369a8bea9c704f985b5382a463364757b26db0d plugin-sdk-api-baseline.jsonl

View File

@@ -12,7 +12,7 @@ OpenClaw standardizes timestamps so the model sees a **single reference time** i
| Surface | What it shows | Default | Configured via |
| ----------------- | ------------------------------------------------------------------------------------------------------- | ------------------------------------- | ------------------------------------------------------- |
| Message envelopes | Wraps inbound channel messages: `[Signal +1555 2026-01-18 00:19 PST] hello` | Host-local | `agents.defaults.envelopeTimezone` |
| Message envelopes | Wraps inbound channel messages: `[Signal +1555 Sun 2026-01-18 00:19:42 PST] hello` | Host-local | `agents.defaults.envelopeTimezone` |
| Tool payloads | Channel `readMessages`-style tools return raw provider time + normalized `timestampMs` / `timestampUtc` | UTC fields always present | Not configurable — preserves provider-native timestamps |
| System prompt | A small `Current Date & Time` block with the **time zone only** (no clock value, for cache stability) | Host timezone if `userTimezone` unset | `agents.defaults.userTimezone` |

View File

@@ -11,10 +11,10 @@ Provider timestamps are preserved so tools keep their native semantics (current
## Message envelopes (local by default)
Inbound messages are wrapped with a timestamp (minute precision):
Inbound messages are wrapped with a timestamp (second precision):
```
[Provider ... 2026-01-05 16:26 PST] message text
[Provider ... Mon 2026-01-05 16:26:34 PST] message text
```
This envelope timestamp is **host-local by default**, regardless of the provider timezone.
@@ -45,19 +45,19 @@ You can override this behavior:
**Local (default):**
```
[WhatsApp +1555 2026-01-18 00:19 PST] hello
[WhatsApp +1555 Sun 2026-01-18 00:19:42 PST] hello
```
**User timezone:**
```
[WhatsApp +1555 2026-01-18 00:19 CST] hello
[WhatsApp +1555 Sun 2026-01-18 00:19:42 CST] hello
```
**Elapsed time enabled:**
```
[WhatsApp +1555 +30s 2026-01-18T05:19Z] follow-up
[WhatsApp +1555 +30s Sun 2026-01-18T05:19:00Z] follow-up
```
## System prompt: current date and time

View File

@@ -515,7 +515,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

@@ -183,81 +183,12 @@ local proof.
</Step>
</Steps>
## Plugin capabilities
<a id="registering-agent-tools"></a>
A single plugin can register any number of capabilities via the `api` object:
## Registering tools
| Capability | Registration method | Detailed guide |
| ---------------------- | ------------------------------------------------ | ------------------------------------------------------------------------------- |
| Text inference (LLM) | `api.registerProvider(...)` | [Provider Plugins](/plugins/sdk-provider-plugins) |
| CLI inference backend | `api.registerCliBackend(...)` | [CLI Backend Plugins](/plugins/cli-backend-plugins) |
| Channel / messaging | `api.registerChannel(...)` | [Channel Plugins](/plugins/sdk-channel-plugins) |
| Speech (TTS/STT) | `api.registerSpeechProvider(...)` | [Provider Plugins](/plugins/sdk-provider-plugins#step-5-add-extra-capabilities) |
| Realtime transcription | `api.registerRealtimeTranscriptionProvider(...)` | [Provider Plugins](/plugins/sdk-provider-plugins#step-5-add-extra-capabilities) |
| Realtime voice | `api.registerRealtimeVoiceProvider(...)` | [Provider Plugins](/plugins/sdk-provider-plugins#step-5-add-extra-capabilities) |
| Media understanding | `api.registerMediaUnderstandingProvider(...)` | [Provider Plugins](/plugins/sdk-provider-plugins#step-5-add-extra-capabilities) |
| Image generation | `api.registerImageGenerationProvider(...)` | [Provider Plugins](/plugins/sdk-provider-plugins#step-5-add-extra-capabilities) |
| Music generation | `api.registerMusicGenerationProvider(...)` | [Provider Plugins](/plugins/sdk-provider-plugins#step-5-add-extra-capabilities) |
| Video generation | `api.registerVideoGenerationProvider(...)` | [Provider Plugins](/plugins/sdk-provider-plugins#step-5-add-extra-capabilities) |
| Web fetch | `api.registerWebFetchProvider(...)` | [Provider Plugins](/plugins/sdk-provider-plugins#step-5-add-extra-capabilities) |
| Web search | `api.registerWebSearchProvider(...)` | [Provider Plugins](/plugins/sdk-provider-plugins#step-5-add-extra-capabilities) |
| Tool-result middleware | `api.registerAgentToolResultMiddleware(...)` | [SDK Overview](/plugins/sdk-overview#registration-api) |
| Agent tools | `api.registerTool(...)` | Below |
| Custom commands | `api.registerCommand(...)` | [Entry Points](/plugins/sdk-entrypoints) |
| Plugin hooks | `api.on(...)` | [Plugin hooks](/plugins/hooks) |
| Internal event hooks | `api.registerHook(...)` | [Entry Points](/plugins/sdk-entrypoints) |
| HTTP routes | `api.registerHttpRoute(...)` | [Internals](/plugins/architecture-internals#gateway-http-routes) |
| CLI subcommands | `api.registerCli(...)` | [Entry Points](/plugins/sdk-entrypoints) |
For the full registration API, see [SDK Overview](/plugins/sdk-overview#registration-api).
Bundled plugins can use `api.registerAgentToolResultMiddleware(...)` when they
need async tool-result rewriting before the model sees the output. Declare the
targeted runtimes in `contracts.agentToolResultMiddleware`, for example
`["pi", "codex"]`. This is a trusted bundled-plugin seam; external
plugins should prefer regular OpenClaw plugin hooks unless OpenClaw grows an
explicit trust policy for this capability.
If your plugin registers custom gateway RPC methods, keep them on a
plugin-specific prefix. Core admin namespaces (`config.*`,
`exec.approvals.*`, `wizard.*`, `update.*`) stay reserved and always resolve to
`operator.admin`, even if a plugin asks for a narrower scope.
`openclaw/plugin-sdk/gateway-method-runtime` is a reserved control-plane bridge
for plugin HTTP routes that declare
`contracts.gatewayMethodDispatch: ["authenticated-request"]`. It is an
intentional-use guard for reviewed native plugins, not a sandbox boundary.
Hook guard semantics to keep in mind:
- `before_tool_call`: `{ block: true }` is terminal and stops lower-priority handlers.
- `before_tool_call`: `{ block: false }` is treated as no decision.
- `before_tool_call`: `{ requireApproval: { ... } }` pauses agent execution and prompts the user for approval via the exec approval overlay, native channel approval clients, or the `/approve` command on any channel.
- `before_install`: `{ block: true }` is terminal and stops lower-priority handlers.
- `before_install`: `{ block: false }` is treated as no decision.
- `message_sending`: `{ cancel: true }` is terminal and stops lower-priority handlers.
- `message_sending`: `{ cancel: false }` is treated as no decision.
- `message_received`: prefer the typed `threadId` field when you need inbound thread/topic routing. Keep `metadata` for channel-specific extras.
- `message_sending`: prefer typed `replyToId` / `threadId` routing fields over channel-specific metadata keys.
The `/approve` command handles both exec and plugin approvals with bounded fallback: when an exec approval id is not found, OpenClaw retries the same id through plugin approvals. Plugin approval forwarding can be configured independently via `approvals.plugin` in config.
If custom approval plumbing needs to detect that same bounded fallback case,
prefer `isApprovalNotFoundError` from `openclaw/plugin-sdk/error-runtime`
instead of matching approval-expiry strings manually.
See [Plugin hooks](/plugins/hooks) for examples and the hook reference.
## Registering agent tools
Tools are typed functions the LLM can call. They can be required (always
available) or optional (user opt-in):
For simple plugins that only own a fixed set of tools, prefer
[`defineToolPlugin`](/plugins/tool-plugins). It generates manifest metadata and
keeps `contracts.tools` aligned. Use the lower-level `api.registerTool(...)`
surface when the plugin also owns channels, providers, hooks, services,
commands, or fully dynamic tool registration.
Tools can be required or optional. Required tools are always available when the
plugin is enabled. Optional tools require user opt-in.
```typescript
register(api) {

View File

@@ -196,22 +196,6 @@ type BeforeToolCallResult = {
timeoutMs?: number;
timeoutBehavior?: "allow" | "deny";
allowedDecisions?: Array<"allow-once" | "allow-always" | "deny">;
actions?: Array<
| {
kind: "decision";
label: string;
style: "primary" | "secondary" | "success" | "danger";
decision: "allow-once" | "allow-always" | "deny";
commandTemplate: string;
}
| {
kind: "command";
label: string;
style: "primary" | "secondary" | "success" | "danger";
commandTemplate: string;
}
>;
keepPendingWithoutRoute?: boolean;
pluginId?: string;
onResolution?: (
decision: "allow-once" | "allow-always" | "deny" | "timeout" | "cancelled",
@@ -227,21 +211,6 @@ Hook guard behavior for typed lifecycle hooks:
- `params` rewrites the tool parameters for execution.
- `requireApproval` pauses the agent run and asks the user through plugin
approvals. The `/approve` command can approve both exec and plugin approvals.
- `allowedDecisions` limits the built-in `/approve` decisions. Omit it to offer
`allow-once`, `allow-always`, and `deny`.
- `actions` customizes the commands shown with the approval. OpenClaw replaces
`{id}` in each `commandTemplate` with the generated approval id before storing
or rendering the request.
- Use `kind: "decision"` plus `decision` when the action resolves the approval
through OpenClaw's approval system. Native clients can render those actions as
buttons with canonical OpenClaw approval callbacks. Decision actions must be
included in `allowedDecisions` when that field is present. Use
`kind: "command"` for plugin-owned commands that collect more context or
verify an external workflow before resolving the approval; native clients
should leave those as visible command text.
- `keepPendingWithoutRoute: true` keeps the request pending when no approval
client or initiating-channel route can receive it. Use this only when your
plugin provides another documented command or UI path to resolve the request.
- A lower-priority `block: true` can still block after a higher-priority hook
requested approval.
- `onResolution` receives the resolved approval decision - `allow-once`,

View File

@@ -140,40 +140,40 @@ 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 |
| [diffs-language-pack](/plugins/reference/diffs-language-pack) | Adds syntax highlighting for languages outside the default diffs viewer set. | `@openclaw/diffs-language-pack`<br />npm; ClawHub: `clawhub:@openclaw/diffs-language-pack` | plugin |
| [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 |
| [diffs-language-pack](/plugins/reference/diffs-language-pack) | Adds syntax highlighting for languages outside the default diffs viewer set. | `@openclaw/diffs-language-pack`<br />npm; ClawHub: `clawhub:@openclaw/diffs-language-pack` | plugin |
| [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,30 +35,30 @@ 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 |
| [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 |
| [diffs-language-pack](/plugins/reference/diffs-language-pack) | Adds syntax highlighting for languages outside the default diffs viewer set. | `@openclaw/diffs-language-pack`<br />npm; ClawHub: `clawhub:@openclaw/diffs-language-pack` | plugin |
| [discord](/plugins/reference/discord) | Adds the Discord channel surface for sending and receiving OpenClaw messages. | `@openclaw/discord`<br />npm; ClawHub | channels: discord; contracts: transcriptSourceProviders |
| [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 |
@@ -67,15 +67,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 |
@@ -84,9 +84,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 |
@@ -95,15 +95,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 |
@@ -111,30 +111,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

@@ -155,7 +155,6 @@ Most channel plugins do not need approval-specific code.
- If custom approval auth intentionally allows only same-chat fallback, return `markImplicitSameChatApprovalAuthorization({ authorized: true })` from `openclaw/plugin-sdk/approval-auth-runtime`; otherwise core treats the result as explicit approver authorization.
- If a channel-owned native callback resolves approvals directly, use `isImplicitSameChatApprovalAuthorization(...)` before resolving so implicit fallback still goes through the channel's normal actor authorization.
- If a channel needs native approval delivery, keep channel code focused on target normalization plus transport/presentation facts. Use `createChannelExecApprovalProfile`, `createChannelNativeOriginTargetResolver`, `createChannelApproverDmTargetResolver`, and `createApproverRestrictedNativeApprovalCapability` from `openclaw/plugin-sdk/approval-runtime`. Put the channel-specific facts behind `approvalCapability.nativeRuntime`, ideally via `createChannelApprovalNativeRuntimeAdapter(...)` or `createLazyChannelApprovalNativeRuntimeAdapter(...)`, so core can assemble the handler and own request filtering, routing, dedupe, expiry, gateway subscription, and routed-elsewhere notices. `nativeRuntime` is split into a few smaller seams:
- Use `createNativeApprovalChannelRouteGates` from `openclaw/plugin-sdk/approval-native-runtime` when a channel supports both session-origin native delivery and explicit approval forwarding targets. The helper centralizes approval config selection, `mode` handling, agent/session filters, account binding, session-target matching, and target-list matching while callers still own the channel id, default forwarding mode, account lookup, transport-enabled check, target normalization, and turn-source target resolution. Do not use it to create core-owned channel policy defaults; pass the channel's documented default mode explicitly.
- `createChannelNativeOriginTargetResolver` uses the shared channel-route matcher by default for `{ to, accountId, threadId }` targets. Pass `targetsMatch` only when a channel has provider-specific equivalence rules, such as Slack timestamp prefix matching.
- Pass `normalizeTargetForMatch` to `createChannelNativeOriginTargetResolver` when the channel needs to canonicalize provider ids before the default route matcher or a custom `targetsMatch` callback runs, while preserving the original target for delivery. Use `normalizeTarget` only when the resolved delivery target itself should be canonicalized.
- `availability` - whether the account is configured and whether a request should be handled

View File

@@ -179,7 +179,7 @@ and pairing-path families.
| `plugin-sdk/approval-gateway-runtime` | Shared approval gateway-resolution helper |
| `plugin-sdk/approval-handler-adapter-runtime` | Lightweight native approval adapter loading helpers for hot channel entrypoints |
| `plugin-sdk/approval-handler-runtime` | Broader approval handler runtime helpers; prefer the narrower adapter/gateway seams when they are enough |
| `plugin-sdk/approval-native-runtime` | Native approval target, account-binding, route-gate, forwarding fallback, and local native exec prompt suppression helpers |
| `plugin-sdk/approval-native-runtime` | Native approval target + account-binding helpers and local native exec prompt suppression |
| `plugin-sdk/approval-reaction-runtime` | Hardcoded approval reaction bindings, reaction prompt payloads, reaction target stores, and compatibility export for local native exec prompt suppression |
| `plugin-sdk/approval-reply-runtime` | Exec/plugin approval reply payload helpers |
| `plugin-sdk/approval-runtime` | Exec/plugin approval payload helpers, native approval routing/runtime helpers, and structured approval display helpers such as `formatApprovalDisplayPath` |

View File

@@ -186,6 +186,9 @@ Per-agent overrides use `agents.list[].subagents.delegationMode`.
<ParamField path="agentId" type="string">
Spawn under another configured agent id when allowed by `subagents.allowAgents`.
</ParamField>
<ParamField path="cwd" type="string">
Optional task working directory for the child run. Native sub-agents still load bootstrap files from the target agent workspace; `cwd` only changes where runtime tools and CLI harnesses do the delegated work.
</ParamField>
<ParamField path="runtime" type='"subagent" | "acp"' default="subagent">
`acp` is only for external ACP harnesses (`claude`, `droid`, `gemini`, `opencode`, or explicitly requested Codex ACP/acpx) and for `agents.list[]` entries whose `runtime.type` is `acp`.
</ParamField>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/brave-plugin",
"version": "2026.5.27",
"version": "2026.5.28",
"description": "OpenClaw Brave Search provider plugin for web search.",
"repository": {
"type": "git",
@@ -21,10 +21,10 @@
"allowInvalidConfigRecovery": true
},
"compat": {
"pluginApi": ">=2026.5.27"
"pluginApi": ">=2026.5.28"
},
"build": {
"openclawVersion": "2026.5.27"
"openclawVersion": "2026.5.28"
},
"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.27",
"version": "2026.5.28",
"private": true,
"description": "OpenClaw browser tool plugin",
"type": "module",

View File

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

View File

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

View File

@@ -8,6 +8,10 @@ import {
resolveNodeFromNodeList,
type NodeMatchCandidate,
} from "openclaw/plugin-sdk/gateway-runtime";
import {
parseStrictFiniteNumber,
parseStrictPositiveInteger,
} from "openclaw/plugin-sdk/number-runtime";
import { defaultRuntime } from "openclaw/plugin-sdk/runtime";
import {
normalizeLowercaseStringOrEmpty,
@@ -70,41 +74,6 @@ export type CanvasCliDependencies = {
type CanvasNodeCandidate = NodeMatchCandidate;
type CanvasSnapshotRequestFormat = "png" | "jpeg";
function normalizeNumericString(value: string): string | undefined {
const trimmed = value.trim();
return trimmed ? trimmed : undefined;
}
function parseStrictPositiveInteger(value: unknown): number | undefined {
if (typeof value === "number") {
return Number.isSafeInteger(value) && value > 0 ? value : undefined;
}
if (typeof value !== "string") {
return undefined;
}
const normalized = normalizeNumericString(value);
if (!normalized || !/^\+?\d+$/.test(normalized)) {
return undefined;
}
const parsed = Number(normalized);
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : undefined;
}
function parseStrictFiniteNumber(value: unknown): number | undefined {
if (typeof value === "number") {
return Number.isFinite(value) ? value : undefined;
}
if (typeof value !== "string") {
return undefined;
}
const normalized = normalizeNumericString(value);
if (!normalized || !/^[+-]?(?:(?:\d+\.?\d*)|(?:\.\d+))(?:e[+-]?\d+)?$/i.test(normalized)) {
return undefined;
}
const parsed = Number(normalized);
return Number.isFinite(parsed) ? parsed : undefined;
}
function parseCanvasSnapshotRequestFormat(raw: unknown): CanvasSnapshotRequestFormat {
const format = normalizeLowercaseStringOrEmpty(normalizeOptionalString(raw) ?? "jpg");
switch (format) {

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,12 +1,12 @@
{
"name": "@openclaw/codex",
"version": "2026.5.27",
"version": "2026.5.28",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@openclaw/codex",
"version": "2026.5.27",
"version": "2026.5.28",
"dependencies": {
"@openai/codex": "0.134.0",
"typebox": "1.1.38",

View File

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

View File

@@ -0,0 +1,202 @@
import type {
CodexBundleMcpThreadConfig,
EmbeddedRunAttemptParams,
} from "openclaw/plugin-sdk/agent-harness-runtime";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { startCodexAttemptThread } from "./attempt-startup.js";
import { defaultLeasedCodexAppServerClientFactory } from "./client-factory.js";
import { CodexAppServerClient } from "./client.js";
import { type CodexPluginConfig, resolveCodexAppServerRuntimeOptions } from "./config.js";
import { clearSharedCodexAppServerClient } from "./shared-client.js";
import { createClientHarness, createCodexTestModel } from "./test-support.js";
type ClientHarness = ReturnType<typeof createClientHarness>;
function createAttemptParams(): EmbeddedRunAttemptParams {
return {
prompt: "hello",
sessionId: "session-1",
sessionKey: "agent:main:session-1",
sessionFile: "/tmp/session.jsonl",
effectiveCwd: "/tmp",
workspaceDir: "/tmp",
runId: "run-1",
provider: "codex",
modelId: "gpt-5.4-codex",
model: createCodexTestModel("codex"),
thinkLevel: "medium",
disableTools: true,
timeoutMs: 5_000,
authStorage: {} as never,
authProfileStore: { version: 1, profiles: {} },
modelRegistry: {} as never,
} as EmbeddedRunAttemptParams;
}
const pluginConfig: CodexPluginConfig = {
appServer: { command: "codex" },
};
const bundleMcpThreadConfig = {
configPatch: undefined,
diagnostics: [],
evaluated: false,
fingerprint: undefined,
} satisfies CodexBundleMcpThreadConfig;
function readHarnessMessages(writes: string[]): Array<{ id?: number; method?: string }> {
return writes.map((write) => JSON.parse(write) as { id?: number; method?: string });
}
function startThreadWithHarness(
startupTimeoutMs: number,
signal = new AbortController().signal,
overrides?: { pluginConfig?: CodexPluginConfig },
) {
const harness = createClientHarness();
vi.spyOn(CodexAppServerClient, "start").mockReturnValue(harness.client);
const effectivePluginConfig = overrides?.pluginConfig ?? pluginConfig;
const run = startCodexAttemptThread({
attemptClientFactory: defaultLeasedCodexAppServerClientFactory,
appServer: resolveCodexAppServerRuntimeOptions({ pluginConfig: effectivePluginConfig }),
pluginConfig: effectivePluginConfig,
computerUseConfig: effectivePluginConfig.computerUse ?? { enabled: false },
startupAuthProfileId: undefined,
startupAuthAccountCacheKey: undefined,
startupEnvApiKeyCacheKey: undefined,
agentDir: "/tmp/agent",
config: undefined,
buildAttemptParams: createAttemptParams,
sessionAgentId: "agent-1",
effectiveWorkspace: "/tmp",
effectiveCwd: "/tmp",
dynamicTools: [],
developerInstructions: undefined,
finalConfigPatch: undefined,
bundleMcpThreadConfig,
nativeToolSurfaceEnabled: true,
sandboxExecServerEnabled: false,
sandbox: null,
contextEngineProjection: undefined,
startupTimeoutMs,
signal,
onStartupTimeout: vi.fn(),
spawnedBy: undefined,
});
return { harness, run };
}
async function answerInitialize(harness: ClientHarness): Promise<void> {
await vi.waitFor(() => expect(harness.writes.length).toBeGreaterThanOrEqual(1), {
interval: 1,
timeout: 5_000,
});
const initialize = JSON.parse(harness.writes[0] ?? "{}") as { id?: number };
harness.send({ id: initialize.id, result: { userAgent: "openclaw/0.125.0 (macOS; test)" } });
}
async function waitForRequest(
harness: ClientHarness,
method: string,
): Promise<{ id?: number; method?: string }> {
await vi.waitFor(
() =>
expect(readHarnessMessages(harness.writes).some((write) => write.method === method)).toBe(
true,
),
{ interval: 1, timeout: 5_000 },
);
const request = readHarnessMessages(harness.writes).find((write) => write.method === method);
if (!request) {
throw new Error(`${method} request was not written`);
}
return request;
}
async function waitForThreadStart(harness: ClientHarness): Promise<{ id?: number }> {
return waitForRequest(harness, "thread/start");
}
describe("startCodexAttemptThread", () => {
beforeEach(() => {
vi.stubEnv("CODEX_API_KEY", "");
vi.stubEnv("OPENAI_API_KEY", "");
clearSharedCodexAppServerClient();
});
afterEach(() => {
clearSharedCodexAppServerClient();
vi.restoreAllMocks();
vi.unstubAllEnvs();
});
it("clears the shared app-server when top-level thread startup fails with an app error", async () => {
const { harness, run } = startThreadWithHarness(5_000);
await answerInitialize(harness);
const threadStart = await waitForThreadStart(harness);
harness.send({
id: threadStart.id,
error: { code: -32000, message: "401 authentication_error: Invalid bearer token" },
});
await expect(run).rejects.toThrow("Invalid bearer token");
expect(harness.process.stdin.destroyed).toBe(true);
});
it("clears the shared app-server when startup abandons an in-flight thread request", async () => {
const { harness, run } = startThreadWithHarness(2_000);
const runError = run.then(
() => undefined,
(error: unknown) => error,
);
await answerInitialize(harness);
await waitForThreadStart(harness);
const error = await runError;
expect(error).toBeInstanceOf(Error);
expect((error as Error).message).toBe("codex app-server startup timed out");
expect(harness.process.stdin.destroyed).toBe(true);
});
it("clears the shared app-server when cancellation abandons an in-flight thread request", async () => {
const abortController = new AbortController();
const { harness, run } = startThreadWithHarness(5_000, abortController.signal);
const runError = run.then(
() => undefined,
(error: unknown) => error,
);
await answerInitialize(harness);
await waitForThreadStart(harness);
abortController.abort();
const error = await runError;
expect(error).toBeInstanceOf(Error);
expect((error as Error).message).toBe("codex app-server startup aborted");
expect(harness.process.stdin.destroyed).toBe(true);
});
it("clears the shared app-server when a startup RPC times out", async () => {
const perRpcTimeoutPluginConfig = {
...pluginConfig,
appServer: { command: "codex", requestTimeoutMs: 100 },
computerUse: { enabled: true, marketplaceDiscoveryTimeoutMs: 1 },
} satisfies CodexPluginConfig;
const { harness, run } = startThreadWithHarness(5_000, new AbortController().signal, {
pluginConfig: perRpcTimeoutPluginConfig,
});
const runError = run.then(
() => undefined,
(error: unknown) => error,
);
await answerInitialize(harness);
await waitForRequest(harness, "plugin/list");
const error = await runError;
expect(error).toBeInstanceOf(Error);
expect((error as Error).message).toBe("plugin/list timed out");
expect(harness.process.stdin.destroyed).toBe(true);
});
});

View File

@@ -49,7 +49,6 @@ import {
} from "./shared-client.js";
import {
startOrResumeThread,
isCodexThreadStartRequestError,
type CodexAppServerThreadLifecycleBinding,
type CodexContextEngineThreadBootstrapProjection,
} from "./thread-lifecycle.js";
@@ -83,6 +82,7 @@ export async function startCodexAttemptThread(params: {
buildAttemptParams: () => EmbeddedRunAttemptParams;
sessionAgentId: string;
effectiveWorkspace: string;
effectiveCwd: string;
dynamicTools: CodexDynamicToolSpec[];
developerInstructions: string | undefined;
finalConfigPatch?: Parameters<typeof startOrResumeThread>[0]["finalConfigPatch"];
@@ -100,7 +100,7 @@ export async function startCodexAttemptThread(params: {
}): Promise<StartCodexAttemptThreadResult> {
let pluginAppServer = params.appServer;
let releaseSharedClientLease: (() => void) | undefined;
let startupClientForCleanup: CodexAppServerClient | undefined;
let startupClientForAbandonedRequestCleanup: CodexAppServerClient | undefined;
let releaseStartupResourcesOnTimeout: (() => Promise<void>) | undefined;
try {
const startupResult = await withCodexStartupTimeout({
@@ -185,7 +185,7 @@ export async function startCodexAttemptThread(params: {
};
releaseSharedClientLease = startupClientLease;
attemptedClient = startupClient;
startupClientForCleanup = startupClient;
startupClientForAbandonedRequestCleanup = startupClient;
await ensureCodexComputerUse({
client: startupClient,
pluginConfig: params.pluginConfig,
@@ -239,7 +239,7 @@ export async function startCodexAttemptThread(params: {
params.nativeToolSurfaceEnabled,
);
const startupExecutionCwd = resolveCodexAppServerExecutionCwd({
effectiveWorkspace: params.effectiveWorkspace,
effectiveCwd: params.effectiveCwd,
environment: startupSandboxEnvironment,
nativeToolSurfaceEnabled: params.nativeToolSurfaceEnabled,
});
@@ -334,8 +334,8 @@ export async function startCodexAttemptThread(params: {
}
const failedClient = attemptedClient;
const clearedSharedClient = clearSharedCodexAppServerClientIfCurrent(failedClient);
if (startupClientForCleanup === failedClient) {
startupClientForCleanup = undefined;
if (startupClientForAbandonedRequestCleanup === failedClient) {
startupClientForAbandonedRequestCleanup = undefined;
}
attemptedClient = undefined;
if (attempt >= CODEX_APP_SERVER_STARTUP_CONNECTION_CLOSE_MAX_ATTEMPTS) {
@@ -365,7 +365,7 @@ export async function startCodexAttemptThread(params: {
throw new Error("codex app-server startup retry loop exited unexpectedly");
},
});
startupClientForCleanup = undefined;
startupClientForAbandonedRequestCleanup = undefined;
if (!releaseSharedClientLease) {
throw new Error("codex app-server startup succeeded without a shared client lease");
}
@@ -375,12 +375,38 @@ export async function startCodexAttemptThread(params: {
releaseSharedClientLease,
};
} catch (error) {
const transportPoisoned = isCodexAppServerConnectionClosedError(error);
const preserveSharedClientForSpawnedThreadStartError =
Boolean(params.spawnedBy?.trim()) && isCodexThreadStartRequestError(error);
if (transportPoisoned || !preserveSharedClientForSpawnedThreadStartError) {
clearSharedCodexAppServerClientIfCurrent(startupClientForCleanup);
if (
params.signal.aborted ||
shouldClearSharedClientAfterStartupRace(error) ||
shouldClearSharedClientAfterStartupFailure({
error,
spawnedBy: params.spawnedBy,
})
) {
clearSharedCodexAppServerClientIfCurrent(startupClientForAbandonedRequestCleanup);
}
throw error;
}
}
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 shouldClearSharedClientAfterStartupFailure(params: {
error: unknown;
spawnedBy: EmbeddedRunAttemptParams["spawnedBy"];
}): boolean {
if (!(params.error instanceof Error)) {
return !params.spawnedBy;
}
if (params.error.message.includes("write EPIPE")) {
return true;
}
return !params.spawnedBy;
}

View File

@@ -48,6 +48,7 @@ export type DynamicToolBuildParams = {
params: EmbeddedRunAttemptParams;
resolvedWorkspace: string;
effectiveWorkspace: string;
effectiveCwd?: string;
sandboxSessionKey: string;
sandbox: OpenClawSandboxContext;
nativeToolSurfaceEnabled?: boolean;
@@ -207,11 +208,15 @@ export async function buildDynamicTools(input: DynamicToolBuildParams) {
sessionId: params.sessionId,
runId: params.runId,
agentDir,
cwd: input.effectiveCwd ?? input.effectiveWorkspace,
workspaceDir: input.effectiveWorkspace,
spawnWorkspaceDir: resolveAttemptSpawnWorkspaceDir({
sandbox: input.sandbox,
resolvedWorkspace: input.resolvedWorkspace,
}),
spawnWorkspaceDir:
input.effectiveCwd && input.effectiveCwd !== input.effectiveWorkspace
? input.resolvedWorkspace
: resolveAttemptSpawnWorkspaceDir({
sandbox: input.sandbox,
resolvedWorkspace: input.resolvedWorkspace,
}),
config: params.config,
authProfileStore: params.toolAuthProfileStore ?? params.authProfileStore,
abortSignal: input.runAbortController.signal,
@@ -461,13 +466,13 @@ export function resolveCodexSandboxEnvironmentSelection(
}
export function resolveCodexAppServerExecutionCwd(params: {
effectiveWorkspace: string;
effectiveCwd: string;
environment?: CodexSandboxExecEnvironment;
nativeToolSurfaceEnabled: boolean;
}): string {
return params.environment && params.nativeToolSurfaceEnabled
? params.environment.cwd
: params.effectiveWorkspace;
: params.effectiveCwd;
}
export function resolveCodexExternalSandboxPolicyForOpenClawSandbox(

View File

@@ -1,4 +1,9 @@
import type { AgentToolResult } from "openclaw/plugin-sdk/agent-core";
import {
onInternalDiagnosticEvent,
waitForDiagnosticEventsDrained,
type DiagnosticEventPayload,
} from "openclaw/plugin-sdk/diagnostic-runtime";
import type { AnyAgentTool } from "openclaw/plugin-sdk/agent-harness";
import {
HEARTBEAT_RESPONSE_TOOL_NAME,
@@ -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,23 @@ 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

@@ -1,4 +1,5 @@
import type { AgentToolResult } from "openclaw/plugin-sdk/agent-core";
import { emitTrustedDiagnosticEvent } from "openclaw/plugin-sdk/diagnostic-runtime";
import {
createAgentToolResultMiddlewareRunner,
createCodexAppServerToolResultExtensionRunner,
@@ -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

@@ -448,9 +448,32 @@ export type CodexSkillsListParams = {
forceReload?: boolean;
};
export type CodexSkillScope = "user" | "repo" | "system" | "admin";
export type CodexSkillMetadata = {
name: string;
description: string;
shortDescription?: string;
interface?: JsonObject;
dependencies?: JsonObject;
path: string;
scope: CodexSkillScope;
enabled: boolean;
};
export type CodexSkillErrorInfo = {
path: string;
message: string;
};
export type CodexSkillsListEntry = {
cwd: string;
skills: CodexSkillMetadata[];
errors: CodexSkillErrorInfo[];
};
export type CodexSkillsListResponse = {
data: JsonValue[];
nextCursor?: string | null;
data: CodexSkillsListEntry[];
};
export type CodexHooksListParams = {

View File

@@ -209,6 +209,7 @@ async function buildDynamicToolsForTest(
params,
resolvedWorkspace: workspaceDir,
effectiveWorkspace: workspaceDir,
effectiveCwd: params.cwd ?? workspaceDir,
sandboxSessionKey,
sandbox: { enabled: false, backendId: "docker" } as never,
nativeToolSurfaceEnabled: true,
@@ -899,6 +900,46 @@ describe("runCodexAppServerAttempt", () => {
expect(binding.mcpServersFingerprint).toBe("mcp-v2");
});
it("uses task cwd for Codex app-server requests while keeping bootstrap workspace separate", async () => {
const sessionFile = path.join(tempDir, "session.jsonl");
const workspaceDir = path.join(tempDir, "workspace");
const taskCwd = path.join(tempDir, "task-repo");
await fs.mkdir(workspaceDir, { recursive: true });
await fs.mkdir(taskCwd, { recursive: true });
await fs.writeFile(path.join(workspaceDir, "SOUL.md"), "workspace bootstrap", "utf8");
await fs.writeFile(path.join(taskCwd, "task-marker.txt"), "task marker", "utf8");
const appServer = createThreadLifecycleAppServerOptions();
const params = createParams(sessionFile, workspaceDir);
const requests: Array<{ method: string; params: unknown }> = [];
await startOrResumeThread({
client: {
getServerVersion: () => "0.132.0",
request: async (method: string, requestParams?: unknown) => {
requests.push({ method, params: requestParams });
if (method === "thread/start") {
return threadStartResult();
}
return {};
},
} as never,
params,
cwd: taskCwd,
dynamicTools: [],
appServer,
developerInstructions: "workspace bootstrap",
});
const threadStart = requests.find((request) => request.method === "thread/start");
expect((threadStart?.params as { cwd?: string } | undefined)?.cwd).toBe(taskCwd);
const turnStart = buildTurnStartParams(params, {
threadId: "thread-1",
cwd: taskCwd,
appServer,
});
expect(turnStart.cwd).toBe(taskCwd);
});
it("starts a no-MCP Codex thread when MCP config is evaluated empty", async () => {
const sessionFile = path.join(tempDir, "session.jsonl");
const workspaceDir = path.join(tempDir, "workspace");
@@ -1581,7 +1622,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 }> }

View File

@@ -362,6 +362,13 @@ export async function runCodexAppServerAttempt(
? resolvedWorkspace
: sandbox.workspaceDir
: resolvedWorkspace;
const requestedCwd = params.cwd ? resolveUserPath(params.cwd) : undefined;
if (sandbox?.enabled && requestedCwd && requestedCwd !== resolvedWorkspace) {
throw new Error(
"cwd override is not supported for sandboxed Codex app-server runs; omit cwd or use the agent workspace as cwd",
);
}
const effectiveCwd = sandbox?.enabled ? effectiveWorkspace : (requestedCwd ?? effectiveWorkspace);
await ensureCodexWorkspaceDirOnce(effectiveWorkspace);
preDynamicStartupStages.mark("effective-workspace");
const appServer = resolveCodexAppServerForOpenClawToolPolicy({
@@ -518,6 +525,7 @@ export async function runCodexAppServerAttempt(
params,
resolvedWorkspace,
effectiveWorkspace,
effectiveCwd,
sandboxSessionKey,
sandbox,
nativeToolSurfaceEnabled,
@@ -534,6 +542,7 @@ export async function runCodexAppServerAttempt(
params,
resolvedWorkspace,
effectiveWorkspace,
effectiveCwd,
sandboxSessionKey,
sandbox,
nativeToolSurfaceEnabled,
@@ -597,6 +606,7 @@ export async function runCodexAppServerAttempt(
buildHarnessContextEngineRuntimeContext({
attempt: buildActiveRunAttemptParams(),
workspaceDir: effectiveWorkspace,
cwd: effectiveCwd,
agentDir,
activeAgentId: sessionAgentId,
contextEnginePluginId: activeContextEnginePluginId,
@@ -777,7 +787,7 @@ export async function runCodexAppServerAttempt(
});
const trajectoryRecorder = createCodexTrajectoryRecorder({
attempt: params,
cwd: effectiveWorkspace,
cwd: effectiveCwd,
developerInstructions: buildRenderedCodexDeveloperInstructions(),
prompt: codexTurnPromptText,
tools: toolBridge.availableSpecs,
@@ -795,7 +805,7 @@ export async function runCodexAppServerAttempt(
}
};
let codexEnvironmentSelection: CodexTurnEnvironmentParams[] | undefined;
let codexExecutionCwd = effectiveWorkspace;
let codexExecutionCwd = effectiveCwd;
let codexSandboxPolicy: CodexSandboxPolicy | undefined;
let restartContextEngineCodexThread:
| (() => Promise<CodexAppServerThreadLifecycleBinding>)
@@ -859,6 +869,7 @@ export async function runCodexAppServerAttempt(
buildAttemptParams: buildActiveRunAttemptParams,
sessionAgentId,
effectiveWorkspace,
effectiveCwd,
dynamicTools: toolBridge.specs,
developerInstructions: promptBuild.developerInstructions,
buildFinalConfigPatch: buildNativeHookRelayFinalConfigPatch,
@@ -902,7 +913,7 @@ export async function runCodexAppServerAttempt(
});
recordCodexTrajectoryContext(trajectoryRecorder, {
attempt: params,
cwd: effectiveWorkspace,
cwd: effectiveCwd,
developerInstructions: promptBuild.developerInstructions,
prompt: codexTurnPromptText,
tools: toolBridge.availableSpecs,
@@ -1846,6 +1857,7 @@ export async function runCodexAppServerAttempt(
agentId: sessionAgentId,
notifyUserMessagePersisted,
sessionKey: sandboxSessionKey,
cwd: effectiveCwd,
threadId: thread.threadId,
turnId: activeTurnId,
});
@@ -1970,6 +1982,7 @@ export async function runCodexAppServerAttempt(
notifyUserMessagePersisted,
result,
sessionKey: contextSessionKey,
cwd: effectiveCwd,
threadId: thread.threadId,
turnId: activeTurnId,
});
@@ -2010,6 +2023,7 @@ export async function runCodexAppServerAttempt(
runtimeContext: buildHarnessContextEngineRuntimeContextFromUsage({
attempt: buildActiveRunAttemptParams(),
workspaceDir: effectiveWorkspace,
cwd: effectiveCwd,
agentDir,
activeAgentId: sessionAgentId,
contextEnginePluginId: activeContextEnginePluginId,

View File

@@ -103,6 +103,7 @@ export async function mirrorTranscriptBestEffort(params: {
notifyUserMessagePersisted: (message: Extract<AgentMessage, { role: "user" }>) => void;
result: EmbeddedRunAttemptResult;
sessionKey?: string;
cwd: string;
threadId: string;
turnId: string;
}): Promise<void> {
@@ -116,6 +117,8 @@ export async function mirrorTranscriptBestEffort(params: {
sessionFile: params.params.sessionFile,
agentId: params.agentId,
sessionKey: params.sessionKey,
sessionId: params.params.sessionId,
cwd: params.cwd,
messages,
// Scope is thread-stable. Each entry in `messagesSnapshot` is tagged
// with a per-turn `attachCodexMirrorIdentity` value carrying its own
@@ -182,6 +185,7 @@ export async function mirrorPromptAtTurnStartBestEffort(params: {
agentId?: string;
notifyUserMessagePersisted: (message: Extract<AgentMessage, { role: "user" }>) => void;
sessionKey?: string;
cwd: string;
threadId: string;
turnId: string;
}): Promise<void> {
@@ -198,6 +202,8 @@ export async function mirrorPromptAtTurnStartBestEffort(params: {
sessionFile: params.params.sessionFile,
agentId: params.agentId,
sessionKey: params.sessionKey,
sessionId: params.params.sessionId,
cwd: params.cwd,
messages: [userPromptMessage],
idempotencyScope: `codex-app-server:${params.threadId}`,
config: params.params.config,
@@ -266,6 +272,8 @@ function buildMirrorDedupeIdentity(message: MirroredAgentMessage): string {
export async function mirrorCodexAppServerTranscript(params: {
sessionFile: string;
sessionId?: string;
cwd?: string;
sessionKey?: string;
agentId?: string;
messages: AgentMessage[];
@@ -326,6 +334,8 @@ export async function mirrorCodexAppServerTranscript(params: {
transcriptPath: params.sessionFile,
message: messageToAppend,
idempotencyLookup: idempotencyKey ? "caller-checked" : "scan",
sessionId: params.sessionId,
cwd: params.cwd,
config: params.config,
});
if (appendedMessage.role === "user") {

View File

@@ -57,7 +57,7 @@ export function formatCodexStatus(probes: CodexStatusProbes): string {
lines.push(
`Skills: ${
probes.skills.ok
? summarizeArrayLike(probes.skills.value)
? summarizeCodexSkills(probes.skills.value)
: formatCodexDisplayText(probes.skills.error)
}`,
);
@@ -199,6 +199,48 @@ export function formatList(response: JsonValue | undefined, label: string): stri
].join("\n");
}
export function formatSkills(response: JsonValue | undefined): string {
const groups = isJsonObject(response) && Array.isArray(response.data) ? response.data : [];
if (groups.length === 0) {
return "Codex skills: none returned.";
}
const lines = ["Codex skills:"];
let renderedSkills = 0;
let loadErrors = 0;
for (const group of groups) {
const record = isJsonObject(group) ? group : {};
if (Array.isArray(record.errors)) {
loadErrors += record.errors.length;
}
const skills = Array.isArray(record.skills) ? record.skills : [];
if (skills.length === 0) {
continue;
}
for (const skill of skills) {
if (isJsonObject(skill) && skill.enabled === false) {
continue;
}
lines.push(`- ${formatCodexSkillEntry(skill)}`);
renderedSkills += 1;
}
}
if (renderedSkills === 0) {
if (loadErrors > 0) {
return `Codex skills: none returned (${loadErrors} load ${
loadErrors === 1 ? "error" : "errors"
}).`;
}
return "Codex skills: none returned.";
}
return lines.join("\n");
}
function formatCodexSkillEntry(entry: JsonValue): string {
const record = isJsonObject(entry) ? entry : {};
const name = readString(record, "name") ?? "<unknown>";
return `\`${formatCodexDisplayText(name)}\``;
}
const CODEX_RESUME_SAFE_THREAD_ID_PATTERN = /^[A-Za-z0-9._:-]+$/;
function formatCodexResumeHint(threadId: string): string {
@@ -350,6 +392,36 @@ function summarizeArrayLike(value: JsonValue | undefined): string {
return `${entries.length}`;
}
function summarizeCodexSkills(value: JsonValue | undefined): string {
const groups = isJsonObject(value) && Array.isArray(value.data) ? value.data : [];
if (groups.length === 0) {
return "none returned";
}
let enabledSkills = 0;
let loadErrors = 0;
for (const group of groups) {
if (!isJsonObject(group)) {
continue;
}
if (Array.isArray(group.errors)) {
loadErrors += group.errors.length;
}
if (!Array.isArray(group.skills)) {
continue;
}
enabledSkills += group.skills.filter(
(skill) => !isJsonObject(skill) || skill.enabled !== false,
).length;
}
if (enabledSkills > 0) {
return `${enabledSkills}`;
}
if (loadErrors > 0) {
return `none returned (${loadErrors} load ${loadErrors === 1 ? "error" : "errors"})`;
}
return "none returned";
}
function formatCodexRateLimitSummary(value: JsonValue | undefined): string {
const summary = summarizeCodexRateLimits(value);
if (summary) {

View File

@@ -30,6 +30,7 @@ import {
formatCodexStatus,
formatList,
formatModels,
formatSkills,
formatThreads,
readString,
} from "./command-formatters.js";
@@ -380,9 +381,8 @@ export async function handleCodexSubcommand(
return { text: "Usage: /codex skills" };
}
return {
text: formatList(
text: formatSkills(
await deps.codexControlRequest(options.pluginConfig, CODEX_CONTROL_METHODS.listSkills, {}),
"Codex skills",
),
};
}
@@ -1932,7 +1932,8 @@ function parseCodexCliSessionsArgs(args: string[]): ParsedCodexCliSessionsArgs {
}
if (arg === "--limit") {
const value = readRequiredOptionValue(args, index);
const parsedLimit = value && /^\+?\d+$/.test(value.trim()) ? Number(value.trim()) : NaN;
const parsedLimit =
value && /^\+?\d+$/.test(value.trim()) ? Number(value.trim()) : Number.NaN;
if (!Number.isSafeInteger(parsedLimit) || parsedLimit <= 0) {
parsed.help = true;
continue;

View File

@@ -12,6 +12,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { CODEX_CONTROL_METHODS } from "./app-server/capabilities.js";
import type { CodexComputerUseStatus } from "./app-server/computer-use.js";
import type { CodexAppServerStartOptions } from "./app-server/config.js";
import type { JsonValue } from "./app-server/protocol.js";
import {
readRecentCodexRateLimits,
resetCodexRateLimitCacheForTests,
@@ -912,6 +913,62 @@ describe("codex command", () => {
expect(result.text).not.toContain("@here");
});
it("summarizes Codex status skill groups by enabled nested skills", async () => {
const deps = createDeps({
readCodexStatusProbes: vi.fn(async () => ({
models: { ok: true as const, value: { models: [] } },
account: { ok: true as const, value: {} },
limits: { ok: true as const, value: { rateLimits: null, rateLimitsByLimitId: null } },
mcps: { ok: true as const, value: { data: [] } },
skills: {
ok: true as const,
value: {
data: [
{
cwd: "/repo-a",
skills: [
{
name: "enabled-one",
description: "",
path: "/repo-a/.codex/skills/enabled-one/SKILL.md",
scope: "repo" as const,
enabled: true,
},
{
name: "disabled-one",
description: "",
path: "/repo-a/.codex/skills/disabled-one/SKILL.md",
scope: "repo" as const,
enabled: false,
},
],
errors: [],
},
{
cwd: "/repo-b",
skills: [
{
name: "enabled-two",
description: "",
path: "/repo-b/.codex/skills/enabled-two/SKILL.md",
scope: "repo" as const,
enabled: true,
},
],
errors: [{ path: "/repo-b/bad/SKILL.md", message: "bad skill" }],
},
],
},
},
})),
});
const result = await handleCodexCommand(createContext("status"), { deps });
expect(result.text).toContain("Skills: 2");
expect(result.text).not.toContain("Skills: 1");
});
it("summarizes generated Codex rate-limit payloads", async () => {
const limits = {
ok: true as const,
@@ -3085,19 +3142,120 @@ describe("codex command", () => {
const codexControlRequest = vi
.fn()
.mockResolvedValueOnce({ data: [{ name: "<@U123> [mcp](https://evil)" }] })
.mockResolvedValueOnce({ data: [{ id: "skill_1 @here" }] });
.mockResolvedValueOnce({
data: [
{
cwd: "/repo",
skills: [
{
name: "skill_1 @here",
description: "",
path: "/repo/.codex/skills/skill_1/SKILL.md",
scope: "repo",
enabled: true,
},
],
errors: [],
},
],
});
const deps = createDeps({ codexControlRequest });
const mcp = await handleCodexCommand(createContext("mcp"), { deps });
const skills = await handleCodexCommand(createContext("skills"), { deps });
expect(mcp.text).toContain("&lt;\uff20U123&gt; \uff3bmcp\uff3d\uff08https://evil\uff09");
expect(skills.text).toContain("skill\uff3f1 \uff20here");
expect(skills.text).toContain("- `skill\uff3f1 \uff20here`");
expect(`${mcp.text}\n${skills.text}`).not.toContain("<@U123>");
expect(`${mcp.text}\n${skills.text}`).not.toContain("[mcp](https://evil)");
expect(`${mcp.text}\n${skills.text}`).not.toContain("@here");
});
it("formats every Codex skill as a code-styled bullet and tolerates malformed entries", async () => {
const malformedSkillEntries: JsonValue[] = [
null,
{ description: "missing name" },
{
name: "final-skill",
description: "Final skill",
path: "/repo-b/.codex/skills/final-skill/SKILL.md",
scope: "repo",
enabled: true,
},
];
const codexControlRequest = vi.fn(async () => ({
data: [
{
cwd: "/repo-a",
skills: Array.from({ length: 26 }, (_, index) => ({
name: `skill-${index + 1}`,
description: `Skill ${index + 1}`,
path: `/repo-a/.codex/skills/skill-${index + 1}/SKILL.md`,
scope: "repo",
enabled: true,
})).concat({
name: "disabled-skill",
description: "Disabled skill",
path: "/repo-a/.codex/skills/disabled-skill/SKILL.md",
scope: "repo",
enabled: false,
}),
errors: [{ path: "/repo-a/bad/SKILL.md", message: "bad skill" }],
},
{
cwd: "/repo-b",
skills: malformedSkillEntries,
errors: [],
},
"malformed group",
],
}));
const deps = createDeps({ codexControlRequest });
const result = await handleCodexCommand(createContext("skills"), { deps });
expect(result.text).toContain("- `skill-1`");
expect(result.text).toContain("- `skill-26`");
expect(result.text).toContain("- `&lt;unknown&gt;`");
expect(result.text).toContain("- `final-skill`");
expect(result.text).not.toContain("Workspace:");
expect(result.text).not.toContain("Error:");
expect(result.text).not.toContain("More skills available");
expect(result.text).not.toContain("Skill 1");
expect(result.text).not.toContain("/repo-a/.codex/skills");
expect(result.text).not.toContain("disabled-skill");
});
it("reports Codex skill load errors when no skills render", async () => {
const codexControlRequest = vi.fn(async () => ({
data: [
{
cwd: "/repo-a",
skills: [
{
name: "disabled-skill",
description: "Disabled skill",
path: "/repo-a/.codex/skills/disabled-skill/SKILL.md",
scope: "repo",
enabled: false,
},
],
errors: [
{ path: "/repo-a/bad/SKILL.md", message: "bad skill <@U123>" },
{ path: "/repo-a/other/SKILL.md", message: "other bad skill @here" },
],
},
],
}));
const deps = createDeps({ codexControlRequest });
const result = await handleCodexCommand(createContext("skills"), { deps });
expect(result.text).toBe("Codex skills: none returned (2 load errors).");
expect(result.text).not.toContain("<@U123>");
expect(result.text).not.toContain("@here");
});
it("returns sanitized command failures instead of leaking app-server errors", async () => {
const sessionFile = path.join(tempDir, "session.jsonl");
await fs.writeFile(

View File

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

View File

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

View File

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

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