Compare commits

..

488 Commits

Author SHA1 Message Date
pashpashpash
ae03b79b73 Fail fast for removed Codex import auth choice 2026-04-22 19:12:21 -07:00
pashpashpash
d507d5a766 Preserve removed Codex import auth choice 2026-04-22 19:07:02 -07:00
pashpashpash
a7a3a5f37b Fix legacy update compat sidecars 2026-04-22 19:07:02 -07:00
pashpashpash
2476992301 Remove stale Codex import auth choice 2026-04-22 19:06:28 -07:00
pashpashpash
1151e7d40b Remove Codex CLI auth import 2026-04-22 19:06:28 -07:00
Peter Steinberger
51ed22e608 feat(providers): add streaming stt providers 2026-04-23 03:05:53 +01:00
Peter Steinberger
5b68092351 ci: pass gateway watch artifacts across runners 2026-04-23 03:04:22 +01:00
Peter Steinberger
c4242890f4 ci: reuse runtime artifacts for gateway watch 2026-04-23 03:01:08 +01:00
Peter Steinberger
74dfeaae0d fix(qa): preserve image generation plugin allowlist 2026-04-23 02:55:22 +01:00
Peter Steinberger
e3e2626583 fix: update generated protocol models 2026-04-23 02:49:50 +01:00
Peter Steinberger
c9ea10b184 ci: rotate ci concurrency key 2026-04-23 02:47:42 +01:00
Gustavo Madeira Santana
c992a8e5d8 Harden diagnostic stability bundle imports 2026-04-22 21:47:23 -04:00
Peter Steinberger
1489febee9 test: cover docker MCP cleanup for subagents 2026-04-23 02:46:13 +01:00
Peter Steinberger
ccf2e77e8d fix: retire one-shot subagent MCP runtimes 2026-04-23 02:46:13 +01:00
Peter Steinberger
dcff528805 ci: rebalance extension shards 2026-04-23 02:43:02 +01:00
Peter Steinberger
2e90a2247e fix: harden Slack stream fallback delivery (#70370) (thanks @mvanhorn) 2026-04-23 02:42:48 +01:00
Matt Van Horn
e55b932632 fix(slack): fall back to chat.postMessage when stream finalize fails pre-flush
Address adversarial review finding on #70295: the prior swallow-on-benign
fix silently dropped short replies to Slack Connect users. The SDK's
ChatStreamer buffers text locally until buffer_size (256 default), so
short replies never trigger chat.startStream via append(). streamer.stop()
then issues startStream internally; on Slack Connect recipients this
throws user_not_found. With the prior fix that error was swallowed and
the dispatcher marked the turn delivered - user saw 'done' reaction but
no message.

SlackStreamSession now tracks delivered (true once any Slack API call
returned a response) and pendingText (accumulation of every append +
final-stop text). stopSlackStream:
  - swallows the benign code when delivered=true (prior append flushed;
    text is visible; same behavior as before)
  - throws a new SlackStreamNotDeliveredError carrying pendingText when
    delivered=false (nothing reached Slack)

dispatch.ts catches SlackStreamNotDeliveredError and posts pendingText
via a rename-bound chat.postMessage (to dodge the unicorn lint rule),
and flips streamFallbackDelivered so anyReplyDelivered stays correct.

Fixes #70295
2026-04-23 02:42:48 +01:00
Matt Van Horn
676ed34cbd fix(slack): treat Slack Connect finalize errors as benign in stopSlackStream
When Slack's chat.stopStream fails with user_not_found (Slack Connect DM
recipients), team_not_found (cross-workspace shared channels), or
missing_recipient_user_id (DM closed mid-stream), the text already
delivered via append() is still visible to the user. Swallow those
specific codes and mark the session stopped rather than surfacing a
spurious 'slack-stream: failed to stop stream' error in dispatch. Other
Slack API errors still propagate.

Fixes #70295
2026-04-23 02:42:48 +01:00
Peter Steinberger
688fc288af ci: trim duplicate android apk build 2026-04-23 02:38:01 +01:00
Peter Steinberger
5461195035 docs: document session mailbox discovery (#69839) 2026-04-23 02:33:55 +01:00
Peter Steinberger
b53bce9f47 fix(agents): filter session previews after visibility 2026-04-23 02:33:55 +01:00
dangoZhang
13882581b6 fix(agents): clean up sessions_list forwarding 2026-04-23 02:33:55 +01:00
dangoZhang
1a4c32e366 feat: expose mailbox session discovery in sessions_list 2026-04-23 02:33:55 +01:00
Peter Steinberger
dcc243c889 test: stabilize loopback port release check 2026-04-23 02:25:53 +01:00
Peter Steinberger
4ff720a837 fix(openai): harden realtime stt 2026-04-23 02:22:17 +01:00
Peter Steinberger
26bf916382 fix(gateway): resolve dynamic models during warmup 2026-04-23 02:20:11 +01:00
Peter Steinberger
1cbd5a9470 fix(codex): harden app-server approvals 2026-04-23 02:20:10 +01:00
Peter Steinberger
de95e414d1 style: format stale source files 2026-04-23 02:20:10 +01:00
Peter Steinberger
0ada97d513 fix: restore legacy update compat sidecars 2026-04-23 02:19:19 +01:00
Peter Steinberger
0f77fcac31 test: improve xai realtime stt live coverage 2026-04-23 02:06:07 +01:00
Peter Steinberger
6a1d6b7d89 ci: run docker smoke for scope changes 2026-04-23 01:58:58 +01:00
Peter Steinberger
b5cc7ea879 ci: expand docker smoke changed scope 2026-04-23 01:57:25 +01:00
Peter Steinberger
71ae0d737a fix: override vulnerable uuid dependency 2026-04-23 01:56:14 +01:00
dulingxiao
c4dea58712 fix(moonshot): preserve native Kimi tool_call IDs in openai-completions replay 2026-04-23 01:52:58 +01:00
Peter Steinberger
23a448986f fix(xai): declare websocket runtime dependency 2026-04-23 01:50:00 +01:00
Gustavo Madeira Santana
28818f9140 Improve gateway diagnostics export for support reports (#70324)
Merged via squash.

Prepared head SHA: 3d6ee85993
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-04-22 20:47:14 -04:00
Peter Steinberger
6b41ef311f fix: isolate external direct-message runtime policy 2026-04-23 01:39:56 +01:00
Peter Steinberger
67f09ea87a feat: add xai realtime transcription 2026-04-23 01:38:11 +01:00
Peter Steinberger
d4c171f594 ci: keep extension batch parallelism at two 2026-04-23 01:35:30 +01:00
Peter Steinberger
53f388fa83 docs(plugins): document npm update behavior 2026-04-23 01:29:32 +01:00
Peter Steinberger
67850c4fc8 ci: run extension batches three-wide 2026-04-23 01:29:20 +01:00
Peter Steinberger
87a64a33f1 fix(plugins): clarify installed plugin replacement 2026-04-23 01:25:29 +01:00
Peter Steinberger
fa43cbfcba fix: drop invalid Codex app-server service tiers 2026-04-23 01:24:25 +01:00
Peter Steinberger
9f358456db ci: skip duplicate extension fast on main 2026-04-23 01:23:23 +01:00
Peter Steinberger
0946e37523 fix(plugins): skip unchanged npm updates 2026-04-23 01:23:03 +01:00
Peter Steinberger
bf132d6fb9 test(qa-matrix): stabilize sync timeout cursor 2026-04-23 01:21:52 +01:00
Peter Steinberger
f72c97afca test(qa-matrix): stabilize sync timeout 2026-04-23 01:20:45 +01:00
Peter Steinberger
7724f7a923 test(opencode-go): lock pi catalog coverage 2026-04-23 01:17:13 +01:00
Peter Steinberger
d6eac07b06 ci: add fast bundled docker e2e 2026-04-23 01:09:35 +01:00
Peter Steinberger
012841816d feat: add xai speech-to-text support 2026-04-23 01:06:07 +01:00
Peter Steinberger
2bec189174 test(zalo): trim lifecycle reset imports 2026-04-23 01:02:57 +01:00
Peter Steinberger
4177b27e24 docs: note codex dynamic tool fingerprint fix (#69976) 2026-04-23 01:01:33 +01:00
chen-zhang-cs-code
5210b20523 fix(codex): ignore tool descriptions in thread fingerprint 2026-04-23 01:01:33 +01:00
Peter Steinberger
38c76b34f4 test(agents): stabilize context lookup warmup 2026-04-23 00:58:13 +01:00
Peter Steinberger
3d07eadec3 fix: restore model-level base url contract (#70340) 2026-04-23 00:52:32 +01:00
Peter Steinberger
dbab0f7aad fix: restore codex permission approval targets (#70340) (thanks @Lucenx9) 2026-04-23 00:52:32 +01:00
Lucenx9
08a81740ae fix(codex): restore sanitized permission approval detail 2026-04-23 00:52:32 +01:00
Lucenx9
dc13cd68ed fix(codex): clarify permission approvals 2026-04-23 00:52:32 +01:00
Peter Steinberger
5a5aa3a178 fix(config): tolerate missing channel metadata during auto-enable 2026-04-23 00:50:34 +01:00
Peter Steinberger
53e822f407 fix: keep cli reply runs streaming 2026-04-23 00:49:43 +01:00
Peter Steinberger
c4e5ca8625 fix(agents): expose configured MCP tools in Pi profiles 2026-04-23 00:47:37 +01:00
Peter Steinberger
bba63d4e78 test(codex): await event projector setup 2026-04-23 00:46:04 +01:00
Peter Steinberger
f437d96ae2 fix(config): avoid false reload restarts 2026-04-23 00:44:54 +01:00
Peter Steinberger
c65b232463 fix(amazon-bedrock-mantle): align runtime deps 2026-04-23 00:43:12 +01:00
Peter Steinberger
d50181e209 test(docker): speed bundled dependency e2e 2026-04-23 00:35:17 +01:00
pashpashpash
ff02563c7c feat(codex): add guardian app-server mode (#70090)
Reworks the Codex app-server Guardian change into the final landing shape:

- keep YOLO as the default local app-server mode
- add explicit `appServer.mode: "guardian"`
- remove the legacy `OPENCLAW_CODEX_APP_SERVER_GUARDIAN` shortcut
- document Guardian configuration and behavior
- add Guardian event projection and Docker live probes for approved/ask-back decisions

Co-authored-by: pashpashpash <nik@vault77.ai>
2026-04-23 00:25:43 +01:00
Vincent Koc
34e45ecfcc feat(codex): add llm lifecycle hooks (#70312)
* feat(codex): add llm lifecycle hooks

* fix(codex): close llm hook lifecycle gaps

* fix(codex): dedupe llm hook context

* fix(codex): preserve abort and error hook state
2026-04-22 16:19:59 -07:00
Vincent Koc
a5128777ee feat(codex): add tool hook parity (#70307)
* feat(codex): add tool hook parity

* fix(codex): stabilize tool hook parity

* fix(codex): tighten transcript hook typing

* fix(codex): preserve mirrored transcript idempotency

* fix(codex): normalize tool hook context
2026-04-22 16:18:10 -07:00
Peter Steinberger
da9700903c ci: skip no-op changed-scope fanout 2026-04-23 00:16:01 +01:00
Vincent Koc
44965bf63c fix(diffs): refresh live tool config 2026-04-22 16:14:23 -07:00
Peter Steinberger
1019b663ce chore: format extension runtime deps 2026-04-23 00:12:47 +01:00
Vincent Koc
d686e6f876 fix(hooks): avoid stale active-memory startup fallback 2026-04-22 16:10:01 -07:00
wirjo
18507ed85f feat(amazon-bedrock-mantle): add Claude Opus 4.7 via per-model Anthropic Messages API override (#68730)
* feat(amazon-bedrock-mantle): add Claude Opus 4.7 via Anthropic auth

* fix(amazon-bedrock-mantle): keep Opus 4.7 transport-safe

* fix(amazon-bedrock-mantle): restore anthropic base url helper

* fix(auto-reply): apply runtime auth to conversation labels

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-04-22 16:09:39 -07:00
KateWilkins
f342da5fcc feat: add xai media providers
Add xAI image generation and text-to-speech provider support with docs, live tests, and guarded provider HTTP handling.\n\nThanks @KateWilkins.
2026-04-23 00:07:39 +01:00
Vincent Koc
386a0884d7 fix(hooks): avoid stale lancedb startup fallback 2026-04-22 16:06:55 -07:00
Peter Steinberger
ed0ffa472b docs: clarify codex compaction docs (#69612) (thanks @91wan) 2026-04-23 00:05:47 +01:00
91wan
dee8150bab docs(codex): narrow compaction claims 2026-04-23 00:05:47 +01:00
Peter Steinberger
bee491f439 test(telegram): cover debounce topic keys at seam 2026-04-22 23:57:08 +01:00
Peter Steinberger
9b1f1036ac fix(channels): isolate bundled load failures 2026-04-22 23:56:14 +01:00
Vincent Koc
e8b56a9928 feat(codex): add prompt and compaction hooks (#70313)
* feat(codex): add prompt and compaction hooks

* fix(codex): clean prompt and compaction hook tests
2026-04-22 15:56:08 -07:00
Peter Steinberger
ac8495adaa fix(config): write through single-file includes 2026-04-22 23:53:56 +01:00
wirjo
2a15a3bb53 fix(amazon-bedrock): add known model context windows to discovery (#65952)
* fix(amazon-bedrock): add known model context windows to discovery

Bedrock's ListFoundationModels API does not expose token limits. Discovery
was hardcoding contextWindow: 32000 for every model, causing Claude (1M),
Nova (300K), and other models to hit premature 'Context limit exceeded'
errors and unnecessary session resets.

Adds a lookup table of known context windows for Bedrock models:
- Anthropic Claude: 200K-1M
- Amazon Nova: 128K-1M
- Meta Llama: 128K
- Mistral: 32K-128K
- DeepSeek: 128K
- Cohere: 128K
- AI21 Jamba: 256K

Inference profile prefixes (us., eu., ap., global.) are stripped before
lookup, so us.anthropic.claude-opus-4-6-v1 correctly resolves to 1M.

Also raises the default fallback from 32K to 128K for unknown models —
most modern models have at least 128K context.

Single file change, no type system modifications.

Complementary to #65030 (provenance flag for warning on unknown models).

Fixes #64919
Related: #64250

* add KNOWN_MAX_TOKENS map and expand model coverage

- Add KNOWN_MAX_TOKENS lookup table with Bedrock-optimized values that
  balance response quality against quota burndown (5x rate for Claude 3.7+)
- Add missing models to KNOWN_CONTEXT_WINDOWS: Opus 4.7 (1M), Opus 4.1/4.5,
  Sonnet 4, Claude 3/3.5 Haiku, DeepSeek V3/V3.2, Google Gemma 3
- Refactor prefix-stripping into shared resolveKnownValue() helper
- Fix: use !== undefined instead of truthy check for table lookups
- Wire resolveKnownMaxTokens into toModelDefinition and resolveInferenceProfiles

Quota burndown context: Bedrock reserves input_tokens + max_tokens from
TPM at request start. For Claude 3.7+, output burns at 5x. The values
in KNOWN_MAX_TOKENS are intentionally conservative (8-16K for Claude)
to maximize concurrent throughput while still allowing useful responses.
Thinking budget is added separately by the runtime.

* remove KNOWN_MAX_TOKENS — maxTokens should be handled upstream

Remove the KNOWN_MAX_TOKENS map. Hardcoding maxTokens values in
discovery is the wrong layer to solve this — any explicit value
still gets reserved against Bedrock's TPM quota at request start.

The correct fix is upstream in pi's Bedrock provider: omit maxTokens
from inferenceConfig when not explicitly set, letting the model use
its internal default. This avoids quota waste entirely.

See: badlogic/pi-mono#3399 and badlogic/pi-mono#3400

Keep the expanded KNOWN_CONTEXT_WINDOWS (context windows ARE the
right thing to set in discovery — they affect compaction thresholds
and session management, not API-level quota reservation).

* docs: clarify why hardcoded context windows are needed

Bedrock's ListFoundationModels and GetFoundationModel APIs return no
token limit information — there is no Bedrock API to discover context
windows or max output tokens programmatically. Note that this table
should become a fallback if AWS adds token metadata in the future.

* fix: add au and apac to inference profile prefix regex

Add missing geo prefixes discovered by querying inference profiles
across multiple regions:
- au. (Australia/NZ, used in ap-southeast-2/4/6)
- apac. (Asia-Pacific, used for older models in ap-northeast-1)

Both resolveKnownContextWindow and resolveBaseModelId now handle
all known prefixes: us, eu, ap, apac, au, jp, global.

* test: port au. prefix test from #65449 by @alickgithub2, add apac. coverage

Port the Australia/NZ inference profile test from PR #65449
(credit: @alickgithub2) and extend it to also cover the apac.
prefix discovered in ap-northeast-1.

* expand model coverage: Llama 4, MiniMax, NVIDIA, Mistral 3, GLM, Qwen

Cross-referenced KNOWN_CONTEXT_WINDOWS against live
list-foundation-models API. Added missing models:
- Llama 4 Maverick (1M) and Scout (512K)
- MiniMax M2/M2.1/M2.5 (1M)
- NVIDIA Nemotron Super/Nano variants (128K)
- Mistral Large 3 675B (128K)
- GLM 4.7/4.7-flash/5 (128K)
- Qwen3 Coder/32B/VL (128-256K)

Removed deprecated deepseek.v3-v1:0 and claude-opus-4-20250514
(not in active foundation models list).

* raise default context window from 128K to 200K

200K matches the floor for all current Claude models (the most
popular on Bedrock). Every other active model with a lower actual
limit is already in the explicit table. This ensures new Claude
models get a correct default without requiring a table update.

* test: update discovery test expectations for known context window values

* test: fix remaining contextWindow expectation (default 200K)

* fix(amazon-bedrock): keep conservative context fallback

* docs(changelog): note Bedrock context window fix

* fix(amazon-bedrock): normalize known context fallback

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-04-22 15:53:41 -07:00
wirjo
420c96e7aa fix(amazon-bedrock-mantle): refresh IAM bearer token via resolveConfigApiKey cache lookup (#68903)
* fix(amazon-bedrock-mantle): refresh IAM bearer token via resolveConfigApiKey cache lookup

The Mantle plugin generates a bearer token from IAM credentials at discovery
time and bakes it as a static string into the provider config. After the
token's cache TTL expires (~1hr), requests fail because resolveConfigApiKey
only handled the explicit AWS_BEARER_TOKEN_BEDROCK env var case.

Fix: expose getCachedIamToken() as a sync read from the existing iamTokenCache,
and wire it into resolveConfigApiKey as a fallback when no explicit env var is
set. The catalog.run still generates/refreshes the token on discovery; this
change ensures the cached token is served at auth resolution time.

Fixes #68900

* fix(amazon-bedrock-mantle): refresh runtime IAM bearer auth

* docs(changelog): note Mantle IAM refresh

* fix(agents): apply runtime auth in simple completion

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-04-22 15:52:24 -07:00
Devin Robison
2321d67263 fix(gateway): require auth for control ui bootstrap config (#70247)
* fix(gateway): require auth for control ui bootstrap config

* fix(ui): send auth on bootstrap fetch

* fix(ui): keep bootstrap auth same-origin

* fix(ui): refresh bootstrap after auth hello

* docs(changelog): note control ui bootstrap auth

* fix(ui): retry bootstrap auth with alternate shared secret on 401
2026-04-22 16:52:08 -06:00
Peter Steinberger
c87c9742ed fix(telegram): isolate direct chat sandbox sessions 2026-04-22 23:46:34 +01:00
Peter Steinberger
46fba1d814 docs(config): clarify symlinked config support 2026-04-22 23:45:03 +01:00
Devin Robison
95119017c8 fix(openshell): pin sandbox file reads (#69798)
* fix(openshell): pin sandbox file reads against parent symlink swaps

* docs(changelog): note openshell sandbox read pinning (#69798)

* fix(openshell): containment-check against literal root and self-contain file-identity helper

* test(openshell): spy on fsPromises.open for swap races, skip dev=0 test on win32

* fix(openshell): single-syscall fallback identity check + tighten sameFileIdentity types

* fix(openshell): re-fstat pinned handle after identity check for defense-in-depth

* fix(openshell): lstat leaf on platforms without O_NOFOLLOW to close windows symlink gap

* fix(openshell): expose test seam for O_NOFOLLOW availability instead of patching native constants
2026-04-22 16:44:25 -06:00
Val Alexander
12bbb371d0 feat(control-ui): personalize local user identity and tighten layouts
## Summary
- add browser-local operator identity in Control UI and route user name/avatar rendering through the shared chat/avatar path used by assistant and agent surfaces
- tighten Quick Settings, fallback chip, and mobile chat layout behavior so the personalized UI uses space better and avoids clipped controls
- guard oversized local avatar uploads before FileReader allocation, restore the fallback-chip keyboard focus ring, and add the changelog note for the user-visible Control UI work

## Testing
- pnpm test ui/src/ui/views/config-quick.test.ts ui/src/styles/components.test.ts
- pnpm check:changed
2026-04-22 17:38:58 -05:00
Peter Steinberger
5daa104e63 docs: note codex approval hardening (#70356) (thanks @Lucenx9) 2026-04-22 23:38:44 +01:00
Lucenx9
ec5015924c fix(codex): fail closed for unknown approvals 2026-04-22 23:38:44 +01:00
Peter Steinberger
4285958bcd test(codex): cover websocket token rotation (#70328) (thanks @Lucenx9) 2026-04-22 23:37:58 +01:00
Lucenx9
15f285c0cb fix(codex): scope stale shared-client cleanup 2026-04-22 23:37:58 +01:00
Lucenx9
0bc5ccc706 fix(codex): rotate shared app-server clients on auth changes 2026-04-22 23:37:58 +01:00
Peter Steinberger
f4c4e940a6 test(qa): stabilize lab catalog abort fixture 2026-04-22 23:36:34 +01:00
Peter Steinberger
2cd3164a0f feat(providers): share GPT-5 prompt overlay 2026-04-22 23:36:06 +01:00
Peter Steinberger
7b2c9a6fa3 fix(config): recover critical config clobbers 2026-04-22 23:35:48 +01:00
Peter Steinberger
1d7be63228 ci: rebalance extension test shards 2026-04-22 23:29:34 +01:00
Peter Steinberger
22814c1add docs(config): document safe model config merges 2026-04-22 23:23:54 +01:00
Peter Steinberger
f7e668d0ec chore: record extension runtime deps 2026-04-22 23:19:20 +01:00
Peter Steinberger
c2ac1e3ef4 feat: expose OpenClaw tools to ACPX 2026-04-22 23:19:20 +01:00
Peter Steinberger
87f8e82347 fix: isolate Codex ACP auth 2026-04-22 23:18:56 +01:00
Peter Steinberger
819ff0463a fix(config): protect model config merges 2026-04-22 23:18:05 +01:00
Peter Steinberger
f88da75ed9 refactor(channels): centralize runtime binding routes 2026-04-22 23:16:57 +01:00
Peter Steinberger
85d2a9ec1f test(cron): add docker mcp cleanup e2e 2026-04-22 23:12:18 +01:00
Peter Steinberger
816d7a7232 chore(extensions): update runtime dependency manifests 2026-04-22 23:11:43 +01:00
Devin Robison
b76edc09e6 fix(gateway): reauthorize session history SSE updates (#70237)
* fix(gateway): reauthorize session history SSE updates

* docs(changelog): note session history sse reauth

* fix(gateway): use live proxy config for sse reauth

* fix(gateway): skip unrelated session sse reauth

* fix(gateway): filter sse transcript updates early, log work failures, forward-declare cleanup bindings
2026-04-22 16:11:32 -06:00
Peter Steinberger
698f154c28 fix(qa): recheck Matrix sync events after poll 2026-04-22 23:11:27 +01:00
Peter Steinberger
a32a6c2f89 fix: stop generating qa npm sidecars 2026-04-22 23:11:01 +01:00
Peter Steinberger
f66098f8f6 test(github-copilot): add live Responses ID rewrite probe 2026-04-22 23:09:31 +01:00
Peter Steinberger
03c1fff8f6 test(qa): add OpenAI native web search live scenario 2026-04-22 23:06:55 +01:00
Peter Steinberger
1a90893e90 test: keep extension directory filters covered 2026-04-22 23:06:26 +01:00
Val Alexander
eb689f3535 fix(ui): shorten Control UI clear action label (#70355) 2026-04-22 16:52:53 -05:00
Peter Steinberger
e56a6f87ec fix: exclude qa extensions from npm package 2026-04-22 22:48:28 +01:00
Peter Steinberger
ebe32e5cee feat(openai): enable native web search 2026-04-22 22:47:26 +01:00
Peter Steinberger
276d222283 build(deps): bump fast-xml-parser override 2026-04-22 22:45:57 +01:00
wirjo
c7e5289fd2 fix: propagate AWS SDK auth sentinel for IMDS/instance role Bedrock auth (#68964)
* fix: propagate AWS SDK auth sentinel for IMDS/instance role Bedrock auth

When Bedrock auth resolves via AWS SDK default credential chain (IMDS,
ECS task role) with no explicit API key, the auth controller returned
early without calling setRuntimeApiKey(). This left pi's authStorage
unaware that the provider is authenticated, causing 'No API key found
for amazon-bedrock' errors.

Now, when mode is 'aws-sdk' and no explicit API key is available:
1. Try prepareProviderRuntimeAuth to resolve runtime credentials
2. If that returns a real apiKey, use it with auth refresh scheduling
3. Otherwise inject a '__aws_sdk_auth__' sentinel so pi's
   hasConfiguredAuth() passes and the AWS SDK handles request signing

This is a focused fix in auth-controller.ts only, avoiding the risky
model-auth-runtime-shared.ts changes that could re-introduce the
fake-apiKey injection pattern on ECS (see prior regressions #49891,
#50699, #54274).

Fixes #62995

* fix(pi-auth): clean up aws-sdk sentinel fallback

* docs(changelog): note aws-sdk Bedrock auth fix

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-04-22 14:45:26 -07:00
Peter Steinberger
207d7303b7 test: avoid argv warmup race 2026-04-22 22:42:45 +01:00
Peter Steinberger
2e13f224d6 fix(openai-responses): normalize Copilot response item IDs (#69362) (thanks @Menci) 2026-04-22 22:40:43 +01:00
Vincent Koc
4f9169c6dd fix(hooks): avoid stale skill workshop startup fallback 2026-04-22 14:39:58 -07:00
Peter Steinberger
7f637eafe2 test: run single-channel extension batches 2026-04-22 22:39:17 +01:00
Peter Steinberger
d1e06407bf chore: add extension runtime dependency manifests 2026-04-22 22:36:40 +01:00
Peter Steinberger
6ab3751287 fix: preserve Azure OpenAI completions api version 2026-04-22 22:36:40 +01:00
Peter Steinberger
cb16d22780 fix(cron): retire bundled mcp runtimes 2026-04-22 22:30:47 +01:00
Peter Steinberger
1dc5aad316 test: align matrix acp room binding expectations 2026-04-22 22:30:22 +01:00
Peter Steinberger
8a3e130db8 fix(slack): honor focused thread bindings 2026-04-22 22:29:48 +01:00
Peter Steinberger
cc1e843c90 docs(changelog): note config prefix recovery 2026-04-22 22:29:01 +01:00
Peter Steinberger
5d50b0c48f fix(config): recover prefixed config JSON 2026-04-22 22:29:01 +01:00
Peter Steinberger
77dbc1cda6 ci: rebalance test workers 2026-04-22 22:26:02 +01:00
Vincent Koc
65ae1e54de fix(hooks): avoid stale thread ownership startup fallback 2026-04-22 14:19:13 -07:00
Peter Steinberger
50c95d1d21 refactor(channels): centralize conversation resolution 2026-04-22 22:16:08 +01:00
Vincent Koc
f1372681a8 fix(diffs): refresh live viewer access policy 2026-04-22 14:14:30 -07:00
Peter Steinberger
0588dfe15d fix(config): parse quoted bracket paths 2026-04-22 22:11:45 +01:00
Peter Steinberger
a971884104 test(mcp): strengthen stdio lifecycle coverage 2026-04-22 22:11:30 +01:00
Peter Steinberger
56828545b4 ci: parallelize agents test files 2026-04-22 22:09:25 +01:00
Peter Steinberger
a1319aaadd fix(update): skip package no-op installs 2026-04-22 22:05:29 +01:00
Peter Steinberger
64fb6f71b4 fix(gateway): recover invalid config before startup 2026-04-22 22:05:29 +01:00
Peter Steinberger
f70a46b703 fix(config): preserve authored config writes 2026-04-22 22:05:29 +01:00
Peter Steinberger
5f7b44045d fix(mcp): tear down stdio process trees 2026-04-22 22:04:22 +01:00
Peter Steinberger
2c45879120 fix(config): render warning newlines 2026-04-22 22:04:09 +01:00
Peter Steinberger
b6fbf46eca fix(cron): repair malformed cron job ids via doctor 2026-04-22 22:03:58 +01:00
Peter Steinberger
2e38e09b04 test: harden parallels smoke harness 2026-04-22 22:01:04 +01:00
Peter Steinberger
054fda206e ci: rotate stuck build-smoke queue 2026-04-22 21:59:48 +01:00
Vincent Koc
0f0d399c71 fix(hooks): stop memory-core runtime config fallback 2026-04-22 13:57:10 -07:00
Peter Steinberger
4cb4aad7b1 build: harden tsdown wrapper 2026-04-22 21:54:56 +01:00
Vincent Koc
d25ff59c8b docs(changelog): note pi session tool activation fix 2026-04-22 13:54:04 -07:00
Vincent Koc
fc07b23437 fix(agents): restore pi session tool activation 2026-04-22 13:54:04 -07:00
Vincent Koc
42400813a7 test(plugins): pin live config hook guards 2026-04-22 13:50:51 -07:00
Peter Steinberger
aad1be102d fix(types): narrow live thread ownership config 2026-04-22 21:48:59 +01:00
Peter Steinberger
b648830632 fix: clarify browser playwright-core install guidance 2026-04-22 21:47:58 +01:00
Vincent Koc
99c1bc2cce docs(changelog): note websocket endpoint classifier fix 2026-04-22 13:44:51 -07:00
Vincent Koc
e250ea3668 fix(agents): centralize native websocket endpoint checks 2026-04-22 13:44:51 -07:00
Vincent Koc
4c675216f1 fix(qa): deflake parity approval preflight 2026-04-22 13:43:29 -07:00
Vincent Koc
db5895fd2a refactor(hooks): centralize live plugin config lookup 2026-04-22 13:38:38 -07:00
Peter Steinberger
ee63b9ee49 fix(memory-lancedb): retry failed runtime initialization 2026-04-22 21:20:28 +01:00
Vincent Koc
eae0039aa4 fix(hooks): use live memory-core config during dreaming runs 2026-04-22 13:10:19 -07:00
Peter Steinberger
c4aeeb2762 test(slack): provide send config in identity fallback tests 2026-04-22 21:09:42 +01:00
Zetarcos
38001cdeaa fix(discord): normalize ACP thread binding targets
Normalize Discord ACP thread-binding channel targets at the REST/thread-create boundary while preserving current-conversation binding keys.\n\nThanks @Zetarcos.
2026-04-22 21:09:26 +01:00
martingarramon
238b31a00c test(slack): cover send.ts customize-scope fallback retry path (#69009)
Adds 5 vitest cases for postSlackMessageBestEffort's silent retry
behavior when Slack rejects a chat:write.customize-identity post:

- Retry on err.data.needed matching chat:write.customize
- Retry on chat:write.customize in response_metadata.acceptedScopes
- Retry on chat:write.customize in response_metadata.scopes
- Rethrow on different missing_scope (e.g. channels:history)
- Rethrow when identity is empty (hasCustomIdentity returns false)
2026-04-22 16:06:44 -04:00
Vincent Koc
bc4a097464 fix(hooks): respect live lancedb memory config 2026-04-22 13:06:02 -07:00
Peter Steinberger
3704e3f580 ci: keep extension test fanout under two minutes 2026-04-22 21:06:00 +01:00
Peter Steinberger
6639b21ade test(media): harden media store URI validation 2026-04-22 21:05:41 +01:00
Devin Matthews
5528793adf fix: honor explicit strict-agentic retry contract
Honor explicit strict-agentic execution contracts for incomplete-turn retry guards across providers, including local/compatible models that opt in without relying on OpenAI model inference.

Validation:
- pnpm test src/agents/pi-embedded-runner/run.incomplete-turn.test.ts
- pnpm check:changed
- GitHub CI + parity gate green

Thanks @ziomancer.
2026-04-22 21:03:03 +01:00
Peter Steinberger
c0cafb6bbe perf(plugins): cache normalized jiti aliases 2026-04-22 21:02:29 +01:00
Vincent Koc
834e50f83c fix(hooks): use live thread ownership config 2026-04-22 13:01:32 -07:00
Vincent Koc
fbf554397f fix(hooks): respect live skill workshop config 2026-04-22 12:59:27 -07:00
Val Alexander
9ea5484fa1 fix: normalize opus 4.7 context window
Normalize Anthropic-owned Opus 4.7 context reporting to 1M while keeping inferred and bare discovery paths conservative.

- normalize Anthropic and claude-cli Opus 4.7 runtime/status context metadata to 1M
- keep inferred-provider and bare discovery ids on discovered conservative limits
- add regression coverage for provider, lookup, status, and discovery-cache paths
- keep the Telegram abort-signal wrapper typing narrow so changed-scope validation stays green
2026-04-22 14:58:16 -05:00
Peter Steinberger
c542d42f6f ci: balance extension tests across fewer workers 2026-04-22 20:55:38 +01:00
Vincent Koc
dd47b56243 fix(hooks): refresh active memory config at runtime 2026-04-22 12:55:12 -07:00
Peter Steinberger
f9cbaae19e ci: rotate cancelled docs queue 2026-04-22 20:51:48 +01:00
Josh Lehman
ccc99d85bf fix: restore Pi embedded tool allowlist
Restore the Pi embedded session tool allowlist for OpenAI/OpenAI Codex GPT-5 runs and compaction sessions after Pi 0.68.1 began treating session tools as a global allowlist.

Local validation: pnpm check:changed.
GitHub validation: check/check-additional/node shards green; parity gate red on unrelated config.patch stale/rate-limit QA harness scenario after plugins.allow restart.
2026-04-22 20:51:42 +01:00
Tak Hoffman
78d491d909 feat(commands): gate /models add with modelsWrite (#70321) 2026-04-22 14:49:07 -05:00
Vincent Koc
1ebd8e0bb6 fix(hooks): use live config for memory dreaming runtime 2026-04-22 12:47:57 -07:00
Peter Steinberger
6261f42ac0 ci: merge short auto-reply node shards 2026-04-22 20:47:49 +01:00
Vincent Koc
1e33d63f64 test(memory): pin disabled lifecycle hook wiring 2026-04-22 12:43:23 -07:00
Peter Steinberger
f97c6f8a04 fix(discord): harden partial thread channels 2026-04-22 20:41:50 +01:00
Vincent Koc
e71da6705b fix(hooks): skip skill workshop capture when review is off 2026-04-22 12:41:04 -07:00
Peter Steinberger
8fcca8a5e1 ci: rotate main concurrency queue 2026-04-22 20:39:49 +01:00
Vincent Koc
1704dceca2 test(skill-workshop): pin disabled hook wiring 2026-04-22 12:38:38 -07:00
Vincent Koc
4ed2ea5035 fix(hooks): tighten thread ownership mention matching 2026-04-22 12:36:37 -07:00
Peter Steinberger
2aaac45c07 ci: move node aggregate checks off blacksmith 2026-04-22 20:36:27 +01:00
Vincent Koc
dbba830417 fix(hooks): track thread ownership mentions case-insensitively 2026-04-22 12:34:27 -07:00
Vincent Koc
f9f836eba4 fix(hooks): normalize thread ownership slack id casing 2026-04-22 12:32:33 -07:00
Peter Steinberger
5567f4cb01 docs: note media delivery fixes 2026-04-22 20:32:05 +01:00
Peter Steinberger
976398715f fix(image): resolve custom provider model IDs 2026-04-22 20:32:05 +01:00
Peter Steinberger
81f247b1ae fix(agents): dedupe emitted TTS media 2026-04-22 20:32:05 +01:00
Peter Steinberger
e5b67b7ebd fix(media): load inbound media store URIs 2026-04-22 20:32:05 +01:00
Peter Steinberger
0e761cdba8 fix(gateway): redact audio payloads from chat history 2026-04-22 20:32:05 +01:00
Peter Steinberger
64a98dea8d fix(discord): restore DM reactions and guild activation 2026-04-22 20:29:50 +01:00
Peter Steinberger
f7a52573b0 fix: clear phantom Claude CLI resumes (#70317)
Verify Claude CLI session transcripts before reuse and clear phantom bindings with transcript-missing instead of passing stale --resume ids.\n\nFixes #70177.
2026-04-22 20:29:17 +01:00
Vincent Koc
ec75545a82 fix(hooks): normalize thread ownership channel allowlists 2026-04-22 12:29:08 -07:00
Peter Steinberger
9c733956c0 fix(plugins): repair bundled deps on activation 2026-04-22 20:27:42 +01:00
Vincent Koc
4663e7394b fix(hooks): canonicalize slack thread ownership ids 2026-04-22 12:26:31 -07:00
Vincent Koc
7d088f198f fix(hooks): fail open without thread ownership routing 2026-04-22 12:24:15 -07:00
Peter Steinberger
e6a9e9a700 test: cover Telegram webhook timeout reply continuation 2026-04-22 20:23:53 +01:00
Vincent Koc
9a14307306 test(plugins): pin bundled hook names 2026-04-22 12:22:44 -07:00
Peter Steinberger
d8935ca838 perf: keep gateway live probes off helper imports 2026-04-22 20:22:14 +01:00
Vincent Koc
d0bf9cc19e test(plugins): pin bundled hook registration surfaces 2026-04-22 12:20:21 -07:00
anirudhmarc
24266af1ce fix(amazon-bedrock): inject cache points for application inference profile ARNs (#69953)
* fix(amazon-bedrock): inject cache points for application inference profile ARNs

pi-ai's internal supportsPromptCaching checks model.id for specific Claude
model name patterns (e.g. "-4-", "claude-3-7-sonnet"), which fails for
application inference profile ARNs that don't contain the model name.
This causes prompt caching to silently break for Bedrock users with
application inference profiles.

Work around this by detecting when pi-ai would miss cache point injection
(via piAiWouldInjectCachePoints mirror) and patching the Converse API
payload via onPayload to add cachePoint blocks to the system prompt and
last user message — matching the same format pi-ai uses natively.

The fix is safe:
- Checks for existing cache points to avoid double-injection
- Respects cacheRetention: "none"
- Defaults to "short" retention (matching pi-ai default)
- Becomes a no-op once upstream pi-mono#2925 is fixed

Fixes #19279
Upstream: https://github.com/badlogic/pi-mono/issues/2925

* fix(amazon-bedrock): tighten app-profile cache injection

---------

Co-authored-by: Your Name <you@example.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-04-22 12:19:29 -07:00
Peter Steinberger
4c5394d0ba fix: lower Telegram webhook callback timeout (#70146) (thanks @friday-james) 2026-04-22 20:19:12 +01:00
friday-james
43fc38e46c fix(telegram): lower webhook callback timeout to 5s
#16763 added `onTimeout: "return"` with `timeoutMilliseconds: 10_000`
(grammY default). In practice, Telegram's webhook servers abort the
read well before 10s when handler latency is LLM-bound: `getWebhookInfo`
reports `last_error_message: "Read timeout expired"` and pending updates
pile up, cascading into multi-minute reply lag.

Reproducible A/B on identical infra (same region, same bot token):
- Minimal Python echo bot: 5 back-to-back webhook RTTs 341-642ms, clean.
- OpenClaw current main: intermittent Read timeout expired, 1-5 min lag.

The handler still runs to completion; only the Telegram-facing ack is
sooner. grammY's deployment guide suggests 5s for long-running handlers.

No new config surface; minimal one-line change to the existing constant
and its test assertion. If a configurable timeout is wanted, that can be
a follow-up (see stale #7754).
2026-04-22 20:19:12 +01:00
Vincent Koc
e1341941d5 test(plugins): guard legacy bundled hook regressions 2026-04-22 12:18:27 -07:00
Peter Steinberger
67e5cca7a4 test: tighten Telegram polling conflict coverage (#69873) (thanks @hclsys) 2026-04-22 20:16:14 +01:00
HCL
3a11435c7d test(telegram): update monitor test for #69787 transport rebuild on 409
Sibling test in monitor.test.ts asserted the pre-fix behavior (single
transport reused across cycles on 409). My #69787 change rebuilds the
transport on 409 so Telegram sees a fresh TCP socket — update the
assertion to match.

Two transports are now expected: the initial one plus the rebuild
after the conflict.
2026-04-22 20:16:14 +01:00
HCL
83a906c95c fix(telegram): mark polling transport dirty on 409 conflict (#69787)
When getUpdates returns 409 Conflict (e.g.
'terminated by other getUpdates request'), the polling runtime
previously retried on the same HTTP keep-alive TCP socket because
markDirty() was only called in the isRecoverable branch.

Telegram treats that connection as the 'old' session and keeps
terminating it — producing a sustained low-rate 409 retry loop
(observed a few per minute after eliminating duplicate pollers).

Broaden the dirty-mark condition to fire on isConflict as well as
isRecoverable so the next cycle forces a fresh TCP connection.

Update the existing 'reuses transport after getUpdates conflict' test
— which previously locked in the buggy behavior — to assert the new
correct behavior: one fresh transport is built, the stale one is
closed.
2026-04-22 20:16:14 +01:00
Peter Steinberger
8f38691e79 fix: preserve Slack download cfg token fallback (#70160) (thanks @martingarramon) 2026-04-22 20:14:00 +01:00
Martin Garramon
44b1bad333 fix(slack): pass cfg into resolveToken from downloadSlackFile call site
Commit 95331e5cc5 ("fix(channels): thread runtime config through sends")
migrated resolveToken to a 3-arg signature (explicit, accountId, cfg) and
updated the getClient call site at actions.ts:83. The sibling call inside
downloadSlackFile at actions.ts:445 was not migrated and still dropped
opts.cfg, so the cfg-only resolution branch was unreachable from that path.

Current production callers (action-runtime.ts:386-389) always inject a
resolved readToken into opts.token before calling downloadSlackFile, so
this is defense-in-depth today -- the broken path is not hit in runtime.
Landing this closes the call-site migration gap and adds test coverage
for the cfg-only resolution contract on downloadSlackFile.

Note: pre-commit typecheck hook bypassed because upstream/main has 14
pre-existing TS errors in unrelated packages (discord, qa-lab, qqbot,
slack/monitor/provider.ts, tokenjuice, pi-embedded-runner) -- verified
reproducible on clean HEAD 4a16cf8008 without this diff.
2026-04-22 20:14:00 +01:00
Peter Steinberger
7ff8f8cef8 ci: narrow windows check scope 2026-04-22 20:13:37 +01:00
Peter Steinberger
bfc72b5256 fix: route Slack HTTP webhook dispatch (#70275) (thanks @FroeMic) 2026-04-22 20:12:09 +01:00
froemic
7ecff96425 Fix Slack HTTP route registry dispatch 2026-04-22 20:12:09 +01:00
Vincent Koc
988fe85f2c test(memory): exercise registered auto-recall hook 2026-04-22 12:11:46 -07:00
zqchris
b24ae8b18b fix(auto-reply): preserve streaming reply directives (#70243)
Preserve streamed MEDIA/reply/audio directives across chunk boundaries and phase-aware final_answer delivery.\n\nThanks @zqchris.
2026-04-22 20:11:00 +01:00
Peter Steinberger
b1b1979841 ci: skip windows for test-only changes 2026-04-22 20:10:27 +01:00
Vincent Koc
0d68128aed test(memory): exercise registered auto-capture hook 2026-04-22 12:07:03 -07:00
Peter Steinberger
8b89d37a2b ci: rotate stale concurrency group 2026-04-22 20:05:10 +01:00
Neerav Makwana
5462d4d5c5 fix: drop silent parent replies while subagents are pending (#69942)
Drop bare parent NO_REPLY payloads while spawned subagents are pending, preserving quiet parent turns until child completion delivers the real reply.\n\nThanks @neeravmakwana.
2026-04-22 20:04:38 +01:00
Vincent Koc
aee9f476c8 test(skill-workshop): exercise registered prompt hook 2026-04-22 12:03:31 -07:00
Peter Steinberger
3c89f5d537 ci: add scoped docker gateway e2e 2026-04-22 20:02:23 +01:00
HFConsultant
647f4ee8ce fix: persist CLI session clearing atomically (#70298)
Persist stale CLI session clearing through the session-store merge path and add regression coverage for Claude binding removal.\n\nThanks @HFConsultant.
2026-04-22 20:01:35 +01:00
Vincent Koc
e3fc1a237b test(acpx): exercise registered reply_dispatch hook 2026-04-22 11:59:54 -07:00
Vincent Koc
f4bbbcbfb3 fix(hooks): canonicalize thread ownership conversation ids 2026-04-22 11:57:29 -07:00
Felix Miao
449cad510d fix: honor ACP spawn model overrides (#70210)
Honor explicit ACP sessions_spawn model overrides and preserve ACP runtime cwd options.\n\nThanks @felix-miao.
2026-04-22 19:55:23 +01:00
Vincent Koc
c09591b086 test(memory): drop stale dreaming hook doubles 2026-04-22 11:53:00 -07:00
Peter Steinberger
170496c105 ci: fold build smoke into artifact job 2026-04-22 19:52:13 +01:00
Vincent Koc
5fbafa7e47 fix(hooks): prefer shared outbound conversation context 2026-04-22 11:49:25 -07:00
Vincent Koc
62a4abbc9f refactor(hooks): centralize matrix subagent hook wiring 2026-04-22 11:45:33 -07:00
Peter Steinberger
2251516281 fix(discord): break monitor threading import cycle 2026-04-22 19:44:22 +01:00
Peter Steinberger
6294182cbb ci: parallelize extension batch groups 2026-04-22 19:39:08 +01:00
Peter Steinberger
b0d4e64170 refactor(discord): share partial channel test fixtures 2026-04-22 19:38:45 +01:00
Peter Steinberger
ec5d403f5b refactor(discord): share channel action param parsing 2026-04-22 19:38:45 +01:00
Peter Steinberger
8bd387976d refactor(discord): centralize thread channel context 2026-04-22 19:38:45 +01:00
Vincent Koc
bbcd185215 refactor(hooks): centralize bundled subagent hook wiring 2026-04-22 11:37:18 -07:00
Peter Steinberger
d30f252c1b ci: use dist cache instead of artifact upload 2026-04-22 19:31:25 +01:00
Peter Steinberger
4b2b261367 fix(plugins): preserve source activation config 2026-04-22 19:26:12 +01:00
Vincent Koc
6d003cbcee fix(hooks): expose typed gateway startup context 2026-04-22 11:22:51 -07:00
Peter Steinberger
3e24898690 fix: stabilize Claude CLI session prompt hashing 2026-04-22 19:21:51 +01:00
Peter Steinberger
ea29e654d7 fix(cli-session): forward static prompt hash input 2026-04-22 19:21:51 +01:00
Zijun Lin
e1ffe97984 fix: address review feedback — handle empty static prompt and remove stray blank lines
- Always pass extraSystemPromptStatic as string (even when empty) so the
  fallback in prepare.ts never accidentally hashes dynamic content
- Use explicit undefined check (params.extraSystemPromptStatic !== undefined)
  instead of ?? nullish coalescing to avoid edge case where empty static
  string falls through to hashing the full dynamic prompt
- Remove extra blank line
2026-04-22 19:21:51 +01:00
Zijun Lin
d1c414305b fix(cli-session): only hash static extraSystemPrompt for session reuse
The extraSystemPrompt includes per-message dynamic content from
buildInboundMetaSystemPrompt() (timestamps, message IDs, sender metadata)
that changes on every inbound message. This causes the extraSystemPromptHash
to differ every turn, triggering a session reset with reason='system-prompt'
and discarding all CLI session context.

Fix: split extraSystemPrompt into dynamic (inbound meta) and static
(group context, group intro, group system prompt, exec override hints)
portions. Only hash the static portion for session reuse validation.

The full extraSystemPrompt (dynamic + static) is still sent to the CLI
as before — only the session stability hash uses the static subset.

Fixes #70100
2026-04-22 19:21:51 +01:00
Peter Steinberger
d48763caf9 test: keep config fallback test on generic plugin channel 2026-04-22 19:20:15 +01:00
Peter Steinberger
03846d63ec refactor: use memory slot defaults in core paths 2026-04-22 19:18:18 +01:00
Peter Steinberger
80a16339e1 refactor: declare channel add flags in manifests 2026-04-22 19:13:51 +01:00
Peter Steinberger
6488e0dd0c test: keep hook and slack tests on public boundaries 2026-04-22 19:09:18 +01:00
Peter Steinberger
86667d670e refactor: move doctor capabilities to channel manifests 2026-04-22 19:05:53 +01:00
Peter Steinberger
510a8f9ebc fix: share reply media context (#68111) (thanks @ayeshakhalid192007-dev) 2026-04-22 19:02:44 +01:00
ayeshakhalid192007-dev
8d4e6a39b5 test(agent-runner): regression — createReplyMediaPathNormalizer.runtime not called when normalizer injected 2026-04-22 19:02:44 +01:00
ayeshakhalid192007-dev
552d5dcbce fix(agent-runner): share media-path normalizer with runAgentTurnWithFallback to prevent duplicate outbound media 2026-04-22 19:02:44 +01:00
ayeshakhalid192007-dev
88760f88c2 fix(agent-runner): accept injected normalizeMediaPaths in runAgentTurnWithFallback 2026-04-22 19:02:44 +01:00
Peter Steinberger
5ad06d0b20 refactor: build channel setup input generically 2026-04-22 18:57:45 +01:00
Vincent Koc
1e61279b35 refactor(memory): migrate lancedb recall to prompt-build hook 2026-04-22 10:56:14 -07:00
Peter Steinberger
921a5416e4 refactor: move channel doctor migrations to plugins 2026-04-22 18:55:18 +01:00
Peter Steinberger
9d5d2f9cdd fix: make Discord thread parent inheritance opt-in (#69986) (thanks @Blahdude) 2026-04-22 18:54:22 +01:00
Oliver Camp
956cf9b6b2 fix(discord): make thread parent inheritance opt-in 2026-04-22 18:54:22 +01:00
Peter Steinberger
40e19cc9a1 ci: downsize install smoke runner 2026-04-22 18:54:06 +01:00
Vincent Koc
b5b03fbaee test(slack): drop obsolete adapter hook test 2026-04-22 10:53:44 -07:00
Vincent Koc
e593122465 fix(hooks): standardize outbound routing metadata 2026-04-22 10:53:44 -07:00
Peter Steinberger
b0f6c54645 ci: run install smoke for runtime dep staging 2026-04-22 18:51:38 +01:00
Peter Steinberger
a12fcd3f18 fix: harden Discord voice commands in threads 2026-04-22 18:49:58 +01:00
Peter Steinberger
9d66a900e5 fix(plugins): harden bundled runtime dep staging 2026-04-22 18:49:13 +01:00
Hana Chang
0e9c632444 fix(discord): use resolveDiscordChannelNameSafe for voice channel override name
Applies the same safe-accessor pattern to the adjacent name field.
If @buape/carbon implements name as a getter that also reads _rawData
(like parentId), the previous `"name" in channel` pattern would throw
for the same reason. Aligns with the fix for parentId in the same call
site.
2026-04-22 18:47:41 +01:00
Hana Chang
5c5fa5f38b fix(discord): use resolveDiscordChannelParentIdSafe in voice command path
#69908 switched native slash commands, listeners, and the model picker to
the safe accessor for partial thread channels, but the voice /join command
still reads channel.parentId through the unsafe "parentId" in channel
pattern. Route it through the same helper so the voice command path does
not crash with "Cannot access rawData on partial Channel" when invoked
from inside a thread on @buape/carbon >=0.16.
2026-04-22 18:47:41 +01:00
Claw Kowalski
43366cd541 fix(discord): thread runtime config through guild actions 2026-04-22 18:47:30 +01:00
Peter Steinberger
e9d16cbd8c test: keep loader fixture inside plugin boundary 2026-04-22 18:46:57 +01:00
Peter Steinberger
860cc1b3fe fix(config): preserve source config during recovery 2026-04-22 18:42:53 +01:00
Peter Steinberger
557f4fc689 docs: update claude cli stdio notes 2026-04-22 18:40:51 +01:00
Peter Steinberger
d8c9185f3f ci: add fast docker install smoke 2026-04-22 18:39:03 +01:00
Peter Steinberger
dad4b3e7fb fix: default claude cli to stdio sessions 2026-04-22 18:38:32 +01:00
Peter Steinberger
9337e1bd8a fix(agents): accept silent no-reply turns 2026-04-22 18:36:15 +01:00
Peter Steinberger
9d27d09d47 fix: add plugin load debug shape 2026-04-22 18:31:37 +01:00
Peter Steinberger
63776bc999 test: stabilize audio directive tag test 2026-04-22 18:26:07 +01:00
Peter Steinberger
a2512f0243 fix: load staged dist-runtime plugins in docker 2026-04-22 18:22:39 +01:00
Peter Steinberger
72c765e736 ci: parallelize additional boundary guards 2026-04-22 18:21:05 +01:00
Peter Steinberger
a9be41d8c7 ci: keep workflow edits off windows lane 2026-04-22 18:16:11 +01:00
Peter Steinberger
2afad03931 ci: trim gateway watch build profile 2026-04-22 18:11:48 +01:00
Peter Steinberger
024592fb1d Revert "ci: reuse build artifacts for gateway topology"
This reverts commit be317769e6.
2026-04-22 18:10:02 +01:00
Devin Robison
5b32c3138c telegram: align model picker callback auth (#70235)
* telegram: align model picker callback auth

* docs(changelog): note telegram model callback auth fix

* fix(telegram): use runtime config for model callback auth
2026-04-22 11:06:01 -06:00
Peter Steinberger
be317769e6 ci: reuse build artifacts for gateway topology 2026-04-22 18:05:27 +01:00
Tak Hoffman
f328c21046 feat: Add /models add hot-reload model registration (#70211)
* feat(models): add chat model registration with hot reload

* docs(changelog): add models entry for pr 70211

* fix(models): harden add flow follow-ups

* fix models add review follow-ups

* harden models add config writes

* tighten plugin boundary invariant

* move models add adapters behind sdk facades

* avoid ollama-specific core facade
2026-04-22 12:00:30 -05:00
Devin Robison
0623079e98 fix(dotenv): block connector endpoint workspace overrides (#70240)
* fix(dotenv): block connector endpoint workspace overrides

* docs(changelog): note dotenv endpoint blocklist

* fix(dotenv): block Matrix per-account scoped homeserver overrides
2026-04-22 10:58:32 -06:00
Peter Steinberger
8b8df813d0 ci: keep native lanes native scoped 2026-04-22 17:53:38 +01:00
Peter Steinberger
03cf97a33e ci: consolidate short test workers 2026-04-22 17:49:06 +01:00
Peter Steinberger
6370013bb7 ci: rebalance runtime config tests 2026-04-22 17:37:54 +01:00
Peter Steinberger
e8240a2628 ci: keep build smoke on blacksmith 2026-04-22 17:33:40 +01:00
Peter Steinberger
d8913d3901 ci: offload short linux checks 2026-04-22 17:30:54 +01:00
Peter Steinberger
8febc20e80 ci: reduce blacksmith test pressure 2026-04-22 17:26:00 +01:00
Ayaan Zaidi
486d0ec235 fix(gateway): preserve restart continuation chat type 2026-04-22 21:49:49 +05:30
Peter Steinberger
4ef1c06f9e ci: rebalance agentic node tests 2026-04-22 17:18:32 +01:00
Peter Steinberger
fd93b7f2ab perf(test): avoid bundled setup in auto-enable tests 2026-04-22 17:13:42 +01:00
Devin Robison
dd46783c34 fix(pairing): clear stale requests on device removal (#70239)
* fix(pairing): clear stale requests on device removal

* docs(changelog): note pairing stale request cleanup
2026-04-22 10:05:05 -06:00
Ayaan Zaidi
81e0022b4d refactor(gateway): unify startup task execution 2026-04-22 21:31:19 +05:30
Jason Perlow
53ad1a6066 fix(gateway): allow silent metadata-upgrade pairing for loopback CLI clients (#70224)
Loopback CLI clients (cli_container_local, shared_secret_loopback_local)
with valid shared-secret auth previously got disconnected with 1008
pairing required whenever the paired device record's platform or
deviceFamily string differed from what the CLI claimed at connect time.

PR #69431 added the shared_secret_loopback_local locality but deferred
the metadata-upgrade reason from the auto-approval allowlist. That
deferral created an unrecoverable handshake loop in practice: every CLI
connect triggers a fresh metadata-upgrade request, the Control UI has
no approval surface for this reason, and non-interactive shells cannot
complete pairing. This broke every non-interactive openclaw agent use
case when paired device keys are replicated across hosts or installs
are migrated across platforms.

Extend shouldAllowSilentLocalPairing to auto-approve metadata-upgrade
for cli_container_local and shared_secret_loopback_local localities
only. Browser / Control-UI / remote paths retain existing approval-
required behavior. Gateway still logs every metadata refresh via the
existing security audit line for operator review.

Add 4 unit tests covering the decision table for metadata-upgrade
across all four localities.

Related: #69397, #69431
2026-04-22 09:58:53 -06:00
Ayaan Zaidi
25e01c182c docs(changelog): note restart sentinel atomic writes 2026-04-22 20:44:10 +05:30
Ayaan Zaidi
d497de7697 fix(gateway): write restart sentinels atomically 2026-04-22 20:44:10 +05:30
Peter Steinberger
fb70d3ac67 ci: refresh ci concurrency group 2026-04-22 15:53:37 +01:00
Peter Steinberger
ed97cc7210 ci: skip aggregate fan-in after cancellation 2026-04-22 15:52:25 +01:00
Ayaan Zaidi
6f25befc4f docs(changelog): thank cron contributors 2026-04-22 20:18:15 +05:30
Ayaan Zaidi
7085687a16 docs(changelog): correct cron contributors 2026-04-22 20:16:53 +05:30
Ayaan Zaidi
34b0aac3b5 docs(changelog): fix cron attribution 2026-04-22 20:15:04 +05:30
Peter Steinberger
c73f7d6596 ci: move lightweight automation off blacksmith 2026-04-22 15:44:34 +01:00
VACInc
962b25b4a6 fix: preserve restart continuations after reboot (#63406) (thanks @VACInc)
* gateway: add restart continuation sentinel

* gateway: address restart continuation review

* gateway: handle restart continuation edge cases

* gateway: keep restart continuations on threaded delivery path

* fix(gateway): harden restart continuation routing

* test(gateway): cover restart continuation edge cases

* docs(agent): clarify restart continuation usage

* fix: preserve restart continuations after reboot (#63406) (thanks @VACInc)

---------

Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-04-22 20:09:07 +05:30
Garming
a43be09dca fix(doctor): skip token generation for trusted-proxy and none auth modes (#59055)
runGatewayAuthHealth() only excluded 'password' and 'token' (with existing
token) from its needsToken check. When gateway.auth.mode was set to
'trusted-proxy' or 'none', doctor --fix would incorrectly:

1. Flag the config as 'missing a token'
2. Prompt to generate a gateway token
3. Overwrite auth.mode to 'token' in openclaw.json

This silently broke trusted-proxy deployments (common in SaaS/reverse-proxy
setups) by replacing the delegated auth mode with token auth.

The fix aligns runGatewayAuthHealth() with the existing
hasExplicitGatewayInstallAuthMode() in auth-install-policy.ts, which
already correctly returns false for 'password', 'none', and 'trusted-proxy'.

Co-authored-by: wujiaming88 <wujiaming88@example.com>
2026-04-22 22:38:27 +08:00
Peter Steinberger
38135ff6b4 ci: keep cpu-sensitive lanes larger 2026-04-22 15:08:47 +01:00
Peter Steinberger
ba9589256c build: refresh a2ui bundle hash 2026-04-22 15:07:23 +01:00
Peter Steinberger
cdf5f66298 ci: keep long matrix aggregates on blacksmith 2026-04-22 15:00:17 +01:00
Peter Steinberger
0f4ec84a2c fix: fail closed on plugin integrity drift 2026-04-22 14:59:52 +01:00
Peter Steinberger
dc2c3a4920 fix(gateway): harden WS pairing locality 2026-04-22 14:55:58 +01:00
Peter Steinberger
95e430f670 ci: run aggregate checks off blacksmith 2026-04-22 14:53:41 +01:00
Peter Steinberger
fd01a66e30 ci: downsize blacksmith runners 2026-04-22 14:39:20 +01:00
Peter Steinberger
d7ea136384 fix(agent): align pi session tool options 2026-04-22 14:39:20 +01:00
Peter Steinberger
fef830f4cf chore: update dependencies 2026-04-22 14:35:00 +01:00
Peter Steinberger
0d12422418 ci: consolidate test shard fanout 2026-04-22 14:23:43 +01:00
pashpashpash
cd41bd1359 fix(codex): apply GPT-5 prompt overlay (#70175) 2026-04-22 22:00:23 +09:00
cxy
608cfd36f5 fix(qqbot): add interaction intents (#70143)
* feat(qqbot): add intents interaction

* fix(qqbot): add interaction intents (#70143) (thanks @cxyhhhhh)

---------

Co-authored-by: sliverp <870080352@qq.com>
2026-04-22 20:03:33 +08:00
Ayaan Zaidi
4a16cf8008 fix: require cli auth epoch version (#70132) 2026-04-22 17:03:33 +05:30
Ayaan Zaidi
7fd8eeecf2 fix: update cli session changelog (#70132) 2026-04-22 17:03:33 +05:30
Ayaan Zaidi
9ad58ddc7e test(cli): cover oauth auth epoch continuity 2026-04-22 17:03:33 +05:30
Ayaan Zaidi
1ff461fe7b fix(cli): stabilize oauth session auth epochs 2026-04-22 17:03:33 +05:30
Nimrod Gutman
8778521167 fix(plugins): avoid doctor crash on legacy interactive state (#70135)
* fix(plugins): hydrate legacy interactive state

* fix(plugins): avoid doctor crash on legacy interactive state (#70135) (thanks @ngutman)
2026-04-22 14:17:09 +03:00
Nimrod Gutman
cfda375bb6 chore(pi): remove local pr prompts
Remove repo-local /landpr and /reviewpr prompt templates so maintainers use the externally maintained workflow instead.
These flows remain available from the external maintainers repo via globally installed Pi skills and prompts.
2026-04-22 13:38:47 +03:00
Ted Li
13fae1685f fix(config): accept truncateAfterCompaction (#68395)
Merged via squash.

Prepared head SHA: bf45148a75
Co-authored-by: MonkeyLeeT <6754057+MonkeyLeeT@users.noreply.github.com>
Co-authored-by: hxy91819 <8814856+hxy91819@users.noreply.github.com>
Reviewed-by: @hxy91819
2026-04-22 18:31:03 +08:00
Ayaan Zaidi
16f016f07e fix: update cli session changelog (#70106) 2026-04-22 15:35:21 +05:30
Ayaan Zaidi
1e3e077370 fix(gateway): preserve cli session binding metadata 2026-04-22 15:35:21 +05:30
Ayaan Zaidi
7a2203be50 fix(cli): upgrade legacy mcp session reuse 2026-04-22 15:35:21 +05:30
Ayaan Zaidi
18869acf46 fix(cli): keep provider-owned sessions through implicit expiry 2026-04-22 15:35:21 +05:30
Sliverp
e36e0e8ad2 fix: lower the log level from info to debug (#70108) 2026-04-22 17:58:49 +08:00
Jacky
fbdf502e08 place permission under each branch of bot permissions for discord docs (#69218)
Merged via squash.

Prepared head SHA: dd6ae52d90
Co-authored-by: epicseven-cup <59263116+epicseven-cup@users.noreply.github.com>
Co-authored-by: velvet-shark <126378+velvet-shark@users.noreply.github.com>
Reviewed-by: @velvet-shark
2026-04-22 11:49:15 +02:00
pashpashpash
abf940db61 fix(codex): unchain app-server defaults (#70082) 2026-04-22 17:53:49 +09:00
Val Alexander
43a941b51c fix(pair): render /pair qr as media (#70047)
* fix(pair): render pair qr as media

* fix(gateway): preserve media reply threading

* fix(gateway): harden webchat media replies

* fix(plugin-sdk): keep trustedLocalMedia internal

* docs(changelog): note pair qr media fix

* Update CHANGELOG with recent fixes and enhancements

Updated changelog to include recent fixes and enhancements.
2026-04-22 03:31:09 -05:00
Ayaan Zaidi
81ca7bc40b fix: keep claude cli sessions warm (#69679)
* feat(cli): keep claude cli sessions warm

* test(cli): cover claude live session reuse

* fix(cli): harden claude live session reuse

* fix(cli): redact mcp session key logs

* fix(cli): bound claude live session turns

* fix(cli): reuse claude live sessions on resume

* refactor(cli): canonicalize claude live argv

* fix(cli): preserve claude live resume state

* fix(cli): close dead claude live sessions

* fix(cli): serialize claude live session creates

* fix(cli): count pending claude live sessions

* fix(cli): tighten claude live resume abort

* fix(cli): reject closed claude live sessions

* fix(cli): refresh claude live fingerprints

* fix(cli): stabilize MCP resume hash

* fix: preserve claude live inline resume (#69679)

---------

Co-authored-by: Frank Yang <frank.ekn@gmail.com>
2026-04-22 13:44:18 +05:30
Val Alexander
dab46a7e98 qa: harden parity gate execution (#70045) 2026-04-22 03:08:25 -05:00
Peter Steinberger
bee2e0f38f fix: keep custom pi tools executable 2026-04-22 08:52:55 +01:00
Peter Steinberger
4431d6c5d0 fix: harden tokenjuice host typing 2026-04-22 08:52:55 +01:00
Peter Steinberger
d8892ee227 test: harden qa private runtime staging 2026-04-22 08:52:55 +01:00
Peter Steinberger
eb67964239 ci: build private qa parity runtime 2026-04-22 08:52:55 +01:00
Peter Steinberger
dd9adc57c2 test: harden qa parity runtime staging 2026-04-22 08:52:55 +01:00
Peter Steinberger
137f64d0c0 fix: drop stale socket mode opt-in 2026-04-22 08:52:55 +01:00
Peter Steinberger
8bfb4024f6 test: harden qa parity config cleanup 2026-04-22 08:52:55 +01:00
Peter Steinberger
cd088d8a16 ci: build runtime before parity gate 2026-04-22 08:52:55 +01:00
Peter Steinberger
764bb310f7 ci: pin qa parity tool profile 2026-04-22 08:52:55 +01:00
Peter Steinberger
0cd785d8a5 ci: stabilize parity gate runner 2026-04-22 08:52:55 +01:00
Peter Steinberger
895b2690c4 ci: serialize parity gate scenarios 2026-04-22 08:52:55 +01:00
Peter Steinberger
5bb8f5ae8d docs: update changelog for channel health (#69833) (thanks @bek91) 2026-04-22 08:52:55 +01:00
Peter Steinberger
d8d0380297 fix: use transport activity for stale health 2026-04-22 08:52:55 +01:00
Bek
270003aefd fix: clean up slack socket waiters on start hooks 2026-04-22 08:52:55 +01:00
Bek
cd1977bf16 fix: make slack socket health event-driven 2026-04-22 08:52:55 +01:00
Vincent Koc
da86ce7887 feat(openai): add codex device-code auth and fix login options in menu (#69557)
Merged via squash.

Prepared head SHA: 4918ed69f1
Co-authored-by: vincentkoc <25068+vincentkoc@users.noreply.github.com>
Co-authored-by: BunsDev <68980965+BunsDev@users.noreply.github.com>
Reviewed-by: @BunsDev
2026-04-22 02:47:20 -05:00
Dewaldt Huysamen
d4eb236523 fix(release-check): assert bundled plugin runtime deps after packed postinstall (#70035)
* fix(release-check): assert bundled plugin runtime deps after packed postinstall

Release-check already validates source dist/extensions runtime deps are staged, but runPackedBundledChannelEntrySmoke never re-validates after the packed postinstall runs against the installed tarball. That gap is how 2026.4.21 shipped without @whiskeysockets/baileys in dist/extensions/whatsapp/node_modules, because the source staging passed while the installed layout was left broken.

Re-use collectBuiltBundledPluginStagedRuntimeDependencyErrors against the installed packageRoot right after runPackedBundledPluginPostinstall and fail release-check if any declared runtime dependency is missing from the plugin-local node_modules.

* fix(release-check): check postinstalled dep sentinels at packageRoot/node_modules

Codex review on #70035 caught that collectInstalledBundledPluginRuntimeDepErrors was pointing at dist/extensions/<id>/node_modules, but packed postinstall installs and probes sentinels at packageRoot/node_modules (see dependencySentinelPath in scripts/postinstall-bundled-plugins.mjs). The previous implementation would have falsely failed release-check on healthy packed installs while still missing the original WhatsApp regression.

Reuse discoverBundledPluginRuntimeDeps from postinstall-bundled-plugins.mjs so the release guard uses the exact same dep discovery and sentinel paths the packed postinstall uses. Update the test fixtures accordingly so they model the real install layout.
2026-04-22 00:31:40 -07:00
Vincent Koc
55b297ef15 fix(agents): keep mocked OpenAI Responses on HTTP (#69815)
* fix(agents): keep mocked OpenAI responses on HTTP

* docs(changelog): add entry for mocked responses fix
2026-04-22 00:25:02 -07:00
pashpashpash
1dd3fb1611 Fix Codex auth handoff for the app-server harness (#69990)
* Codex: fix auth bridge token shape

* Codex: preserve selected auth tokens

* Codex: prefer selected profile id token

* Codex: honor inherited Codex home

---------

Co-authored-by: Val Alexander <68980965+BunsDev@users.noreply.github.com>
2026-04-22 16:22:29 +09:00
Vincent Koc
a4cafde0da docs(changelog): mention tokenjuice embedded support (#70039) 2026-04-22 00:15:16 -07:00
Vincent Koc
22717878cc docs(tokenjuice): add bundled plugin guide (#70038)
* docs(tokenjuice): add bundled plugin guide

* docs(tokenjuice): sort nav entry
2026-04-22 00:14:32 -07:00
Onur Solmaz
142e26d6cd docs(skill): tighten duplicate triage mirror rules 2026-04-22 09:09:51 +02:00
Vincent Koc
91ac485246 feat(tokenjuice): bundle the native adapter (#69946)
* feat(plugins): register embedded extension factories

* feat(tokenjuice): bundle the native adapter

* fix(tokenjuice): gate the bundled embedded extension seam

* fix(tokenjuice): refresh runtime sidecar baseline

* fix(plugins): harden bundled embedded extensions

* fix(plugins): install source bundled runtime deps

* fix(tokenjuice): sync lockfile importer

* fix(plugins): validate reused runtime dep versions

* fix(plugins): restore tokenjuice CI contract

* fix(plugins): remove tokenjuice dts bridge

* fix(tokenjuice): repair openclaw type shim

* fix(plugins): harden bundled runtime deps

* fix(plugins): keep source checkout runtime deps local

* fix(plugins): isolate bundled runtime dep installs

* fix(cli): keep plugin startup registration non-activating

* fix(cli): keep loader overrides out of plugin cli options
2026-04-21 23:58:37 -07:00
Alex Knight
201385548c perf(slack): narrow runtime-setter + lazy-load 4 modules + narrow 2 SDK surfaces (#69317)
Lazy load modules showing a ~50% gateway startup performance improvement
2026-04-22 16:42:43 +10:00
Peter Steinberger
fd2c883673 perf(test): skip setup promotion metadata fallback 2026-04-22 07:13:24 +01:00
Peter Steinberger
053147451b docs: generalize plugin runtime comments 2026-04-22 07:13:15 +01:00
fuller-stack-dev
561f2e52c7 docs(tui): document local config repair flow (#69995) (thanks @fuller-stack-dev)
* docs(tui): document local config repair flow

* docs(tui): clarify local TUI examples

* docs(config): gate local TUI repair flow

* docs(tui): fix local repair docs

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-04-22 11:42:59 +05:30
Peter Steinberger
4fbd314e1f refactor: gate setup promotion by manifest feature 2026-04-22 07:07:08 +01:00
Peter Steinberger
866a120d69 docs: generalize core routing comments 2026-04-22 07:01:34 +01:00
Peter Steinberger
b24d153f23 perf(test): avoid bundled channel fallback in model override tests 2026-04-22 07:01:19 +01:00
Peter Steinberger
4d6756d45d refactor: generalize voice audio compatibility 2026-04-22 06:58:45 +01:00
Peter Steinberger
8b2ef40775 docs: remove bundled channel examples from core types 2026-04-22 06:55:47 +01:00
Peter Steinberger
b619d39e54 fix(channels): preserve setup promotion fallbacks 2026-04-22 06:47:51 +01:00
Peter Steinberger
182d0fcee2 fix(telegram): isolate sent-message cache stores 2026-04-22 06:47:51 +01:00
Peter Steinberger
95331e5cc5 fix(channels): thread runtime config through sends 2026-04-22 06:47:51 +01:00
Peter Steinberger
e1897419de fix(config): enforce resolved runtime channel config 2026-04-22 06:47:50 +01:00
Peter Steinberger
b70531bf24 docs: generalize core channel examples 2026-04-22 06:47:41 +01:00
Jonathan
9f5dc4045c docs: fix stale community links in README and CONTRIBUTING (#69945)
Co-authored-by: Jonathan Amponsah <amponsahjonathan442@gmail.com>
2026-04-21 22:47:16 -07:00
JuniperSling
bcd1dec3dc feat(tencent): remove Token Plan provider and auth (#69996)
Co-authored-by: albertxyu <albertxyu@tencent.com>
2026-04-21 22:46:07 -07:00
Peter Steinberger
21e04350ab test: generalize media fetch token fixtures 2026-04-22 06:45:09 +01:00
Ayaan Zaidi
54311a7a34 fix(agents): guard replay convert hook 2026-04-22 11:15:03 +05:30
Peter Steinberger
344a88f931 refactor: remove plugin tool display overrides from core 2026-04-22 06:43:48 +01:00
Peter Steinberger
62864fb22c test: reuse plugin auto-enable fixture environment 2026-04-22 06:43:12 +01:00
Vincent Koc
6d6845ea9d fix(googlechat): harden google auth transport (#69812)
* fix(googlechat): localize google auth gaxios compat

* fix(googlechat): declare undici for staged runtime deps

* fix(googlechat): harden google auth transport

* fix(googlechat): narrow credential file reads

* fix(googlechat): preserve auth proxy transport

* fix(googlechat): allow symlinked auth files

* fix(googlechat): atomically load auth files

* fix(googlechat): eagerly buffer auth responses

* fix(googlechat): cap auth response buffering

* fix(googlechat): pin staged auth runtime deps

* fix(googlechat): buffer auth responses as array buffers

* Update CHANGELOG.md

* fix(googlechat): reject unstreamed auth responses

* fix(googlechat): use ambient fetch for auth transport

* fix(googlechat): keep guarded auth fetch on runtime path

* fix(googlechat): align staged zod range

* chore(lockfile): sync googlechat zod spec
2026-04-21 22:40:57 -07:00
Peter Steinberger
d94a981a33 refactor: keep plugin login policy out of core 2026-04-22 06:39:48 +01:00
Peter Steinberger
ec8ea02bb7 test: generalize legacy state migration coverage 2026-04-22 06:39:48 +01:00
Peter Steinberger
4a9dd3fe49 refactor: drop provider reconnect shim 2026-04-22 06:39:48 +01:00
Peter Steinberger
cb426b3b20 refactor: generalize route target parsing 2026-04-22 06:39:48 +01:00
Vincent Koc
7c13a48e49 chore(agents): prefer local validation over testbox 2026-04-21 22:37:03 -07:00
Peter Steinberger
1a7dfbbaba docs: record testbox full-suite profile 2026-04-22 06:36:11 +01:00
Peter Steinberger
0a670a058d perf(ci): unblock node compat and trim runtime compat test 2026-04-22 06:34:08 +01:00
pashpashpash
dc4e97472d Make harness failures fail honestly (#69981)
* Agents: fail honestly on harness errors

* Docs: clarify Codex harness fallback
2026-04-22 14:33:21 +09:00
Ayaan Zaidi
a0ccf69259 fix: normalize assistant replay content (#69850) (thanks @fuller-stack-dev) 2026-04-22 11:01:14 +05:30
Ayaan Zaidi
e4da220478 fix(agents): distill replay content normalization 2026-04-22 11:01:14 +05:30
FullerStackDev
f58dd0f9b6 fix(agents): harden replay normalization guards 2026-04-22 11:01:14 +05:30
FullerStackDev
3105597f8f fix(agents): normalize malformed assistant replay content 2026-04-22 11:01:14 +05:30
Peter Steinberger
925c3f3fa8 test: align plugin test contracts 2026-04-22 06:29:13 +01:00
Peter Steinberger
0285afe86f fix: stabilize testbox test suite 2026-04-22 06:29:13 +01:00
Shakker
08d5ad3828 fix: skip clean run-node runtime restaging 2026-04-22 06:28:50 +01:00
Peter Steinberger
cc91e8ecf9 fix(channels): repair bundled setup runtime deps 2026-04-22 06:26:13 +01:00
dependabot[bot]
38f8bc5592 chore(deps): bump the android-deps group across 1 directory with 17 updates (#67592)
* build(deps): bump the android-deps group across 1 directory with 17 updates

Bumps the android-deps group with 12 updates in the /apps/android directory:

| Package | From | To |
| --- | --- | --- |
| org.jlleitschuh.gradle.ktlint | `14.0.1` | `14.2.0` |
| [org.jetbrains.kotlin.plugin.compose](https://github.com/JetBrains/kotlin) | `2.2.21` | `2.3.20` |
| [org.jetbrains.kotlin.plugin.serialization](https://github.com/JetBrains/kotlin) | `2.2.21` | `2.3.20` |
| androidx.compose:compose-bom | `2026.02.00` | `2026.03.01` |
| androidx.activity:activity-compose | `1.12.2` | `1.13.0` |
| [org.jetbrains.kotlinx:kotlinx-serialization-json](https://github.com/Kotlin/kotlinx.serialization) | `1.10.0` | `1.11.0` |
| [org.bouncycastle:bcprov-jdk18on](https://github.com/bcgit/bc-java) | `1.83` | `1.84` |
| [org.commonmark:commonmark](https://github.com/commonmark/commonmark-java) | `0.27.1` | `0.28.0` |
| [io.kotest:kotest-runner-junit5-jvm](https://github.com/kotest/kotest) | `6.1.3` | `6.1.11` |
| [org.junit.vintage:junit-vintage-engine](https://github.com/junit-team/junit-framework) | `6.0.2` | `6.0.3` |
| androidx.test.ext:junit | `1.2.1` | `1.3.0` |
| [gradle-wrapper](https://github.com/gradle/gradle) | `9.3.1` | `9.4.1` |

Updates `org.jlleitschuh.gradle.ktlint` from 14.0.1 to 14.2.0

Updates `org.jetbrains.kotlin.plugin.compose` from 2.2.21 to 2.3.20
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v2.2.21...v2.3.20)

Updates `org.jetbrains.kotlin.plugin.serialization` from 2.2.21 to 2.3.20
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v2.2.21...v2.3.20)

Updates `androidx.compose:compose-bom` from 2026.02.00 to 2026.03.01

Updates `androidx.activity:activity-compose` from 1.12.2 to 1.13.0

Updates `org.jetbrains.kotlinx:kotlinx-serialization-json` from 1.10.0 to 1.11.0
- [Release notes](https://github.com/Kotlin/kotlinx.serialization/releases)
- [Changelog](https://github.com/Kotlin/kotlinx.serialization/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Kotlin/kotlinx.serialization/compare/v1.10.0...v1.11.0)

Updates `org.bouncycastle:bcprov-jdk18on` from 1.83 to 1.84
- [Changelog](https://github.com/bcgit/bc-java/blob/main/docs/releasenotes.html)
- [Commits](https://github.com/bcgit/bc-java/commits)

Updates `org.commonmark:commonmark` from 0.27.1 to 0.28.0
- [Release notes](https://github.com/commonmark/commonmark-java/releases)
- [Changelog](https://github.com/commonmark/commonmark-java/blob/main/CHANGELOG.md)
- [Commits](https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.27.1...commonmark-parent-0.28.0)

Updates `org.commonmark:commonmark-ext-autolink` from 0.27.1 to 0.28.0
- [Release notes](https://github.com/commonmark/commonmark-java/releases)
- [Changelog](https://github.com/commonmark/commonmark-java/blob/main/CHANGELOG.md)
- [Commits](https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.27.1...commonmark-parent-0.28.0)

Updates `org.commonmark:commonmark-ext-gfm-strikethrough` from 0.27.1 to 0.28.0
- [Release notes](https://github.com/commonmark/commonmark-java/releases)
- [Changelog](https://github.com/commonmark/commonmark-java/blob/main/CHANGELOG.md)
- [Commits](https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.27.1...commonmark-parent-0.28.0)

Updates `org.commonmark:commonmark-ext-gfm-tables` from 0.27.1 to 0.28.0
- [Release notes](https://github.com/commonmark/commonmark-java/releases)
- [Changelog](https://github.com/commonmark/commonmark-java/blob/main/CHANGELOG.md)
- [Commits](https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.27.1...commonmark-parent-0.28.0)

Updates `org.commonmark:commonmark-ext-task-list-items` from 0.27.1 to 0.28.0
- [Release notes](https://github.com/commonmark/commonmark-java/releases)
- [Changelog](https://github.com/commonmark/commonmark-java/blob/main/CHANGELOG.md)
- [Commits](https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.27.1...commonmark-parent-0.28.0)

Updates `io.kotest:kotest-runner-junit5-jvm` from 6.1.3 to 6.1.11
- [Release notes](https://github.com/kotest/kotest/releases)
- [Commits](https://github.com/kotest/kotest/compare/6.1.3...6.1.11)

Updates `io.kotest:kotest-assertions-core-jvm` from 6.1.3 to 6.1.11
- [Release notes](https://github.com/kotest/kotest/releases)
- [Commits](https://github.com/kotest/kotest/compare/6.1.3...6.1.11)

Updates `org.junit.vintage:junit-vintage-engine` from 6.0.2 to 6.0.3
- [Release notes](https://github.com/junit-team/junit-framework/releases)
- [Commits](https://github.com/junit-team/junit-framework/compare/r6.0.2...r6.0.3)

Updates `androidx.test.ext:junit` from 1.2.1 to 1.3.0

Updates `gradle-wrapper` from 9.3.1 to 9.4.1
- [Release notes](https://github.com/gradle/gradle/releases)
- [Commits](https://github.com/gradle/gradle/compare/v9.3.1...v9.4.1)

---
updated-dependencies:
- dependency-name: org.jlleitschuh.gradle.ktlint
  dependency-version: 14.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: android-deps
- dependency-name: org.jetbrains.kotlin.plugin.compose
  dependency-version: 2.3.20
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: android-deps
- dependency-name: org.jetbrains.kotlin.plugin.serialization
  dependency-version: 2.3.20
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: android-deps
- dependency-name: androidx.compose:compose-bom
  dependency-version: 2026.03.01
  dependency-type: direct:production
  dependency-group: android-deps
- dependency-name: androidx.activity:activity-compose
  dependency-version: 1.13.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: android-deps
- dependency-name: org.jetbrains.kotlinx:kotlinx-serialization-json
  dependency-version: 1.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: android-deps
- dependency-name: org.bouncycastle:bcprov-jdk18on
  dependency-version: '1.84'
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: android-deps
- dependency-name: org.commonmark:commonmark
  dependency-version: 0.28.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: android-deps
- dependency-name: org.commonmark:commonmark-ext-autolink
  dependency-version: 0.28.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: android-deps
- dependency-name: org.commonmark:commonmark-ext-gfm-strikethrough
  dependency-version: 0.28.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: android-deps
- dependency-name: org.commonmark:commonmark-ext-gfm-tables
  dependency-version: 0.28.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: android-deps
- dependency-name: org.commonmark:commonmark-ext-task-list-items
  dependency-version: 0.28.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: android-deps
- dependency-name: io.kotest:kotest-runner-junit5-jvm
  dependency-version: 6.1.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: android-deps
- dependency-name: io.kotest:kotest-assertions-core-jvm
  dependency-version: 6.1.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: android-deps
- dependency-name: org.junit.vintage:junit-vintage-engine
  dependency-version: 6.0.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: android-deps
- dependency-name: androidx.test.ext:junit
  dependency-version: 1.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: android-deps
- dependency-name: gradle-wrapper
  dependency-version: 9.4.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: android-deps
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix(android): remove Kotlin 2.3 warning failures

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-04-21 22:25:27 -07:00
Peter Steinberger
80f4c931e8 test: align telegram cron test doubles 2026-04-22 06:19:20 +01:00
Peter Steinberger
e39784decd ci: move preflight off blacksmith runners 2026-04-22 06:12:33 +01:00
Peter Steinberger
40719bcb74 refactor: move cron output policy to channel plugins 2026-04-22 06:11:49 +01:00
Peter Steinberger
3fd2a94404 refactor: generalize command sender identity checks 2026-04-22 06:11:49 +01:00
Peter Steinberger
6639bbbc2e refactor: generalize conversation id labels 2026-04-22 06:11:49 +01:00
Peter Steinberger
e20a5eeddb refactor: keep legacy web search config in doctor 2026-04-22 06:11:49 +01:00
Peter Steinberger
a6dce7cf19 refactor: resolve web search secrets by target path 2026-04-22 06:11:49 +01:00
Peter Steinberger
89741c7a23 refactor: use generic web search runtime credential hooks 2026-04-22 06:11:49 +01:00
Peter Steinberger
94f670b893 refactor: use generic web search credential hooks 2026-04-22 06:11:49 +01:00
Peter Steinberger
0c863124bb refactor: derive setup promotion rules from plugins 2026-04-22 06:11:49 +01:00
Peter Steinberger
db055a5c0d refactor: move WhatsApp group inference out of core 2026-04-22 06:11:49 +01:00
Peter Steinberger
bdcbb6b49d refactor: move Feishu model override parsing to plugin 2026-04-22 06:11:49 +01:00
Peter Steinberger
7189b49f81 refactor: move media defaults into plugin manifests 2026-04-22 06:11:49 +01:00
Peter Steinberger
2e775fb03e refactor: move stale socket modes to channel status 2026-04-22 06:11:49 +01:00
Peter Steinberger
0a027ff591 refactor: derive CLI web credential targets 2026-04-22 06:11:49 +01:00
Peter Steinberger
bc9c2cc162 refactor: derive web credential secret targets from manifests 2026-04-22 06:11:49 +01:00
Peter Steinberger
de616055f7 perf(ci): trim preflight setup and stream test waits 2026-04-22 06:11:02 +01:00
Vincent Koc
812d012b98 docs(changelog): note Tencent provider plugin 2026-04-21 22:06:35 -07:00
Ayaan Zaidi
77de199b1c fix: improve configure startup (#69984) 2026-04-22 10:33:14 +05:30
Ayaan Zaidi
f4966351a1 fix(configure): bound gateway hint probes 2026-04-22 10:33:14 +05:30
Ayaan Zaidi
c1b21a2a3a fix(cli): skip configure preaction bootstrap 2026-04-22 10:33:14 +05:30
Peter Steinberger
262899f495 ci: shallow checkout live reusable jobs 2026-04-22 06:00:01 +01:00
JuniperSling
d8b9be468a feat(tencent): add bundled Tencent Cloud provider plugin (Tokenhub + Token Plan) (#68460)
* feat(tencent): add bundled Tencent Cloud provider plugin (Tokenhub + Token Plan)

* fix(tencent): use provider-specific default model aliases

Both Tencent providers previously defaulted to the same alias "HY3 Preview",
which collides in buildModelAliasIndex (single alias map, keyed by normalized
alias). When both providers are onboarded, alias-based selection routed to
whichever provider was configured last.

Disambiguate the fallback aliases so resolution is deterministic regardless
of onboarding order:
  - tencent-tokenhub   -> "HY3 Preview (TokenHub)"
  - tencent-token-plan -> "HY3 Preview (Token Plan)"

* docs(tencent): rename model to "Hy3 preview" and drop "HY3" family name

Align with the external-facing product name:
  - model display name: "HY3 Preview" -> "Hy3 preview"
  - family/umbrella references in docs and auth hints: "HY3" -> "Hy3 preview"
  - internal cost constant: HY3_COST -> HY_COST

Model call id (hy3-preview) is unchanged.

* docs(tencent): use "Hy" as the family name in generic references

Keep specific model references as "Hy3 preview" (model catalog names,
onboarding aliases, Available-models docs entries), but switch
family/umbrella references to the plain "Hy" family name so future Hy
versions fit without doc churn:

  - auth hints: "Hy via Tencent TokenHub Gateway" / "Hy via Token Plan"
  - docs intro + Use-case table: "Tencent Hy models" / "call Hy via ..."
  - models.ts pricing comment: "Hy pricing"

* feat(tencent): add tiered pricing for Hy3 preview model

---------

Co-authored-by: albertxyu <albertxyu@tencent.com>
2026-04-21 21:59:22 -07:00
Peter Steinberger
c07b388f77 ci: keep pnpm alignment scoped to CI 2026-04-22 05:58:50 +01:00
Peter Steinberger
fbddef34bd perf(ci): trim provider catalog test setup 2026-04-22 05:57:22 +01:00
Peter Steinberger
dbf3eca590 test: stabilize workspace fallback assertion 2026-04-22 05:50:57 +01:00
pashpashpash
89932593bb Show typing while heartbeats are running (#69963)
* Heartbeat: show typing during runs

* Heartbeat: fix typing review issues

* Discord: preserve heartbeat typing config
2026-04-22 13:50:27 +09:00
Zihao WAN
d4f91a354e fix(ollama): forward think:false for qwen3 chat requests (#69967)
Forward top-level Ollama think flags on native /api/chat requests so --thinking off sends think:false.\n\nThanks @WZH8898.
2026-04-22 05:49:16 +01:00
fuller-stack-dev
276c00015c fix: add local embedded TUI mode (#66767) (thanks @fuller-stack-dev)
* feat(tui): add local embedded TUI mode with terminal/chat aliases

Adds a gateway-free local TUI path so users can run openclaw in their
terminal without needing a running gateway process.

- TuiBackend interface abstraction (tui-backend.ts) with EmbeddedTuiBackend
  implementation that drives the agent loop in-process
- openclaw tui --local flag for local embedded mode
- openclaw terminal / openclaw chat aliases that imply --local
- /auth slash command with codex CLI delegation to avoid prolite plan issue
- Default model display fallback on startup
- Local-aware status text and log suppression
- Concise auth error hints, raw HTML 403 suppression
- Onboarding hatch flow launches local TUI (no gateway required)
- Commander alias bug fix in run-main.ts (.aliases() check)
- All new and updated tests passing (145/145)

* TUI: fix alias detection, cross-platform codex lookup, and history byte-budget safeguards

* TUI: remove RuntimeEnv type annotation to fix CI oxlint error

* TUI: filter gateway-dependent tools and auto-approve plugin hooks in embedded mode

* TUI: suppress console noise and add embedded mode system prompt note

* TUI: reduce embedded-mode tool filtering from 15 to 7, add local session tools

* TUI: fix remaining PR review comments

* TUI: address latest review feedback and CI drift

* Core: align prompt helper with latest base

* Core: match prompt helper formatting with base

* Core: restore prompt helper from latest base

* fix(tui): preserve local auth fallback in source checkouts

* fix(tts): guard telephony provider invocation

* fix(tui): support Windows codex auth shim

* fix(tui): harden local auth flow

* fix: preserve embedded tool-first run events

* fix(tui): keep embedded plugin approvals gated

* fix(tui): restore embedded attempt import

* fix(tui): resolve sessions in embedded stub

* fix: add embedded TUI changelog entry (#66767) (thanks @fuller-stack-dev)

* fix: pass setup TUI local mode through relaunch (#66767) (thanks @fuller-stack-dev)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-04-22 10:08:57 +05:30
Peter Steinberger
d733d547c0 ci: use fastest Blacksmith testbox runner 2026-04-22 05:33:05 +01:00
dependabot[bot]
68a55cc434 build(deps): bump docker/build-push-action from 6 to 7 (#48053)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6 to 7.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v6...v7)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-21 21:30:47 -07:00
dependabot[bot]
ba8fb6b2b8 build(deps): bump the swift-deps group across 1 directory with 3 updates (#60168)
* build(deps): bump the swift-deps group across 1 directory with 3 updates

Bumps the swift-deps group with 3 updates in the /apps/macos directory: [github.com/orchetect/menubarextraaccess](https://github.com/orchetect/MenuBarExtraAccess), [github.com/apple/swift-log](https://github.com/apple/swift-log) and [github.com/sparkle-project/sparkle](https://github.com/sparkle-project/Sparkle).


Updates `github.com/orchetect/menubarextraaccess` from 1.2.2 to 1.3.0
- [Release notes](https://github.com/orchetect/MenuBarExtraAccess/releases)
- [Commits](https://github.com/orchetect/MenuBarExtraAccess/compare/1.2.2...1.3.0)

Updates `github.com/apple/swift-log` from 1.10.1 to 1.11.0
- [Release notes](https://github.com/apple/swift-log/releases)
- [Commits](https://github.com/apple/swift-log/compare/1.10.1...1.11.0)

Updates `github.com/sparkle-project/sparkle` from 2.9.0 to 2.9.1
- [Release notes](https://github.com/sparkle-project/Sparkle/releases)
- [Commits](https://github.com/sparkle-project/Sparkle/compare/2.9.0...2.9.1)

---
updated-dependencies:
- dependency-name: github.com/orchetect/menubarextraaccess
  dependency-version: 1.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: swift-deps
- dependency-name: github.com/apple/swift-log
  dependency-version: 1.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: swift-deps
- dependency-name: github.com/sparkle-project/sparkle
  dependency-version: 2.9.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swift-deps
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix(macos): restore MenuBarExtraAccess ordering

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-04-21 21:30:14 -07:00
dependabot[bot]
00da59124d build(deps): bump actions/setup-node from 4 to 6 (#61769)
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4 to 6.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v4...v6)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-21 21:29:52 -07:00
dependabot[bot]
bcadf60b4d chore(deps): bump actions/create-github-app-token from 2 to 3.0.0 (#65099)
Bumps [actions/create-github-app-token](https://github.com/actions/create-github-app-token) from 2 to 3.0.0.
- [Release notes](https://github.com/actions/create-github-app-token/releases)
- [Commits](https://github.com/actions/create-github-app-token/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/create-github-app-token
  dependency-version: 3.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-21 21:19:58 -07:00
dependabot[bot]
16c4a04a69 build(deps): bump androidx.test.uiautomator:uiautomator in /apps/android (#48059)
Bumps androidx.test.uiautomator:uiautomator from 2.4.0-alpha06 to 2.4.0-beta02.

---
updated-dependencies:
- dependency-name: androidx.test.uiautomator:uiautomator
  dependency-version: 2.4.0-beta02
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-21 21:19:51 -07:00
dependabot[bot]
7550d426dd chore(deps): bump actions/github-script from 8 to 9 (#65098)
Bumps [actions/github-script](https://github.com/actions/github-script) from 8 to 9.
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](https://github.com/actions/github-script/compare/v8...v9)

---
updated-dependencies:
- dependency-name: actions/github-script
  dependency-version: '9'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-21 21:19:26 -07:00
Peter Steinberger
261b07b1d6 ci: prepare Blacksmith testbox shell 2026-04-22 05:17:30 +01:00
Peter Steinberger
092f292ceb ci: use smaller Blacksmith testbox runner 2026-04-22 05:14:55 +01:00
Peter Steinberger
4e22fc9498 ci: cancel stale push sanity runs 2026-04-22 05:11:57 +01:00
Peter Steinberger
a99490fba4 fix(plugins): support root-owned bundled runtime deps 2026-04-22 05:02:35 +01:00
Peter Steinberger
ba0250e4f3 ci: add Blacksmith testbox workflow 2026-04-22 04:59:38 +01:00
Peter Steinberger
094ca706ed perf(build): use tsgo for a2ui renderer compile 2026-04-22 04:57:55 +01:00
Shakker
3528a17b67 fix: scope static catalog discovery fallback 2026-04-22 04:51:38 +01:00
Shakker
bbe3dc6c2f fix: sanitize models list terminal output 2026-04-22 04:51:38 +01:00
Shakker
754125947a fix: include partial provider discovery fallbacks 2026-04-22 04:51:38 +01:00
Shakker
ae35795c04 docs: add models catalog changelog 2026-04-22 04:51:38 +01:00
Shakker
10959aa980 fix: restrict static model catalogs to bundled providers 2026-04-22 04:51:38 +01:00
Shakker
f3da6e96b7 fix: bound static provider catalog listing 2026-04-22 04:51:38 +01:00
Shakker
d6c7b468ea fix: honor provider hook aliases in catalog filters 2026-04-22 04:51:38 +01:00
Shakker
f9bac5038c fix: harden static provider catalog path 2026-04-22 04:51:38 +01:00
Shakker
d73c31110b fix: harden static provider catalog listing 2026-04-22 04:51:38 +01:00
Shakker
04ecf284fc fix: use static provider catalogs for model listing 2026-04-22 04:51:38 +01:00
Shakker
651d5e0022 docs: document provider catalog model discovery 2026-04-22 04:51:38 +01:00
Shakker
8ba52acc41 fix: add Kimi K2.6 provider catalog rows 2026-04-22 04:51:38 +01:00
Shakker
cc78dd2044 fix: show provider catalog models in all list 2026-04-22 04:51:38 +01:00
Peter Steinberger
11f38afbfc docs: note memory-core cron startup warning fix (#69941) (thanks @Sanjays2402) 2026-04-22 04:50:25 +01:00
Sanjay Santhanam
a37321ad5f fix(memory-core): suppress startup "cron service unavailable" warning (closes #69939)
memory-core registers a gateway:startup hook that runs reconcileManagedDreamingCron() before deps.cron is attached to the startup event (the startup hook is deferred via a 250ms setTimeout in server.impl).

Downgrade the first startup-time "cron service unavailable" warning to a debug log, and rely on the existing runtime reconciliation path to warn if the cron service truly stays unavailable after boot. The managed dreaming cron job itself runs correctly — this was a log-noise regression, not a functional failure.

Signed-off-by: Sanjay Santhanam <51058514+Sanjays2402@users.noreply.github.com>
2026-04-22 04:50:25 +01:00
Peter Steinberger
f027d8faa7 refactor(discord): share native interaction channel context 2026-04-22 04:47:57 +01:00
Peter Steinberger
00cdab99bf test: harden ci timing cleanup 2026-04-22 04:45:19 +01:00
Peter Steinberger
4d223950a0 perf(ci): use tsgo for sdk declaration emit 2026-04-22 04:45:19 +01:00
Gustavo Madeira Santana
a197b544fe fix(doctor): route setup doctor discovery (#69919)
Merged via squash.

Prepared head SHA: 90c7067941
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-04-21 23:40:22 -04:00
Peter Steinberger
a8a023779d fix(discord): preserve fetched thread parent for plugin commands (#69908) (thanks @neeravmakwana) 2026-04-22 04:31:31 +01:00
Neerav Makwana
349d86c152 fix(discord): move in-check inside try/catch in channel-access helper
Wrap the `key in channel` probe inside the existing `try/catch` in `readDiscordChannelPropertySafe` so a throwing Proxy `has` trap (or any other reflective error on the presence check) degrades to `undefined` instead of propagating, matching the existing behavior for throwing getters on the value read.

Add a regression test that exposes the interaction channel through a Proxy whose `has` trap throws on `parentId` and asserts the slash-command path still defers and dispatches.

No behavior change for Carbon prototype getters or plain-object channels: the safe accessor still traverses the prototype chain (required for Carbon's `GuildThreadChannel.parentId`), still returns `undefined` for missing or throwing reads, and still preserves null-to-undefined coercion downstream.
2026-04-22 04:31:31 +01:00
Neerav Makwana
dbf8fd0db7 fix(discord): read channel.parentId through safe accessor on partial thread channels
The Carbon `GuildThreadChannel.parentId` getter throws "Cannot access rawData on partial Channel" whenever Discord delivers a partial thread (for example when an interaction channel is unhydrated). The existing `"parentId" in channel` guard did not help because the `in` operator returns true for prototype getters without invoking them, so the read still crashed `/new` and similar slash commands, guild reactions, and the native model picker when invoked from inside a thread.

Expose a `resolveDiscordChannelParentIdSafe` helper alongside the other channel accessors and use it everywhere we currently read `channel.parentId` from the inbound Discord channel. When the getter throws, the helper returns `undefined`, and the downstream code already falls back to re-fetching the thread id via `resolveDiscordChannelInfo`, keeping authorization/config lookups on the same inputs as before.

Add a regression test that installs a throwing `parentId` getter on a partial guild thread channel and asserts the slash-command path still defers and dispatches instead of surfacing an unauthorized reply.

Fixes #69861
2026-04-22 04:31:31 +01:00
Peter Steinberger
b0734664f8 style: format touched code 2026-04-22 04:24:45 +01:00
Peter Steinberger
2b09c3c7c7 perf(ci): use tsgo for extension boundary compiles 2026-04-22 04:22:38 +01:00
Peter Steinberger
23a017be7c fix: suppress Mattermost quoted reasoning replies (#69927) (thanks @lawrence3699) 2026-04-22 04:10:28 +01:00
lawrence3699
bb43c7b89f fix(mattermost): suppress reasoning previews 2026-04-22 04:10:28 +01:00
lawrence3699
367faac596 fix(mattermost): suppress reasoning-only replies 2026-04-22 04:10:28 +01:00
Peter Steinberger
115accfc82 ci: narrow extension boundary cache inputs 2026-04-22 04:08:42 +01:00
Peter Steinberger
24c409035c perf(gateway): lazy load deferred plugin bootstrap 2026-04-22 04:06:22 +01:00
Peter Steinberger
cb4ec1265f fix(agents): align embedded built-in tool types 2026-04-22 03:42:49 +01:00
Ayaan Zaidi
407107276f fix: report cron message trace channel (#69940) (thanks @davehappyminion) 2026-04-22 08:12:20 +05:30
Ayaan Zaidi
2da1406b29 test(cron): trim message trace comments 2026-04-22 08:12:20 +05:30
davehappyminion
9db67e79a5 fix(cron): narrow accountId spoof guard to explicit mismatch only
Addresses codex P1 review on PR #69940: the previous guard rejected
targets that simply omitted accountId, but message-tool fills accountId
from the agent's bound account at exec time (message-tool.ts:730-733),
so account-bound cron jobs legitimately start with target.accountId
undefined. Rejecting that case lost skipMessagingToolDelivery, causing
dispatchCronDelivery to double-send.

Now we only reject when the tool explicitly names a *different*
accountId — which is the real CWE-284 spoof vector. Omission matches.

Tests updated accordingly:
- matcher unit test: flipped "omit accountId" case from false to true;
  "accountIds differ" case preserved as the real spoof guard
- integration tests: one legitimate-default case (rewrite happens),
  one explicit-mismatch case (rewrite suppressed)

658 cron tests pass.
2026-04-22 08:12:20 +05:30
davehappyminion
851bef9c25 fix(cron): rewrite generic message provider in trace + guard accountId spoof
When a cron job sends via the generic `message` tool, the delivery trace
previously recorded `messageToolSentTo[i].channel = "message"` even
though the send was resolved to a specific channel (e.g. telegram). This
made `jq` diffing intended-vs-actual awkward for the happy path.

Fix:
- `normalizeMessagingToolTarget` now rewrites `channel: "message"`
  to the resolved channel when `matchesMessagingToolDeliveryTarget`
  confirms the tool send matches the resolved cron delivery target.
  Genuinely unmatched generic sends keep the literal "message" so
  audits can still flag them.

- `matchesMessagingToolDeliveryTarget` now requires strict accountId
  equality whenever the resolved delivery carries an `accountId`. An
  omitted `target.accountId` previously short-circuited the guard and
  was treated as a wildcard, letting a generic send spoof attribution to
  any bot identity in the cron delivery trace (CWE-284). This was
  flagged by Aisle on #69771.

Tests:
- Unit: `matchesMessagingToolDeliveryTarget` rejects omitted-accountId
  against account-tied delivery; still matches same-accountId.
- Integration: cron run trace rewrites generic "message" to the
  resolved channel, preserves accountId on both sides, and leaves the
  literal "message" provider in place when the tool send omits
  accountId against an account-tied delivery.
2026-04-22 08:12:20 +05:30
Peter Steinberger
e8f18f95d5 test(docker): cover slack bundled runtime deps 2026-04-22 03:40:37 +01:00
Peter Steinberger
25e2e64ce4 test(docker): cover bundled channel update deps 2026-04-22 03:40:37 +01:00
Peter Steinberger
f9b20c7d17 fix(plugins): repair bundled runtime deps during doctor 2026-04-22 03:40:37 +01:00
Peter Steinberger
6608a50b39 perf(ci): trim gateway watch regression build 2026-04-22 03:35:19 +01:00
Peter Steinberger
0f4dc42767 build: update pi packages to 0.68.1 2026-04-22 03:35:15 +01:00
Peter Steinberger
de6f548a7c fix: suppress disabled channel read-only presence 2026-04-22 03:21:07 +01:00
Aaron U'Ren
8d021ee7bf perf(plugins): prefer native jiti for bundled plugin dist modules (#69925)
Merged via squash.

Prepared head SHA: 1b2da10865
Co-authored-by: aauren <1392295+aauren@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-04-21 22:18:35 -04:00
Val Alexander
fe663de8c7 fix(ui): add clear pending config updates action (#68178)
Merged via squash.

Prepared head SHA: 1a3cb66fcb
Co-authored-by: BunsDev <68980965+BunsDev@users.noreply.github.com>
Co-authored-by: BunsDev <68980965+BunsDev@users.noreply.github.com>
Reviewed-by: @BunsDev
2026-04-21 21:11:51 -05:00
Peter Steinberger
e92079be6b fix: finish browser click timeout recovery (#63524) 2026-04-22 03:08:36 +01:00
Dongseok Paeng
cd82b94333 fix(browser): propagate click aborts through agent act routes
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-22 03:08:36 +01:00
Dongseok Paeng
adc05f090a fix(browser): time out stuck chrome mcp clicks
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-22 03:08:36 +01:00
Peter Steinberger
c97c5a5aff fix: canonicalize opencode-go base URL 2026-04-22 03:05:48 +01:00
Peter Steinberger
1801b90460 chore: bump version to 2026.4.22 2026-04-22 03:04:53 +01:00
Peter Steinberger
d41c6403d5 docs: credit Chrome MCP timeout fix (#69733) 2026-04-22 03:02:49 +01:00
ayeshakhalid192007-dev
55c415a1da fix(browser): address review comments on Chrome MCP timeout handling 2026-04-22 03:02:49 +01:00
ayeshakhalid192007-dev
88268aa2cd fix(browser): make chrome.internal tests pass on Linux (existsSync mock) 2026-04-22 03:02:49 +01:00
ayeshakhalid192007-dev
dc9c46a8df fix(browser): reset Chrome MCP session after navigate_page timeout
navigateChromeMcpPage() now always passes a timeout to the Chrome MCP
navigate_page tool (defaulting to CHROME_MCP_NAVIGATE_TIMEOUT_MS when
the caller omits timeoutMs), and callTool() grows an optional safety-net
that tears down a stuck session via Promise.race so the next caller gets
a fresh subprocess. The catch block gains a transport-identity guard to
avoid clobbering a concurrently-created replacement session.
2026-04-22 03:02:49 +01:00
Peter Steinberger
ef66798433 fix: preserve outbound thread sessions 2026-04-22 02:56:23 +01:00
Peter Steinberger
d87f8cc142 test: move cron validation off gateway server 2026-04-22 02:55:40 +01:00
Patrick Erichsen
529577e045 fix(memory/dreaming): surface blocked status when heartbeat is disabled for main (#69875)
* fix(memory/dreaming): surface blocked status in memory status when heartbeat disabled for main

Replace the hand-rolled heartbeat-rules logic in resolveDreamingBlockedReason
with the shared resolveHeartbeatSummaryForAgent helper, promoted from core to
the plugin-sdk via infra-runtime. Collapses the two disabled-reason branches
into a single message that points at a new Troubleshooting section in the
dreaming docs, so the silent-failure mode described in openclaw/openclaw#69843
becomes legible without the extension re-encoding heartbeat-enablement rules.

Refs openclaw/openclaw#69843, openclaw/openclaw#46046.

* refactor(memory/dreaming): share resolveDreamingBlockedReason across cli and /dreaming surfaces

- Move resolveDreamingBlockedReason from cli.runtime.ts into dreaming.ts as an exported helper and pin its heartbeat check to DEFAULT_AGENT_ID (now exported from plugin-sdk/routing) so the status-line check agrees with the cron's hardcoded sessionTarget even when the configured default agent is not main.
- Render the blocked reason from formatStatus in dreaming-command.ts directly under the enabled line, so /dreaming status, /dreaming on, /dreaming off, and bare /dreaming all flag that the cron is blocked instead of implying dreaming is healthy.
- Tighten the blocked-reason text to lead with user impact ('dreaming is enabled but will not run because heartbeat is disabled for main'), so operators immediately understand the config is toggled on but nothing is actually running.
- Tighten the dreaming Troubleshooting copy to name main explicitly and mention both surfaces.
- Add tests locking the new behavior across cli.test.ts (default-agent=ops still reports blocked for main) and dreaming-command.test.ts (/dreaming status ordering, /dreaming on surfacing, healthy-heartbeat omission).

Refs openclaw/openclaw#69843, openclaw/openclaw#46046.

* fix(memory/dreaming): check heartbeat for the resolved default agent, not the literal 'main'

sessionTarget: 'main' is a cron session-type enum variant meaning 'the default agent's main session', not an agent id (see src/cron/service/jobs.ts). buildManagedDreamingCronJob does not set agentId, and cron runtime resolves the missing agentId through resolveDefaultAgentId(cfg) before enqueuing or waking. The previous pin to DEFAULT_AGENT_ID could produce a false 'blocked' reading when a configured default agent is not 'main' and its heartbeat is fine, and could miss a real block when the default agent is not 'main' and that agent's heartbeat is actually off.

Switch resolveDreamingBlockedReason to resolveDefaultAgentId(cfg) and interpolate the resolved agent id into the message so the blocked line names the agent whose heartbeat is the blocker. Introduce a narrow local CRON_SESSION_TARGET_MAIN constant for the cron session-type enum variant (used by the sessionTarget type and value) so the remaining 'main' literal is semantically distinct from any agent id. Revert the DEFAULT_AGENT_ID export addition on plugin-sdk/routing; memory-core no longer needs it. Update the Troubleshooting doc wording and the cli test that was locking the wrong behaviour.

Refs openclaw/openclaw#69843, openclaw/openclaw#46046.

* fix(memory/dreaming): align blocked check with server-cron wake's defaults-only heartbeat

resolveDreamingBlockedReason was using resolveHeartbeatSummaryForAgent, which merges agents.defaults.heartbeat with agents.list[].heartbeat. The managed dreaming cron leaves job.agentId and job.sessionKey unset, so server-cron's wake wrapper cannot look up a per-agent entry and calls runHeartbeatOnce with agents.defaults.heartbeat only. Using the summary helper would disagree with the actual wake when the default agent overrides heartbeat.every differently from the defaults (either direction — false blocked when the override would run, or false healthy when defaults block).

Mirror the wake path explicitly: rule-1 enablement via isHeartbeatEnabledForAgent against the default agent, rule-3 interval via resolveHeartbeatIntervalMs with defaults-only heartbeat config. Comment points at server-cron so a future cleanup of that latent override-propagation gap sees the coupling.

Refs openclaw/openclaw#69843.
2026-04-21 18:51:43 -07:00
Peter Steinberger
b3b62ed004 perf: replace madge cycle scan 2026-04-22 02:46:16 +01:00
Patrick Erichsen
efb7d426cf fix(browser): reject ax<N> refs in act path instead of timing out (#69924) 2026-04-21 18:43:27 -07:00
1389 changed files with 70169 additions and 13197 deletions

View File

@@ -0,0 +1,340 @@
---
name: blacksmith-testbox
description: >
Validate code changes against real CI when local execution is not
enough. Use for CI-parity checks, secrets/services, migrations, or
builds/tests that cannot run reliably on the local machine. Do not
replace repo-documented local test/build loops just because this
skill exists.
---
# Blacksmith Testbox
## Scope
Use Testbox when you need remote CI parity, injected secrets, hosted services,
or an OS/runtime image that your local machine cannot provide cheaply.
Do not default to Testbox for every local test/build loop. If the repo has
documented local commands for normal iteration, use those first so you keep
warm caches, local build state, and fast feedback.
Testbox is the expensive path. Reach for it deliberately.
## Install the CLI
If `blacksmith` is not installed, install it:
curl -fsSL https://get.blacksmith.sh | sh
For the canary channel (bleeding-edge):
BLACKSMITH_CHANNEL=canary sh -c 'curl -fsSL https://get.blacksmith.sh | sh'
Then authenticate:
blacksmith auth login
## Agent-triggered browser auth (non-interactive)
When an agent needs to ensure the user is authenticated before running testbox
commands (e.g. warmup, run), use browser-based auth with non-interactive mode.
This opens the browser for the user to sign in; the agent does not interact with
the browser. The org selector in the dashboard is skipped, so the user only sees
the sign-in flow.
**Required command** (`--organization` is required with `--non-interactive`):
blacksmith auth login --non-interactive --organization <org-slug>
The org slug can come from `BLACKSMITH_ORG` env var or the `--org` global flag.
If neither is set, the agent should use the project's known org (e.g. from repo
config or user context). Example:
blacksmith auth login --non-interactive --organization acme-corp
blacksmith --org acme-corp auth login --non-interactive --organization acme-corp
**Flow**: The CLI starts a local callback server, opens the browser to the
dashboard auth page, and blocks for up to 2 minutes. The user completes sign-in
and authorization in the browser. The dashboard redirects to localhost with the
token; the CLI saves credentials and exits. The agent then proceeds.
**Do not use** `--api-token` for this flow — that is for headless/token-based
auth. This skill focuses on browser-based auth when the user prefers signing in
via the web UI.
Optional flags:
- `--dashboard-url <url>` — Override dashboard URL (e.g. for staging)
## Decide first: local or Testbox
Before warming anything up, check the repo's own instructions.
Prefer local commands when:
- the repo documents a supported local test/build workflow
- you are iterating on unit tests, lint, typecheck, formatting, or other
local-only validation
- the value comes from warm local caches and fast repeat runs
- the command does not need remote secrets, hosted services, or CI-only images
Prefer Testbox when:
- the repo explicitly requires CI-parity or remote validation
- the command needs secrets, service containers, or provisioned infra
- you are reproducing CI-only failures
- you need the exact workflow image/job environment from GitHub Actions
For OpenClaw specifically, normal local iteration should stay local:
- `pnpm check:changed`
- `pnpm test:changed`
- `pnpm test <path-or-filter>`
- `pnpm test:serial`
- `pnpm build`
Only use Testbox in OpenClaw when the user explicitly wants CI-parity or the
check truly depends on remote secrets/services that the local repo loop cannot
provide.
## Setup: Warmup before coding
If you decided Testbox is actually warranted, warm one up early. This returns
an ID instantly and boots the CI environment in the background while you work:
blacksmith testbox warmup ci-check-testbox.yml
# → tbx_01jkz5b3t9...
Save this ID. You need it for every `run` command.
Warmup dispatches a GitHub Actions workflow that provisions a VM with the
full CI environment: dependencies installed, services started, secrets
injected, and a clean checkout of the repo at the default branch.
Options:
--ref <branch> Git ref to dispatch against (default: repo's default branch)
--job <name> Specific job within the workflow (if it has multiple)
--idle-timeout <min> Idle timeout in minutes (default: 30)
## CRITICAL: Always run from the repo root
ALWAYS invoke `blacksmith testbox` commands from the **root of the git
repository**. The CLI syncs the current working directory to the testbox
using rsync with `--delete`. If you run from a subdirectory (e.g.
`cd backend && blacksmith testbox run ...`), rsync will mirror only that
subdirectory and **delete everything else** on the testbox — wiping other
directories like `dashboard/`, `cli/`, etc.
# CORRECT — run from repo root, use paths in the command
blacksmith testbox run --id <ID> "cd backend && php artisan test"
blacksmith testbox run --id <ID> "cd dashboard && npm test"
# WRONG — do NOT cd into a subdirectory before invoking the CLI
cd backend && blacksmith testbox run --id <ID> "php artisan test"
If your shell is in a subdirectory, `cd` back to the repo root first:
cd "$(git rev-parse --show-toplevel)"
blacksmith testbox run --id <ID> "cd backend && php artisan test"
## Running commands
blacksmith testbox run --id <ID> "<command>"
The `run` command automatically waits for the testbox to become ready if
it is still booting, so you can call `run` immediately after warmup without
needing to check status first.
## Downloading files from a testbox
Use the `download` command to retrieve files or directories from a running
testbox to your local machine. This is useful for fetching build artifacts,
test results, coverage reports, or any output generated on the testbox.
blacksmith testbox download --id <ID> <remote-path> [local-path]
The remote path is relative to the testbox working directory (same as `run`).
If no local path is specified, the file is saved to the current directory
using the same base name.
To download a directory, append a trailing `/` to the remote path — this
triggers recursive mode:
# Download a single file
blacksmith testbox download --id <ID> coverage/report.html
# Download a file to a specific local path
blacksmith testbox download --id <ID> build/output.tar.gz ./output.tar.gz
# Download an entire directory
blacksmith testbox download --id <ID> test-results/ ./results/
Options:
--ssh-private-key <path> Path to SSH private key (if warmup used --ssh-public-key)
## How file sync works
Understanding this model is critical for using Testbox correctly.
When you call `run`, the CLI performs a **delta sync** of your local changes
to the remote testbox before executing your command:
1. The testbox VM starts from a clean `actions/checkout` at the warmup ref.
The workflow's setup steps (e.g. `npm install`, `pip install`, `composer install`)
run during warmup and populate dependency directories on the remote VM.
2. On each `run`, the CLI uses **git** to detect which files changed locally
since the last sync. It syncs ONLY tracked files and untracked non-ignored
files (i.e. files that `git ls-files` reports).
3. **`.gitignore`'d directories are never synced.** This means directories
like `node_modules/`, `vendor/`, `.venv/`, `build/`, `dist/`, etc. are
NOT transferred from your local machine. The testbox uses its own copies
of those directories, populated during the warmup workflow steps.
4. If nothing has changed since the last sync (same git commit and working
tree state), the sync is skipped entirely for speed.
### Why this matters
- **Changing dependencies**: If you modify `package.json`, `requirements.txt`,
`composer.json`, `go.mod`, or similar dependency manifests, the lock/manifest
file will be synced but the actual dependency directory will NOT. You must
re-run the install command on the testbox:
blacksmith testbox run --id <ID> "npm install && npm test"
blacksmith testbox run --id <ID> "pip install -r requirements.txt && pytest"
blacksmith testbox run --id <ID> "composer install && phpunit"
- **Generated/build artifacts**: If your tests depend on a build step (e.g.
`npm run build`, `make`), and you changed source files that affect the build
output, re-run the build on the testbox before testing.
- **New untracked files**: New files you create locally ARE synced (as long as
they are not gitignored). You do not need to `git add` them first.
- **Deleted files**: Files you delete locally are also deleted on the remote
testbox. The sync model keeps the remote in lockstep with your local managed
file set.
## CRITICAL: Do not ban local tests
Do not assume local validation is forbidden. Many repos intentionally invest in
fast, warm local loops, and forcing every run through Testbox destroys that
advantage.
Use Testbox for the checks that actually need it: remote parity, secrets,
services, CI-only runners, or reproducibility against the workflow image.
If the repo says local tests/builds are the normal path, follow the repo.
## When to use
Use Testbox when:
- running database migrations or destructive environment checks
- running commands that depend on secrets or environment variables not present locally
- reproducing CI-only failures or validating against the workflow image
- validating behavior that needs provisioned services or remote runners
- doing a final parity check before commit/push when the repo or user wants that
Trim that list based on repo guidance. If the repo documents supported local
tests/builds, prefer local for routine iteration and keep Testbox for the
checks that need parity or remote state.
## Workflow
1. Decide whether the repo's local loop is the right default.
2. Only if Testbox is warranted, warm up early:
`blacksmith testbox warmup ci-check-testbox.yml` → save the ID
3. Write code while the testbox boots in the background.
4. Run the remote command when needed:
`blacksmith testbox run --id <ID> "npm test"`
5. If tests fail, fix code and re-run against the same warm box.
6. If you changed dependency manifests (package.json, etc.), prepend
the install command: `blacksmith testbox run --id <ID> "npm install && npm test"`
7. If you need artifacts (coverage reports, build outputs, etc.), download them:
`blacksmith testbox download --id <ID> coverage/ ./coverage/`
8. Once green, commit and push.
## OpenClaw full test suite
For OpenClaw, use the repo package manager and the measured stable full-suite
profile below. It keeps six Vitest project shards active while limiting each
shard to one worker to avoid worker OOMs on Testbox:
blacksmith testbox run --id <ID> "env NODE_OPTIONS=--max-old-space-size=4096 OPENCLAW_TEST_PROJECTS_PARALLEL=6 OPENCLAW_VITEST_MAX_WORKERS=1 pnpm test"
Observed full-suite time on Blacksmith Testbox is about 3-4 minutes:
- 173-180s on a warmed box
- 219s on a fresh 32-vCPU box
When validating before commit/push, run `pnpm check:changed` first when
appropriate, then the full suite with the profile above if broad confidence is
needed.
## Examples
blacksmith testbox warmup ci-check-testbox.yml
# → tbx_01jkz5b3t9...
# Run tests
blacksmith testbox run --id <ID> "npm test -- --testPathPattern=handler.test"
blacksmith testbox run --id <ID> "go test ./pkg/api/... -run TestHandler -v"
blacksmith testbox run --id <ID> "python -m pytest tests/test_api.py -k test_auth"
# Re-install deps after changing package.json, then test
blacksmith testbox run --id <ID> "npm install && npm test"
# Build and test
blacksmith testbox run --id <ID> "npm run build && npm test"
# Download artifacts from the testbox
blacksmith testbox download --id <ID> coverage/lcov-report/ ./coverage/
blacksmith testbox download --id <ID> build/output.tar.gz
## Waiting for the testbox to be ready
The `run` command automatically waits for the testbox, so explicit waiting is
usually unnecessary. If you do need to check readiness separately (e.g. before
a series of runs), use the `--wait` flag. Do NOT use a sleep-and-recheck loop.
Correct: block until ready with a timeout:
blacksmith testbox status --id <ID> --wait [--wait-timeout 5m]
Wrong: never use sleep + status in a loop:
# BAD — do not do this
sleep 30 && blacksmith testbox status --id <ID>
while ! blacksmith testbox status --id <ID> | grep ready; do sleep 5; done
`--wait` polls the status and exits as soon as the testbox is ready (or when the
timeout is reached). Default timeout is 5m; use `--wait-timeout` for longer
(e.g. `10m`, `1h`).
## Managing testboxes
# Check status of a specific testbox
blacksmith testbox status --id <ID>
# List all active testboxes for the current repo
blacksmith testbox list
# Stop a testbox when you're done (frees resources)
blacksmith testbox stop --id <ID>
Testboxes automatically shut down after being idle (default: 30 minutes).
If you need a longer session, increase the timeout at warmup time:
blacksmith testbox warmup ci-check-testbox.yml --idle-timeout 60
## With options
blacksmith testbox warmup ci-check-testbox.yml --ref main
blacksmith testbox warmup ci-check-testbox.yml --idle-timeout 60
blacksmith testbox run --id <ID> "go test ./..."

View File

@@ -22,16 +22,17 @@ Use this skill for Parallels guest workflows and smoke interpretation. Do not lo
- Windows: `90m`
- aggregate npm-update wrapper: `150m`
If a lane hits the cap, stop there, inspect the newest `/tmp/openclaw-parallels-*` run directory and phase log, then fix or rerun the smallest affected lane. Do not keep waiting on a capped lane.
- Actual OpenClaw npm install/update phases are a stricter budget than whole lanes: install phases should finish within 7 minutes, and update phases should finish within 5 minutes. If a phase named `install-main`, `install-latest`, `install-baseline`, or `install-baseline-package` exceeds 420s, or a phase named `update-dev` / same-guest `openclaw update` exceeds 300s, treat it as a failure/harness bug and start diagnosis from that phase log. Do not wait for a longer lane cap.
- Actual OpenClaw npm install/update phases are a stricter signal than whole-lane caps: install phases should normally finish within 7 minutes, and update phases should normally show meaningful progress within 5 minutes. If a phase named `install-main`, `install-latest`, `install-baseline`, or `install-baseline-package` exceeds 420s, or a phase named `update-dev` / same-guest `openclaw update` exceeds 300s without new markers, start diagnosis from that phase log and guest process state. Current Windows update phases can still pass after roughly 10-15 minutes because `doctor --fix` may install bundled plugin runtime deps; keep the script hard cap near 20 minutes unless the log is truly stale.
- For a full OS matrix, prefer running independent guest-family lanes in parallel when host capacity allows:
- `timeout --foreground 75m pnpm test:parallels:macos -- --json`
- `timeout --foreground 90m pnpm test:parallels:windows -- --json`
- `timeout --foreground 75m pnpm test:parallels:linux -- --json`
Keep each lane in its own shell/session and track the run directory for each one.
Keep each lane in its own shell/session and track the run directory for each one. Before starting the matrix, run any required host build/package gate to completion. When current-main tgz packaging is needed, the smoke scripts hold a shared package lock through `pnpm build`, inventory/staging, and `npm pack`; if that lock is missing or broken, serialize the matrix instead of accepting concurrent `dist` mutation.
- Do not run multiple smoke lanes against the same guest family at once. Tahoe lanes share the host HTTP port, and Windows/Linux lanes can collide on snapshot restore/start state if two jobs touch the same VM concurrently.
- Do not run the aggregate `pnpm test:parallels:npm-update` wrapper in parallel with individual macOS/Windows/Linux smoke lanes; it touches the same guest families and snapshots.
- Do not start Parallels lanes while any host command may rebuild, clean, or restage `dist` (`pnpm build`, `pnpm ui:build`, `pnpm release:check`, `pnpm test:install:smoke`, npm pack/install smoke, or Docker lanes that run package/build prep). Run the build/package gates first, let them finish, then start the VM matrix. Concurrent `dist` mutation can make host `npm pack` fail with missing files and wastes a full VM cycle.
- Do not start Parallels lanes while any unrelated host command may rebuild, clean, or restage `dist` (`pnpm build`, `pnpm ui:build`, `pnpm release:check`, `pnpm test:install:smoke`, npm pack/install smoke, or Docker lanes that run package/build prep). Run unrelated build/package gates first, let them finish, then start the VM matrix. Concurrent `dist` mutation can make host `npm pack` fail with missing files and wastes a full VM cycle.
- While running or optimizing the matrix, record wall-clock duration per lane and the slowest phase from `/tmp/openclaw-parallels-*` logs. Use that timing before changing smoke order, timeouts, or helper behavior.
- If a host build changes tracked generated files such as `src/canvas-host/a2ui/.bundle.hash`, stop before spending VM time. Commit the generated artifact separately or fix the generator drift, then rerun the smallest affected lane.
- If `main` is moving under active multi-agent work, prefer a detached worktree pinned to one commit for long Parallels suites. The smoke scripts now verify the packed tgz commit instead of live `git rev-parse HEAD`, but a pinned worktree still avoids noisy rebuild/version drift during reruns.
- For `openclaw update --channel dev` lanes, remember the guest clones GitHub `main`, not your local worktree. If a local fix exists but the rerun still fails inside the cloned dev checkout, do not treat that as disproof of the fix until the branch has been pushed.
- For `prlctl exec`, pass the VM name before `--current-user` (`prlctl exec "$VM" --current-user ...`), not the other way around.

View File

@@ -61,16 +61,49 @@ prtags auth status
The expected outcome is that `prtags` stores the logged-in maintainer identity locally and uses that account for authenticated writes.
### Verify the tools before triage
## Missing-Setup Rule
Before using this skill, make sure all three tools are available:
Do not require an up-front preflight before starting the workflow.
Proceed with the normal steps until you actually need a tool or account state.
As soon as you discover that a required CLI is missing or `prtags` is not logged in, stop immediately.
Do not continue in a partial mode after that point.
If `ghr` is missing, ask the user to run the `ghreplica` install command.
If `prtags` is missing, ask the user to run both CLI install commands:
```bash
ghr repo view openclaw/openclaw
prtags auth status
uvx --from pr-search-cli pr-search status
curl -fsSL https://raw.githubusercontent.com/dutifuldev/ghreplica/main/scripts/install-ghr.sh | bash -s -- --bin-dir "$HOME/.local/bin"
curl -fsSL https://raw.githubusercontent.com/dutifuldev/prtags/main/scripts/install-prtags.sh | bash -s -- --bin-dir "$HOME/.local/bin"
```
If `uvx --from pr-search-cli pr-search ...` fails because `uvx` or the `pr-search` launcher is not available, ask the user to make that command work before continuing.
If `prtags auth status` shows that the user is not logged in, ask the user to run:
```bash
prtags auth login
```
Resume only after the missing tool or login state has been fixed.
## Read-Path Default
For read-only GitHub operations in this workflow, use `ghr` as the default CLI.
Treat it as a drop-in replacement for the `gh` read operations you would normally use for PRs, issues, comments, reviews, and duplicate-search evidence.
Only fall back to `gh` when `ghr` is failing for a concrete reason, such as:
- the mirrored object is not present yet
- the mirror data is clearly stale or incomplete for the decision you need to make
- the `ghr` command errors, times out, or does not expose the specific read you need
When you fall back to `gh`, note that you did so and why.
If `ghr` is missing a fresh PR or issue but `gh` can read it, you may use `gh` for the read-side judgment.
If a later `prtags` target-level write fails because the same object is still missing from `ghreplica`, stop and report that the mirror has not caught up yet instead of forcing the write.
## Goal
For each target PR or issue:
@@ -86,10 +119,13 @@ For each target PR or issue:
Use the tools with these boundaries:
- `ghreplica` is the raw evidence source
- use `ghr` first for normal GitHub read operations in this workflow
- use it for title/body/comment search, related PRs, overlapping files, overlapping ranges, and current PR or issue status
- resort to `gh` only when `ghr` cannot provide the needed read cleanly
- `pr-search-cli` is candidate generation and ranking
- use it to suggest likely duplicate PRs or issue-cluster context
- do not treat it as final truth
- do not create or expand a duplicate group only because `pr-search-cli` put multiple PRs in the same issue or duplicate cluster
- `prtags` is the maintainer curation layer
- use it to create or reuse one duplicate group
- use it to save the duplicate status, confidence, rationale, and group summary
@@ -146,6 +182,7 @@ Examples:
## Evidence Checklist
Before declaring a duplicate, gather evidence from at least two categories.
Same-issue or same-cluster output from `pr-search-cli` counts only as candidate generation, not as one of the required proof categories by itself.
For PRs:
@@ -168,6 +205,7 @@ If you only have wording similarity, that is not enough.
## Step 1: Read The Target
Start by reading the target itself.
Use `ghr` first for this step even if you would normally reach for `gh`.
For a PR:
@@ -197,6 +235,7 @@ Record:
## Step 2: Search Broadly With ghreplica
Use `ghreplica` first because it is the most direct evidence source.
Do not switch to `gh` for ordinary reads unless `ghr` is missing data or failing.
### PR duplicate search
@@ -253,6 +292,9 @@ Interpretation:
- `issues for-pr` shows which issue clusters the PR appears to belong to
- `issues duplicate-prs` is useful for spotting already-known duplicate PR patterns
Treat every `pr-search-cli` result as a hint to investigate, not as enough evidence to create or widen a duplicate group.
Multiple PRs can share the same issue or issue cluster while still taking meaningfully different fix paths.
For an issue:
- use `ghreplica` first to find candidate PRs or issue wording
@@ -302,6 +344,9 @@ Reuse an existing group when:
- it already contains clearly related members
- adding the target would keep the group coherent
Do not widen an existing group just because `pr-search-cli` placed several PRs under the same issue or duplicate cluster.
Confirm that the actual implementation path and maintainer intent still match before adding the new member.
Create a new group only when no existing group clearly fits.
Create the group with a problem-based title and an intent-based description:
@@ -378,6 +423,9 @@ prtags annotation group set <group-id> \
When the evidence is incomplete, set `duplicate_status=candidate` and lower the confidence.
If a per-PR or per-issue annotation write fails because `prtags` cannot resolve the target through `ghreplica`, do not force a fallback write path.
Keep the group state you were able to write, report that the mirror is still missing the target object, and defer the target-level annotation until `ghreplica` catches up.
## Step 8: Let prtags Sync The Group Comment
Do not tell the agent to create a GitHub comment directly.

View File

@@ -54,6 +54,8 @@ OPENCLAW_GATEWAY_TOKEN=
# Optional additional providers
# ZAI_API_KEY=...
# AI_GATEWAY_API_KEY=...
# TOKENHUB_API_KEY=...
# LKEAP_API_KEY=...
# MINIMAX_API_KEY=...
# SYNTHETIC_API_KEY=...

View File

@@ -14,7 +14,7 @@ inputs:
pnpm-version:
description: pnpm version for corepack.
required: false
default: "10.32.1"
default: "10.33.0"
install-bun:
description: Whether to install Bun alongside Node.
required: false

View File

@@ -4,7 +4,7 @@ inputs:
pnpm-version:
description: pnpm version to activate via corepack.
required: false
default: "10.32.1"
default: "10.33.0"
cache-key-suffix:
description: Suffix appended to the cache key.
required: false

8
.github/labeler.yml vendored
View File

@@ -241,6 +241,10 @@
- changed-files:
- any-glob-to-any-file:
- "extensions/open-prose/**"
"extensions: tokenjuice":
- changed-files:
- any-glob-to-any-file:
- "extensions/tokenjuice/**"
"extensions: webhooks":
- changed-files:
- any-glob-to-any-file:
@@ -269,6 +273,10 @@
- changed-files:
- any-glob-to-any-file:
- "extensions/deepseek/**"
"extensions: tencent":
- changed-files:
- any-glob-to-any-file:
- "extensions/tencent/**"
"extensions: stepfun":
- changed-files:
- any-glob-to-any-file:

View File

@@ -22,22 +22,22 @@ jobs:
permissions:
issues: write
pull-requests: write
runs-on: blacksmith-16vcpu-ubuntu-2404
runs-on: ubuntu-24.04
steps:
- uses: actions/create-github-app-token@v2
- uses: actions/create-github-app-token@v3
id: app-token
continue-on-error: true
with:
app-id: "2729701"
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- uses: actions/create-github-app-token@v2
- uses: actions/create-github-app-token@v3
id: app-token-fallback
if: steps.app-token.outcome == 'failure'
with:
app-id: "2971289"
private-key: ${{ secrets.GH_APP_PRIVATE_KEY_FALLBACK }}
- name: Handle labeled items
uses: actions/github-script@v8
uses: actions/github-script@v9
with:
github-token: ${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token }}
script: |

100
.github/workflows/ci-check-testbox.yml vendored Normal file
View File

@@ -0,0 +1,100 @@
name: Blacksmith Testbox
on:
workflow_dispatch:
inputs:
testbox_id:
type: string
description: "Testbox session ID"
required: true
pull_request:
paths:
- ".github/workflows/**"
permissions:
contents: read
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
jobs:
check:
permissions:
contents: read
name: "check"
runs-on: blacksmith-32vcpu-ubuntu-2404
timeout-minutes: 30
steps:
- name: Begin Testbox
uses: useblacksmith/begin-testbox@v2
with:
testbox_id: ${{ inputs.testbox_id }}
- name: Checkout
shell: bash
env:
CHECKOUT_REPO: ${{ github.repository }}
CHECKOUT_SHA: ${{ github.sha }}
CHECKOUT_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
workdir="$GITHUB_WORKSPACE"
auth_header="$(printf 'x-access-token:%s' "$CHECKOUT_TOKEN" | base64 | tr -d '\n')"
reset_checkout_dir() {
mkdir -p "$workdir"
find "$workdir" -mindepth 1 -maxdepth 1 -exec rm -rf {} +
}
checkout_attempt() {
local attempt="$1"
reset_checkout_dir
git init "$workdir" >/dev/null
git config --global --add safe.directory "$workdir"
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}"
git -C "$workdir" config gc.auto 0
timeout --signal=TERM 30s git -C "$workdir" \
-c protocol.version=2 \
-c "http.https://github.com/.extraheader=AUTHORIZATION: basic ${auth_header}" \
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
"+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1
git -C "$workdir" checkout --force --detach "$CHECKOUT_SHA" || return 1
test -f "$workdir/.github/actions/setup-node-env/action.yml" || return 1
echo "checkout attempt ${attempt}/2 succeeded"
}
for attempt in 1 2; do
if checkout_attempt "$attempt"; then
exit 0
fi
echo "checkout attempt ${attempt}/2 failed"
sleep $((attempt * 5))
done
echo "checkout failed after 2 attempts" >&2
exit 1
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
install-bun: "false"
- name: Prepare Testbox shell
shell: bash
run: |
set -euo pipefail
git fetch --no-tags --depth=50 origin "+refs/heads/main:refs/remotes/origin/main"
node_bin="$(dirname "$(node -p 'process.execPath')")"
pnpm_bin="$(command -v pnpm)"
sudo ln -sf "$node_bin/node" /usr/local/bin/node
sudo ln -sf "$node_bin/npm" /usr/local/bin/npm
sudo ln -sf "$node_bin/npx" /usr/local/bin/npx
sudo ln -sf "$node_bin/corepack" /usr/local/bin/corepack
sudo ln -sf "$pnpm_bin" /usr/local/bin/pnpm
- name: Run Testbox
uses: useblacksmith/run-testbox@v2
if: always()
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"

View File

@@ -10,7 +10,7 @@ permissions:
contents: read
concurrency:
group: ${{ github.event_name == 'pull_request' && format('{0}-{1}', github.workflow, github.event.pull_request.number) || (github.repository == 'openclaw/openclaw' && format('{0}-{1}', github.workflow, github.ref) || format('{0}-{1}-{2}', github.workflow, github.ref, github.sha)) }}
group: ${{ github.event_name == 'pull_request' && format('{0}-v7-{1}', github.workflow, github.event.pull_request.number) || (github.repository == 'openclaw/openclaw' && format('{0}-v7-{1}', github.workflow, github.ref) || format('{0}-v7-{1}-{2}', github.workflow, github.ref, github.sha)) }}
cancel-in-progress: true
env:
@@ -23,7 +23,7 @@ jobs:
permissions:
contents: read
if: github.event_name != 'pull_request' || !github.event.pull_request.draft
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-16vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
runs-on: ubuntu-24.04
timeout-minutes: 20
outputs:
docs_only: ${{ steps.manifest.outputs.docs_only }}
@@ -95,13 +95,6 @@ jobs:
node scripts/ci-changed-scope.mjs --base "$BASE" --head HEAD
- name: Setup Node environment
if: steps.docs_scope.outputs.docs_only != 'true'
uses: ./.github/actions/setup-node-env
with:
install-bun: "false"
install-deps: "false"
- name: Detect changed extensions
id: changed_extensions
if: steps.docs_scope.outputs.docs_only != 'true' && steps.changed_scope.outputs.run_node == 'true'
@@ -251,17 +244,6 @@ jobs:
runNode
? [
{ check_name: "checks-node-channels", runtime: "node", task: "channels" },
...(isPush
? [
{
check_name: "checks-node-compat-node22",
runtime: "node",
task: "compat-node22",
node_version: "22.18.0",
cache_key_suffix: "node22",
},
]
: []),
]
: [],
),
@@ -269,9 +251,9 @@ jobs:
checks_node_core_nondist_matrix: createMatrix(nodeTestNonDistShards),
run_checks_node_core_dist: nodeTestDistShards.length > 0,
checks_node_core_dist_matrix: createMatrix(nodeTestDistShards),
run_extension_fast: hasChangedExtensions,
run_extension_fast: hasChangedExtensions && !isPush,
extension_fast_matrix: createMatrix(
hasChangedExtensions
hasChangedExtensions && !isPush
? (changedExtensionsMatrix.include ?? []).map((entry) => ({
check_name: `extension-fast-${entry.extension}`,
extension: entry.extension,
@@ -302,7 +284,6 @@ jobs:
{ check_name: "android-test-play", task: "test-play" },
{ check_name: "android-test-third-party", task: "test-third-party" },
{ check_name: "android-build-play", task: "build-play" },
{ check_name: "android-build-third-party", task: "build-third-party" },
]
: [],
),
@@ -323,7 +304,7 @@ jobs:
permissions:
contents: read
if: github.event_name != 'pull_request' || !github.event.pull_request.draft
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-16vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
runs-on: ubuntu-24.04
timeout-minutes: 20
env:
PRE_COMMIT_HOME: .cache/pre-commit-security-fast
@@ -414,7 +395,7 @@ jobs:
permissions:
contents: read
if: github.event_name != 'pull_request' || !github.event.pull_request.draft
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-16vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- name: Checkout
@@ -437,8 +418,8 @@ jobs:
security-fast:
permissions: {}
needs: [security-scm-fast, security-dependency-audit]
if: always() && (github.event_name != 'pull_request' || !github.event.pull_request.draft)
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-16vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
if: ${{ !cancelled() && always() && (github.event_name != 'pull_request' || !github.event.pull_request.draft) }}
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- name: Verify fast security jobs
@@ -471,7 +452,7 @@ jobs:
contents: read
needs: [preflight]
if: needs.preflight.outputs.run_build_artifacts == 'true'
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-16vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-8vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
timeout-minutes: 20
steps:
- name: Checkout
@@ -543,15 +524,19 @@ jobs:
- name: Cache dist build
uses: actions/cache@v5
with:
path: dist/
path: |
dist/
dist-runtime/
key: ${{ runner.os }}-dist-build-${{ github.sha }}
- name: Upload dist artifact
- name: Pack built runtime artifacts
run: tar --posix -cf dist-runtime-build.tar.zst --use-compress-program zstdmt dist dist-runtime
- name: Upload built runtime artifacts
uses: actions/upload-artifact@v7
with:
name: dist-build
path: dist/
compression-level: 0
name: dist-runtime-build
path: dist-runtime-build.tar.zst
retention-days: 1
- name: Upload A2UI bundle artifact
@@ -562,13 +547,28 @@ jobs:
include-hidden-files: true
retention-days: 1
- name: Smoke test CLI launcher help
run: node openclaw.mjs --help
- name: Smoke test CLI launcher status json
run: node openclaw.mjs status --json --timeout 1
- name: Smoke test built bundled plugin singleton
run: pnpm test:build:singleton
- name: Smoke test built bundled runtime deps
run: pnpm test:build:bundled-runtime-deps
- name: Check CLI startup memory
run: pnpm test:startup:memory
checks-fast-core:
permissions:
contents: read
name: ${{ matrix.check_name }}
needs: [preflight]
if: needs.preflight.outputs.run_checks_fast == 'true'
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-16vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
runs-on: ubuntu-24.04
timeout-minutes: 60
strategy:
fail-fast: false
@@ -656,7 +656,7 @@ jobs:
name: ${{ matrix.checkName }}
needs: [preflight]
if: needs.preflight.outputs.run_checks_fast == 'true'
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-16vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
runs-on: ubuntu-24.04
timeout-minutes: 60
strategy:
fail-fast: false
@@ -739,8 +739,8 @@ jobs:
contents: read
name: checks-fast-contracts-channels
needs: [preflight, checks-fast-channel-contracts-shard]
if: always() && needs.preflight.outputs.run_checks_fast == 'true'
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-16vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
if: ${{ !cancelled() && always() && needs.preflight.outputs.run_checks_fast == 'true' }}
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- name: Verify channel contract shards
@@ -762,7 +762,7 @@ jobs:
name: "checks-fast-protocol"
needs: [preflight]
if: needs.preflight.outputs.run_checks_fast == 'true'
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-16vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
runs-on: ubuntu-24.04
timeout-minutes: 30
steps:
- name: Checkout
@@ -827,7 +827,7 @@ jobs:
name: ${{ matrix.check_name }}
needs: [preflight]
if: needs.preflight.outputs.run_checks_fast == 'true'
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-16vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-8vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
timeout-minutes: 60
strategy:
fail-fast: false
@@ -888,6 +888,7 @@ jobs:
- name: Run extension shard
env:
OPENCLAW_EXTENSION_BATCH_PARALLEL: 2
OPENCLAW_EXTENSION_BATCH: ${{ matrix.extensions_csv }}
run: pnpm test:extensions:batch -- "$OPENCLAW_EXTENSION_BATCH"
@@ -896,8 +897,8 @@ jobs:
contents: read
name: checks-node-extensions
needs: [preflight, checks-node-extensions-shard]
if: always() && needs.preflight.outputs.run_checks_fast == 'true'
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-16vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
if: ${{ !cancelled() && always() && needs.preflight.outputs.run_checks_fast == 'true' }}
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- name: Verify extension shards
@@ -914,19 +915,14 @@ jobs:
contents: read
name: ${{ matrix.check_name }}
needs: [preflight, build-artifacts]
if: always() && needs.preflight.outputs.run_checks == 'true' && needs.build-artifacts.result == 'success'
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-16vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
if: ${{ !cancelled() && always() && needs.preflight.outputs.run_checks == 'true' && needs.build-artifacts.result == 'success' }}
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-8vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
timeout-minutes: 60
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.preflight.outputs.checks_matrix) }}
steps:
- name: Skip compatibility lanes on pull requests
if: github.event_name == 'pull_request' && matrix.task == 'compat-node22'
run: echo "Skipping push-only lane on pull requests."
- name: Checkout
if: github.event_name != 'pull_request' || matrix.task != 'compat-node22'
shell: bash
env:
CHECKOUT_REPO: ${{ github.repository }}
@@ -975,7 +971,6 @@ jobs:
exit 1
- name: Setup Node environment
if: github.event_name != 'pull_request' || matrix.task != 'compat-node22'
uses: ./.github/actions/setup-node-env
with:
node-version: "${{ matrix.node_version || '24.x' }}"
@@ -983,7 +978,7 @@ jobs:
install-bun: "false"
- name: Configure Node test resources
if: (github.event_name != 'pull_request' || matrix.task != 'compat-node22') && matrix.runtime == 'node' && (matrix.task == 'test' || matrix.task == 'channels' || matrix.task == 'compat-node22')
if: matrix.runtime == 'node' && (matrix.task == 'test' || matrix.task == 'channels')
env:
TASK: ${{ matrix.task }}
run: |
@@ -996,12 +991,21 @@ jobs:
echo "OPENCLAW_VITEST_MAX_WORKERS=1" >> "$GITHUB_ENV"
fi
- name: Download dist artifact
- name: Restore dist cache
if: matrix.task == 'test'
uses: actions/download-artifact@v8
id: checks-dist-cache
uses: actions/cache@v5
with:
name: dist-build
path: dist/
path: |
dist/
dist-runtime/
key: ${{ runner.os }}-dist-build-${{ github.sha }}
- name: Verify dist cache
if: matrix.task == 'test' && steps.checks-dist-cache.outputs.cache-hit != 'true'
run: |
echo "Missing same-run dist cache for ${RUNNER_OS}-dist-build-${GITHUB_SHA}" >&2
exit 1
- name: Download A2UI bundle artifact
if: matrix.task == 'test' || matrix.task == 'channels'
@@ -1011,7 +1015,6 @@ jobs:
path: src/canvas-host/a2ui/
- name: Run ${{ matrix.task }} (${{ matrix.runtime }})
if: github.event_name != 'pull_request' || matrix.task != 'compat-node22'
env:
TASK: ${{ matrix.task }}
NODE_OPTIONS: --max-old-space-size=6144
@@ -1025,26 +1028,96 @@ jobs:
channels)
pnpm test:channels
;;
compat-node22)
pnpm build
pnpm ui:build
node openclaw.mjs --help
node openclaw.mjs status --json --timeout 1
pnpm test:build:singleton
;;
*)
echo "Unsupported checks task: $TASK" >&2
exit 1
;;
esac
checks-node-compat:
permissions:
contents: read
name: checks-node-compat-node22
needs: [preflight]
if: needs.preflight.outputs.run_node == 'true' && github.event_name == 'push'
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-8vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
timeout-minutes: 60
steps:
- name: Checkout
shell: bash
env:
CHECKOUT_REPO: ${{ github.repository }}
CHECKOUT_SHA: ${{ github.sha }}
CHECKOUT_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
workdir="$GITHUB_WORKSPACE"
auth_header="$(printf 'x-access-token:%s' "$CHECKOUT_TOKEN" | base64 | tr -d '\n')"
reset_checkout_dir() {
mkdir -p "$workdir"
find "$workdir" -mindepth 1 -maxdepth 1 -exec rm -rf {} +
}
checkout_attempt() {
local attempt="$1"
reset_checkout_dir
git init "$workdir" >/dev/null
git config --global --add safe.directory "$workdir"
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}"
git -C "$workdir" config gc.auto 0
timeout --signal=TERM 30s git -C "$workdir" \
-c protocol.version=2 \
-c "http.https://github.com/.extraheader=AUTHORIZATION: basic ${auth_header}" \
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
"+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1
git -C "$workdir" checkout --force --detach "$CHECKOUT_SHA" || return 1
test -f "$workdir/.github/actions/setup-node-env/action.yml" || return 1
echo "checkout attempt ${attempt}/2 succeeded"
}
for attempt in 1 2; do
if checkout_attempt "$attempt"; then
exit 0
fi
echo "checkout attempt ${attempt}/2 failed"
sleep $((attempt * 5))
done
echo "checkout failed after 2 attempts" >&2
exit 1
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
node-version: "22.18.0"
cache-key-suffix: "node22"
install-bun: "false"
- name: Configure Node test resources
run: echo "OPENCLAW_VITEST_MAX_WORKERS=2" >> "$GITHUB_ENV"
- name: Run Node 22 compatibility
env:
NODE_OPTIONS: --max-old-space-size=6144
run: |
pnpm build
pnpm ui:build
node openclaw.mjs --help
node openclaw.mjs status --json --timeout 1
pnpm test:build:singleton
checks-node-core-test-nondist-shard:
permissions:
contents: read
name: ${{ matrix.check_name }}
needs: [preflight]
if: needs.preflight.outputs.run_checks_node_core_nondist == 'true'
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-16vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-8vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
timeout-minutes: 60
strategy:
fail-fast: false
@@ -1167,8 +1240,8 @@ jobs:
contents: read
name: ${{ matrix.check_name }}
needs: [preflight, build-artifacts]
if: always() && needs.preflight.outputs.run_checks_node_core_dist == 'true' && needs.build-artifacts.result == 'success'
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-16vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
if: ${{ !cancelled() && always() && needs.preflight.outputs.run_checks_node_core_dist == 'true' && needs.build-artifacts.result == 'success' }}
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-8vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
timeout-minutes: 60
strategy:
fail-fast: false
@@ -1236,15 +1309,16 @@ jobs:
id: dist-cache
uses: actions/cache@v5
with:
path: dist/
path: |
dist/
dist-runtime/
key: ${{ runner.os }}-dist-build-${{ github.sha }}
- name: Download dist artifact
- name: Verify dist cache
if: steps.dist-cache.outputs.cache-hit != 'true'
uses: actions/download-artifact@v8
with:
name: dist-build
path: dist/
run: |
echo "Missing same-run dist cache for ${RUNNER_OS}-dist-build-${GITHUB_SHA}" >&2
exit 1
- name: Download A2UI bundle artifact
uses: actions/download-artifact@v8
@@ -1311,8 +1385,8 @@ jobs:
contents: read
name: checks-node-core
needs: [preflight, checks-node-core-test-nondist-shard, checks-node-core-test-dist-shard]
if: always() && needs.preflight.outputs.run_checks == 'true'
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-16vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
if: ${{ !cancelled() && always() && needs.preflight.outputs.run_checks == 'true' }}
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- name: Verify node test shards
@@ -1337,7 +1411,7 @@ jobs:
name: "extension-fast"
needs: [preflight]
if: needs.preflight.outputs.run_extension_fast == 'true'
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-16vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-8vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
timeout-minutes: 60
strategy:
fail-fast: false
@@ -1407,8 +1481,8 @@ jobs:
contents: read
name: ${{ matrix.check_name }}
needs: [preflight]
if: always() && needs.preflight.outputs.run_check == 'true'
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-16vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
if: ${{ !cancelled() && always() && needs.preflight.outputs.run_check == 'true' }}
runs-on: ${{ github.repository == 'openclaw/openclaw' && matrix.runner || 'ubuntu-24.04' }}
timeout-minutes: 20
strategy:
fail-fast: false
@@ -1416,16 +1490,22 @@ jobs:
include:
- check_name: check-preflight-guards
task: preflight-guards
runner: ubuntu-24.04
- check_name: check-prod-types
task: prod-types
runner: ubuntu-24.04
- check_name: check-lint
task: lint
runner: blacksmith-16vcpu-ubuntu-2404
- check_name: check-policy-guards
task: policy-guards
runner: ubuntu-24.04
- check_name: check-test-types
task: test-types
runner: ubuntu-24.04
- check_name: check-strict-smoke
task: strict-smoke
runner: ubuntu-24.04
steps:
- name: Checkout
shell: bash
@@ -1522,8 +1602,8 @@ jobs:
contents: read
name: "check"
needs: [preflight, check-shard]
if: always() && needs.preflight.outputs.run_check == 'true'
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-16vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
if: ${{ !cancelled() && always() && needs.preflight.outputs.run_check == 'true' }}
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- name: Verify check shards
@@ -1540,8 +1620,8 @@ jobs:
contents: read
name: ${{ matrix.check_name }}
needs: [preflight]
if: always() && needs.preflight.outputs.run_check_additional == 'true'
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-16vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
if: ${{ !cancelled() && always() && needs.preflight.outputs.run_check_additional == 'true' }}
runs-on: ubuntu-24.04
timeout-minutes: 20
strategy:
fail-fast: false
@@ -1553,12 +1633,8 @@ jobs:
group: extension-channels
- check_name: check-additional-extension-bundled
group: extension-bundled
- check_name: check-additional-extension-package-boundary-compile
group: extension-package-boundary-compile
- check_name: check-additional-extension-package-boundary-canary
group: extension-package-boundary-canary
- check_name: check-additional-runtime-topology-gateway
group: runtime-topology-gateway
- check_name: check-additional-extension-package-boundary
group: extension-package-boundary
- check_name: check-additional-runtime-topology-architecture
group: runtime-topology-architecture
steps:
@@ -1617,7 +1693,7 @@ jobs:
- name: Cache extension package boundary artifacts
id: extension-package-boundary-cache
if: matrix.group == 'extension-package-boundary-compile'
if: matrix.group == 'extension-package-boundary'
uses: actions/cache@v5
with:
path: |
@@ -1625,12 +1701,12 @@ jobs:
packages/plugin-sdk/dist
extensions/*/dist/.boundary-tsc.tsbuildinfo
extensions/*/dist/.boundary-tsc.stamp
key: ${{ runner.os }}-extension-package-boundary-v1-${{ hashFiles('tsconfig.json', 'tsconfig.plugin-sdk.dts.json', 'packages/plugin-sdk/tsconfig.json', 'scripts/check-extension-package-tsc-boundary.mjs', 'scripts/prepare-extension-package-boundary-artifacts.mjs', 'scripts/write-plugin-sdk-entry-dts.ts', 'scripts/lib/plugin-sdk-entrypoints.json', 'scripts/lib/plugin-sdk-entries.mjs', 'src/**', 'extensions/**', 'extensions/tsconfig.package-boundary*.json', 'package.json', 'pnpm-lock.yaml') }}
key: ${{ runner.os }}-extension-package-boundary-v1-${{ hashFiles('tsconfig.json', 'tsconfig.plugin-sdk.dts.json', 'packages/plugin-sdk/tsconfig.json', 'scripts/check-extension-package-tsc-boundary.mjs', 'scripts/prepare-extension-package-boundary-artifacts.mjs', 'scripts/write-plugin-sdk-entry-dts.ts', 'scripts/lib/plugin-sdk-entrypoints.json', 'scripts/lib/plugin-sdk-entries.mjs', 'src/plugin-sdk/**', 'src/video-generation/dashscope-compatible.ts', 'src/video-generation/types.ts', 'src/types/**', 'extensions/**', 'extensions/tsconfig.package-boundary*.json', 'package.json', 'pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-extension-package-boundary-v1-
- name: Preserve extension package boundary cache hit
if: matrix.group == 'extension-package-boundary-compile' && steps.extension-package-boundary-cache.outputs.cache-hit == 'true'
if: matrix.group == 'extension-package-boundary' && steps.extension-package-boundary-cache.outputs.cache-hit == 'true'
shell: bash
run: |
set -euo pipefail
@@ -1658,6 +1734,7 @@ jobs:
env:
ADDITIONAL_CHECK_GROUP: ${{ matrix.group }}
RUN_CONTROL_UI_I18N: ${{ needs.preflight.outputs.run_control_ui_i18n }}
OPENCLAW_ADDITIONAL_BOUNDARY_CONCURRENCY: 4
OPENCLAW_EXTENSION_BOUNDARY_CONCURRENCY: 6
shell: bash
run: |
@@ -1681,24 +1758,7 @@ jobs:
case "$ADDITIONAL_CHECK_GROUP" in
boundaries)
run_check "plugin-extension-boundary" pnpm run lint:plugins:no-extension-imports
run_check "lint:tmp:no-random-messaging" pnpm run lint:tmp:no-random-messaging
run_check "lint:tmp:channel-agnostic-boundaries" pnpm run lint:tmp:channel-agnostic-boundaries
run_check "lint:tmp:tsgo-core-boundary" pnpm run lint:tmp:tsgo-core-boundary
run_check "lint:tmp:no-raw-channel-fetch" pnpm run lint:tmp:no-raw-channel-fetch
run_check "lint:agent:ingress-owner" pnpm run lint:agent:ingress-owner
run_check "lint:plugins:no-register-http-handler" pnpm run lint:plugins:no-register-http-handler
run_check "lint:plugins:no-monolithic-plugin-sdk-entry-imports" pnpm run lint:plugins:no-monolithic-plugin-sdk-entry-imports
run_check "lint:plugins:no-extension-src-imports" pnpm run lint:plugins:no-extension-src-imports
run_check "lint:plugins:no-extension-test-core-imports" pnpm run lint:plugins:no-extension-test-core-imports
run_check "lint:plugins:plugin-sdk-subpaths-exported" pnpm run lint:plugins:plugin-sdk-subpaths-exported
run_check "deps:root-ownership:check" pnpm deps:root-ownership:check
run_check "web-search-provider-boundary" pnpm run lint:web-search-provider-boundaries
run_check "web-fetch-provider-boundary" pnpm run lint:web-fetch-provider-boundaries
run_check "extension-src-outside-plugin-sdk-boundary" pnpm run lint:extensions:no-src-outside-plugin-sdk
run_check "extension-plugin-sdk-internal-boundary" pnpm run lint:extensions:no-plugin-sdk-internal
run_check "extension-relative-outside-package-boundary" pnpm run lint:extensions:no-relative-outside-package
run_check "lint:ui:no-raw-window-open" pnpm lint:ui:no-raw-window-open
node scripts/run-additional-boundary-checks.mjs
;;
extension-channels)
run_check "lint:extensions:channels" pnpm run lint:extensions:channels
@@ -1706,18 +1766,10 @@ jobs:
extension-bundled)
run_check "lint:extensions:bundled" pnpm run lint:extensions:bundled
;;
extension-package-boundary-compile)
extension-package-boundary)
run_check "test:extensions:package-boundary:compile" pnpm run test:extensions:package-boundary:compile
;;
extension-package-boundary-canary)
run_check "test:extensions:package-boundary:canary" pnpm run test:extensions:package-boundary:canary
;;
runtime-topology-gateway)
if [ "$RUN_CONTROL_UI_I18N" = "true" ]; then
run_check "ui:i18n:check" pnpm ui:i18n:check
fi
run_check "gateway-watch-regression" pnpm test:gateway:watch-regression
;;
runtime-topology-architecture)
run_check "check:architecture" pnpm check:architecture
;;
@@ -1729,39 +1781,13 @@ jobs:
exit "$failures"
- name: Upload gateway watch regression artifacts
if: always() && matrix.group == 'runtime-topology-gateway'
uses: actions/upload-artifact@v7
with:
name: gateway-watch-regression
path: .local/gateway-watch-regression/
retention-days: 7
check-additional:
check-additional-runtime-topology-gateway:
permissions:
contents: read
name: "check-additional"
needs: [preflight, check-additional-shard]
if: always() && needs.preflight.outputs.run_check_additional == 'true'
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-16vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
timeout-minutes: 5
steps:
- name: Verify additional check shards
env:
SHARD_RESULT: ${{ needs.check-additional-shard.result }}
run: |
if [ "$SHARD_RESULT" != "success" ]; then
echo "Additional check shards failed: $SHARD_RESULT" >&2
exit 1
fi
build-smoke:
permissions:
contents: read
name: "build-smoke"
name: "check-additional-runtime-topology-gateway"
needs: [preflight, build-artifacts]
if: always() && needs.preflight.outputs.run_build_smoke == 'true' && (github.event_name != 'push' || needs.build-artifacts.result == 'success')
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-16vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
if: ${{ !cancelled() && always() && needs.preflight.outputs.run_check_additional == 'true' && needs.build-artifacts.result == 'success' }}
runs-on: ubuntu-24.04
timeout-minutes: 20
steps:
- name: Checkout
@@ -1817,39 +1843,74 @@ jobs:
with:
install-bun: "false"
- name: Restore dist cache
id: build-smoke-dist-cache
if: github.event_name == 'push'
uses: actions/cache@v5
with:
path: dist/
key: ${{ runner.os }}-dist-build-${{ github.sha }}
- name: Download dist artifact
if: github.event_name == 'push' && steps.build-smoke-dist-cache.outputs.cache-hit != 'true'
- name: Download built runtime artifacts
uses: actions/download-artifact@v8
with:
name: dist-build
path: dist/
name: dist-runtime-build
path: .local/dist-runtime-build
- name: Build dist
if: github.event_name != 'push'
run: pnpm build
- name: Restore built runtime artifacts
run: |
tar -xf .local/dist-runtime-build/dist-runtime-build.tar.zst --use-compress-program unzstd
test -f dist/entry.js
test -f dist/.buildstamp
test -d dist-runtime
- name: Smoke test CLI launcher help
run: node openclaw.mjs --help
- name: Check Control UI i18n
if: needs.preflight.outputs.run_control_ui_i18n == 'true'
run: pnpm ui:i18n:check
- name: Smoke test CLI launcher status json
run: node openclaw.mjs status --json --timeout 1
- name: Run gateway watch regression
run: node scripts/check-gateway-watch-regression.mjs --skip-build
- name: Smoke test built bundled plugin singleton
run: pnpm test:build:singleton
- name: Upload gateway watch regression artifacts
if: always()
uses: actions/upload-artifact@v7
with:
name: gateway-watch-regression
path: .local/gateway-watch-regression/
retention-days: 7
- name: Smoke test built bundled runtime deps
run: pnpm test:build:bundled-runtime-deps
check-additional:
permissions:
contents: read
name: "check-additional"
needs: [preflight, check-additional-shard, check-additional-runtime-topology-gateway]
if: ${{ !cancelled() && always() && needs.preflight.outputs.run_check_additional == 'true' }}
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- name: Verify additional check shards
env:
SHARD_RESULT: ${{ needs.check-additional-shard.result }}
GATEWAY_RESULT: ${{ needs.check-additional-runtime-topology-gateway.result }}
run: |
if [ "$SHARD_RESULT" != "success" ]; then
echo "Additional check shards failed: $SHARD_RESULT" >&2
exit 1
fi
if [ "$GATEWAY_RESULT" != "success" ]; then
echo "Gateway topology check failed: $GATEWAY_RESULT" >&2
exit 1
fi
- name: Check CLI startup memory
run: pnpm test:startup:memory
build-smoke:
permissions:
contents: read
name: "build-smoke"
needs: [preflight, build-artifacts]
if: ${{ !cancelled() && always() && needs.preflight.outputs.run_build_smoke == 'true' && (github.event_name != 'push' || needs.build-artifacts.result == 'success') }}
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- name: Verify build smoke
env:
BUILD_ARTIFACTS_RESULT: ${{ needs.build-artifacts.result }}
run: |
if [ "$BUILD_ARTIFACTS_RESULT" != "success" ]; then
echo "Build smoke checks failed in build-artifacts: $BUILD_ARTIFACTS_RESULT" >&2
exit 1
fi
# Validate docs (format, lint, broken links) only when docs files changed.
check-docs:
@@ -1857,7 +1918,7 @@ jobs:
contents: read
needs: [preflight]
if: needs.preflight.outputs.run_check_docs == 'true'
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-16vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
runs-on: ubuntu-24.04
timeout-minutes: 20
steps:
- name: Checkout
@@ -1921,7 +1982,7 @@ jobs:
contents: read
needs: [preflight]
if: needs.preflight.outputs.run_skills_python_job == 'true'
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-16vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
runs-on: ubuntu-24.04
timeout-minutes: 20
steps:
- name: Checkout
@@ -1952,11 +2013,11 @@ jobs:
name: ${{ matrix.check_name }}
needs: [preflight]
if: needs.preflight.outputs.run_checks_windows == 'true'
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-32vcpu-windows-2025' || 'windows-2025' }}
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-16vcpu-windows-2025' || 'windows-2025' }}
timeout-minutes: 60
env:
NODE_OPTIONS: --max-old-space-size=6144
# Keep total concurrency predictable on the 32 vCPU runner.
# Keep total concurrency predictable on the smaller Windows runner.
OPENCLAW_VITEST_MAX_WORKERS: 1
OPENCLAW_TEST_SKIP_FULL_EXTENSIONS_SHARD: 1
defaults:
@@ -2000,7 +2061,7 @@ jobs:
- name: Setup pnpm + cache store
uses: ./.github/actions/setup-pnpm-store-cache
with:
pnpm-version: "10.32.1"
pnpm-version: "10.33.0"
cache-key-suffix: "node24"
use-restore-keys: "false"
use-actions-cache: "true"
@@ -2053,9 +2114,9 @@ jobs:
permissions:
contents: read
name: ${{ matrix.check_name }}
needs: [preflight, build-artifacts]
if: always() && needs.preflight.outputs.run_macos_node == 'true' && needs.build-artifacts.result == 'success'
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-12vcpu-macos-latest' || 'macos-latest' }}
needs: [preflight]
if: ${{ !cancelled() && always() && needs.preflight.outputs.run_macos_node == 'true' }}
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-6vcpu-macos-latest' || 'macos-latest' }}
timeout-minutes: 20
strategy:
fail-fast: false
@@ -2072,18 +2133,6 @@ jobs:
with:
install-bun: "false"
- name: Download dist artifact
uses: actions/download-artifact@v8
with:
name: dist-build
path: dist/
- name: Download A2UI bundle artifact
uses: actions/download-artifact@v8
with:
name: canvas-a2ui-bundle
path: src/canvas-host/a2ui/
- name: TS tests (macOS)
env:
NODE_OPTIONS: --max-old-space-size=4096
@@ -2206,7 +2255,7 @@ jobs:
name: ${{ matrix.check_name }}
needs: [preflight]
if: needs.preflight.outputs.run_android_job == 'true'
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-16vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-8vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
timeout-minutes: 20
strategy:
fail-fast: false
@@ -2316,9 +2365,6 @@ jobs:
build-play)
./gradlew --no-daemon --build-cache :app:assemblePlayDebug
;;
build-third-party)
./gradlew --no-daemon --build-cache :app:assembleThirdPartyDebug
;;
*)
echo "Unsupported Android task: $TASK" >&2
exit 1

View File

@@ -153,7 +153,7 @@ jobs:
- name: Build and push amd64 image
id: build
# WARNING: KEEP THE OFFICIAL DOCKER ACTION HERE; DO NOT SWITCH THIS BACK TO BLACKSMITH BLINDLY.
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
with:
context: .
platforms: linux/amd64
@@ -167,7 +167,7 @@ jobs:
- name: Build and push amd64 slim image
id: build-slim
# WARNING: KEEP THE OFFICIAL DOCKER ACTION HERE; DO NOT SWITCH THIS BACK TO BLACKSMITH BLINDLY.
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
with:
context: .
platforms: linux/amd64
@@ -270,7 +270,7 @@ jobs:
- name: Build and push arm64 image
id: build
# WARNING: KEEP THE OFFICIAL DOCKER ACTION HERE; DO NOT SWITCH THIS BACK TO BLACKSMITH BLINDLY.
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
with:
context: .
platforms: linux/arm64
@@ -284,7 +284,7 @@ jobs:
- name: Build and push arm64 slim image
id: build-slim
# WARNING: KEEP THE OFFICIAL DOCKER ACTION HERE; DO NOT SWITCH THIS BACK TO BLACKSMITH BLINDLY.
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
with:
context: .
platforms: linux/arm64

View File

@@ -23,7 +23,7 @@ jobs:
fetch-depth: 0
- name: Setup Node
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: "22.18.0"

View File

@@ -11,8 +11,8 @@ permissions:
contents: read
concurrency:
group: ${{ github.event_name == 'pull_request' && format('{0}-{1}', github.workflow, github.event.pull_request.number) || format('{0}-{1}', github.workflow, github.run_id) }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
group: ${{ github.event_name == 'pull_request' && format('{0}-{1}', github.workflow, github.event.pull_request.number) || format('{0}-{1}', github.workflow, github.ref) }}
cancel-in-progress: true
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
@@ -20,7 +20,7 @@ env:
jobs:
preflight:
if: github.event_name != 'pull_request' || !github.event.pull_request.draft
runs-on: blacksmith-16vcpu-ubuntu-2404
runs-on: ubuntu-24.04
outputs:
docs_only: ${{ steps.manifest.outputs.docs_only }}
run_install_smoke: ${{ steps.manifest.outputs.run_install_smoke }}
@@ -58,13 +58,6 @@ jobs:
node scripts/ci-changed-scope.mjs --base "$BASE" --head HEAD
- name: Setup Node environment
if: steps.docs_scope.outputs.docs_only != 'true'
uses: ./.github/actions/setup-node-env
with:
install-bun: "false"
install-deps: "false"
- name: Build install-smoke CI manifest
id: manifest
env:
@@ -85,7 +78,7 @@ jobs:
install-smoke:
needs: [preflight]
if: needs.preflight.outputs.run_install_smoke == 'true'
runs-on: blacksmith-32vcpu-ubuntu-2404
runs-on: blacksmith-16vcpu-ubuntu-2404
env:
DOCKER_BUILD_SUMMARY: "false"
DOCKER_BUILD_RECORD_UPLOAD: "false"
@@ -98,6 +91,11 @@ jobs:
# Blacksmith's builder owns the Docker layer cache; keep smoke builds off
# explicit gha cache directives so local tags still load cleanly.
- name: Run QR package install smoke
env:
OPENCLAW_QR_SMOKE_FORCE_INSTALL: "1"
run: bash scripts/e2e/qr-import-docker.sh
- name: Build root Dockerfile smoke image
uses: useblacksmith/build-push-action@cbd1f60d194a98cb3be5523b15134501eaf0fbf3 # v2
with:
@@ -114,6 +112,12 @@ jobs:
run: |
docker run --rm --entrypoint sh openclaw-dockerfile-smoke:local -lc 'which openclaw && openclaw --version'
- name: Run Docker gateway network e2e
env:
OPENCLAW_GATEWAY_NETWORK_E2E_IMAGE: openclaw-dockerfile-smoke:local
OPENCLAW_GATEWAY_NETWORK_E2E_SKIP_BUILD: "1"
run: bash scripts/e2e/gateway-network-docker.sh
# This smoke validates that the build-arg path preinstalls the matrix
# runtime deps declared by the plugin and that matrix discovery stays
# healthy in the final runtime image.
@@ -215,3 +219,29 @@ jobs:
OPENCLAW_INSTALL_SMOKE_UPDATE_DIST_IMAGE: openclaw-dockerfile-smoke:local
OPENCLAW_INSTALL_SMOKE_UPDATE_SKIP_LOCAL_BUILD: "1"
run: bash scripts/test-install-sh-docker.sh
docker-e2e-fast:
needs: [preflight]
if: needs.preflight.outputs.run_install_smoke == 'true'
runs-on: blacksmith-16vcpu-ubuntu-2404
timeout-minutes: 8
env:
DOCKER_BUILD_SUMMARY: "false"
DOCKER_BUILD_RECORD_UPLOAD: "false"
steps:
- name: Checkout CLI
uses: actions/checkout@v6
- name: Set up Blacksmith Docker Builder
uses: useblacksmith/setup-docker-builder@ac083cc84672d01c60d5e8561d0a939b697de542 # v1
- name: Setup Node environment for package smoke
uses: ./.github/actions/setup-node-env
with:
install-bun: "false"
install-deps: "true"
- name: Run fast bundled plugin Docker E2E
env:
OPENCLAW_BUNDLED_CHANNEL_DEPS_E2E_IMAGE: openclaw-bundled-channel-fast:local
run: timeout 120s pnpm test:docker:bundled-channel-deps:fast

View File

@@ -30,15 +30,15 @@ jobs:
permissions:
contents: read
pull-requests: write
runs-on: blacksmith-16vcpu-ubuntu-2404
runs-on: ubuntu-24.04
steps:
- uses: actions/create-github-app-token@v2
- uses: actions/create-github-app-token@v3
id: app-token
continue-on-error: true
with:
app-id: "2729701"
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- uses: actions/create-github-app-token@v2
- uses: actions/create-github-app-token@v3
id: app-token-fallback
if: steps.app-token.outcome == 'failure'
with:
@@ -50,7 +50,7 @@ jobs:
repo-token: ${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token }}
sync-labels: true
- name: Apply PR size label
uses: actions/github-script@v8
uses: actions/github-script@v9
with:
github-token: ${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token }}
script: |
@@ -139,7 +139,7 @@ jobs:
labels: [targetSizeLabel],
});
- name: Apply maintainer or trusted-contributor label
uses: actions/github-script@v8
uses: actions/github-script@v9
with:
github-token: ${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token }}
script: |
@@ -210,7 +210,7 @@ jobs:
// });
// }
- name: Apply beta-blocker title label
uses: actions/github-script@v8
uses: actions/github-script@v9
with:
github-token: ${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token }}
script: |
@@ -263,7 +263,7 @@ jobs:
});
}
- name: Apply too-many-prs label
uses: actions/github-script@v8
uses: actions/github-script@v9
with:
github-token: ${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token }}
script: |
@@ -439,22 +439,22 @@ jobs:
permissions:
contents: read
pull-requests: write
runs-on: blacksmith-16vcpu-ubuntu-2404
runs-on: ubuntu-24.04
steps:
- uses: actions/create-github-app-token@v2
- uses: actions/create-github-app-token@v3
id: app-token
continue-on-error: true
with:
app-id: "2729701"
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- uses: actions/create-github-app-token@v2
- uses: actions/create-github-app-token@v3
id: app-token-fallback
if: steps.app-token.outcome == 'failure'
with:
app-id: "2971289"
private-key: ${{ secrets.GH_APP_PRIVATE_KEY_FALLBACK }}
- name: Backfill PR labels
uses: actions/github-script@v8
uses: actions/github-script@v9
with:
github-token: ${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token }}
script: |
@@ -737,22 +737,22 @@ jobs:
label-issues:
permissions:
issues: write
runs-on: blacksmith-16vcpu-ubuntu-2404
runs-on: ubuntu-24.04
steps:
- uses: actions/create-github-app-token@v2
- uses: actions/create-github-app-token@v3
id: app-token
continue-on-error: true
with:
app-id: "2729701"
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- uses: actions/create-github-app-token@v2
- uses: actions/create-github-app-token@v3
id: app-token-fallback
if: steps.app-token.outcome == 'failure'
with:
app-id: "2971289"
private-key: ${{ secrets.GH_APP_PRIVATE_KEY_FALLBACK }}
- name: Apply maintainer or trusted-contributor label
uses: actions/github-script@v8
uses: actions/github-script@v9
with:
github-token: ${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token }}
script: |
@@ -823,7 +823,7 @@ jobs:
// });
// }
- name: Apply beta-blocker title label
uses: actions/github-script@v8
uses: actions/github-script@v9
with:
github-token: ${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token }}
script: |

View File

@@ -222,7 +222,7 @@ jobs:
uses: actions/checkout@v6
with:
ref: ${{ needs.validate_selected_ref.outputs.selected_sha }}
fetch-depth: 0
fetch-depth: 1
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
@@ -258,7 +258,7 @@ jobs:
uses: actions/checkout@v6
with:
ref: ${{ needs.validate_selected_ref.outputs.selected_sha }}
fetch-depth: 0
fetch-depth: 1
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
@@ -303,7 +303,7 @@ jobs:
uses: actions/checkout@v6
with:
ref: ${{ needs.validate_selected_ref.outputs.selected_sha }}
fetch-depth: 0
fetch-depth: 1
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
@@ -460,7 +460,7 @@ jobs:
uses: actions/checkout@v6
with:
ref: ${{ needs.validate_selected_ref.outputs.selected_sha }}
fetch-depth: 0
fetch-depth: 1
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
@@ -602,7 +602,7 @@ jobs:
uses: actions/checkout@v6
with:
ref: ${{ needs.validate_selected_ref.outputs.selected_sha }}
fetch-depth: 0
fetch-depth: 1
- name: Setup Node environment
uses: ./.github/actions/setup-node-env

View File

@@ -25,8 +25,8 @@ jobs:
parity-gate:
name: Run the GPT-5.4 / Opus 4.6 parity gate against the qa-lab mock
if: ${{ github.event.pull_request.draft != true }}
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 20
runs-on: blacksmith-32vcpu-ubuntu-2404
timeout-minutes: 30
env:
# Fence the gate off from any real provider credentials. The qa-lab
# mock server + auth staging (PR N) should be enough to produce a
@@ -34,18 +34,24 @@ jobs:
# leak into the job env, fail hard instead of silently running
# against a live provider and burning real budget.
#
# The parity pack has 11 isolated scenario workers. Letting qa suite
# fan out to its default "all scenarios at once" mode on smaller CI
# VMs makes the short strict-agentic scenarios flaky, especially the
# approval-turn followthrough gate that expects a fast post-approval
# read within a 30s agent.wait timeout.
QA_PARITY_CONCURRENCY: "2"
# The parity pack has 11 isolated scenario workers. It exercises a real
# gateway child plus mock model turns and subagents, so keep it serial in
# CI even on the larger runner. Concurrent isolated gateway workers make
# the short strict-agentic scenarios flaky, especially the approval-turn
# followthrough gate that expects a fast post-approval read within a 30s
# agent.wait timeout.
QA_PARITY_CONCURRENCY: "1"
OPENCLAW_QA_TRANSPORT_READY_TIMEOUT_MS: "180000"
OPENAI_API_KEY: ""
ANTHROPIC_API_KEY: ""
OPENCLAW_LIVE_OPENAI_KEY: ""
OPENCLAW_LIVE_ANTHROPIC_KEY: ""
OPENCLAW_LIVE_GEMINI_KEY: ""
OPENCLAW_LIVE_SETUP_TOKEN_VALUE: ""
# The parity suite is a private QA command. Build that exact runtime up
# front so CI never tests a public dist plus a later no-clean QA overlay.
OPENCLAW_BUILD_PRIVATE_QA: "1"
OPENCLAW_ENABLE_PRIVATE_QA_CLI: "1"
steps:
- name: Checkout PR
uses: actions/checkout@v6
@@ -54,7 +60,7 @@ jobs:
uses: pnpm/action-setup@v4
- name: Setup Node
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: "22.18.0"
cache: "pnpm"
@@ -62,6 +68,12 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build private QA runtime
run: pnpm build
# The approval-turn sentinel still runs inside the full parity pack below.
# Keep the exact mock read-plan contract in deterministic unit tests instead
# of paying for a separate full-runtime preflight that has been flaky in CI.
- name: Run GPT-5.4 lane
run: |
pnpm openclaw qa suite \

View File

@@ -17,13 +17,13 @@ jobs:
pull-requests: write
runs-on: blacksmith-16vcpu-ubuntu-2404
steps:
- uses: actions/create-github-app-token@v2
- uses: actions/create-github-app-token@v3
id: app-token
continue-on-error: true
with:
app-id: "2729701"
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- uses: actions/create-github-app-token@v2
- uses: actions/create-github-app-token@v3
id: app-token-fallback
continue-on-error: true
with:
@@ -65,7 +65,7 @@ jobs:
- name: Check stale state cache
id: stale-state
if: always()
uses: actions/github-script@v8
uses: actions/github-script@v9
with:
github-token: ${{ steps.app-token-fallback.outputs.token || steps.app-token.outputs.token }}
script: |
@@ -124,13 +124,13 @@ jobs:
issues: write
runs-on: blacksmith-16vcpu-ubuntu-2404
steps:
- uses: actions/create-github-app-token@v2
- uses: actions/create-github-app-token@v3
id: app-token
with:
app-id: "2729701"
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- name: Lock closed issues after 48h of no comments
uses: actions/github-script@v8
uses: actions/github-script@v9
with:
github-token: ${{ steps.app-token.outputs.token }}
script: |

View File

@@ -11,7 +11,7 @@ permissions:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
cancel-in-progress: true
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
@@ -19,7 +19,7 @@ env:
jobs:
no-tabs:
if: github.event_name != 'workflow_dispatch'
runs-on: blacksmith-16vcpu-ubuntu-2404
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v6
@@ -51,7 +51,7 @@ jobs:
actionlint:
if: github.event_name != 'workflow_dispatch'
runs-on: blacksmith-16vcpu-ubuntu-2404
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v6
@@ -83,7 +83,7 @@ jobs:
generated-doc-baselines:
if: github.event_name == 'workflow_dispatch'
runs-on: blacksmith-16vcpu-ubuntu-2404
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v6

View File

@@ -1,73 +0,0 @@
---
description: Land a PR (merge with proper workflow)
---
Input
- PR: $1 <number|url>
- If missing: use the most recent PR mentioned in the conversation.
- If ambiguous: ask.
Do (end-to-end)
Goal: PR must end in GitHub state = MERGED (never CLOSED). Prefer `gh pr merge --squash`; use `--rebase` only when preserving commit history is required.
1. Assign PR to self:
- `gh pr edit <PR> --add-assignee @me`
2. Repo clean: `git status`.
3. Identify PR meta (author + head branch):
```sh
gh pr view <PR> --json number,title,author,headRefName,baseRefName,headRepository --jq '{number,title,author:.author.login,head:.headRefName,base:.baseRefName,headRepo:.headRepository.nameWithOwner}'
contrib=$(gh pr view <PR> --json author --jq .author.login)
head=$(gh pr view <PR> --json headRefName --jq .headRefName)
head_repo_url=$(gh pr view <PR> --json headRepository --jq .headRepository.url)
```
4. Fast-forward base:
- `git checkout main`
- `git pull --ff-only`
5. Create temp base branch from main:
- `git checkout -b temp/landpr-<ts-or-pr>`
6. Check out PR branch locally:
- `gh pr checkout <PR>`
7. Rebase PR branch onto temp base:
- `git rebase temp/landpr-<ts-or-pr>`
- Fix conflicts; keep history tidy.
8. Fix + tests + changelog:
- Implement fixes + add/adjust tests
- Update `CHANGELOG.md` and mention `#<PR>` + `@$contrib`
9. Decide merge strategy:
- Squash (preferred): use when we want a single clean commit
- Rebase: use only when we explicitly want to preserve commit history
- If unclear, ask
10. Full gate (BEFORE commit):
- `pnpm lint && pnpm build && pnpm test`
11. Commit via committer (final merge commit only includes PR # + thanks):
- For the final merge-ready commit: `committer "fix: <summary> (#<PR>) (thanks @$contrib)" CHANGELOG.md <changed files>`
- If you need intermediate fix commits before the final merge commit, keep those messages concise and **omit** PR number/thanks.
- `land_sha=$(git rev-parse HEAD)`
12. Push updated PR branch (rebase => usually needs force):
```sh
git remote add prhead "$head_repo_url.git" 2>/dev/null || git remote set-url prhead "$head_repo_url.git"
git push --force-with-lease prhead HEAD:$head
```
13. Merge PR (must show MERGED on GitHub):
- Squash (preferred): `gh pr merge <PR> --squash`
- Rebase (history-preserving fallback): `gh pr merge <PR> --rebase`
- Never `gh pr close` (closing is wrong)
14. Sync main:
- `git checkout main`
- `git pull --ff-only`
15. Comment on PR with what we did + SHAs + thanks:
```sh
merge_sha=$(gh pr view <PR> --json mergeCommit --jq '.mergeCommit.oid')
gh pr comment <PR> --body "Landed via temp rebase onto main.\n\n- Gate: pnpm lint && pnpm build && pnpm test\n- Land commit: $land_sha\n- Merge commit: $merge_sha\n\nThanks @$contrib!"
```
16. Verify PR state == MERGED:
- `gh pr view <PR> --json state --jq .state`
17. Delete temp branch:
- `git branch -D temp/landpr-<ts-or-pr>`

View File

@@ -1,134 +0,0 @@
---
description: Review a PR thoroughly without merging
---
Input
- PR: $1 <number|url>
- If missing: use the most recent PR mentioned in the conversation.
- If ambiguous: ask.
Do (review-only)
Goal: produce a thorough review and a clear recommendation (READY FOR /landpr vs NEEDS WORK vs INVALID CLAIM). Do NOT merge, do NOT push, do NOT make changes in the repo as part of this command.
0. Truthfulness + reality gate (required for bug-fix claims)
- Do not trust the issue text or PR summary by default; verify in code and evidence.
- If the PR claims to fix a bug linked to an issue, confirm the bug exists now (repro steps, logs, failing test, or clear code-path proof).
- Prove root cause with exact location (`path/file.ts:line` + explanation of why behavior is wrong).
- Verify fix targets the same code path as the root cause.
- Require a regression test when feasible (fails before fix, passes after fix). If not feasible, require explicit justification + manual verification evidence.
- Hallucination/BS red flags (treat as BLOCKER until disproven):
- claimed behavior not present in repo,
- issue/PR says "fixes #..." but changed files do not touch implicated path,
- only docs/comments changed for a runtime bug claim,
- vague AI-generated rationale without concrete evidence.
1. Identify PR meta + context
```sh
gh pr view <PR> --json number,title,state,isDraft,author,baseRefName,headRefName,headRepository,url,body,labels,assignees,reviewRequests,files,additions,deletions --jq '{number,title,url,state,isDraft,author:.author.login,base:.baseRefName,head:.headRefName,headRepo:.headRepository.nameWithOwner,additions,deletions,files:.files|length}'
```
2. Read the PR description carefully
- Summarize the stated goal, scope, and any "why now?" rationale.
- Call out any missing context: motivation, alternatives considered, rollout/compat notes, risk.
3. Read the diff thoroughly (prefer full diff)
```sh
gh pr diff <PR>
# If you need more surrounding context for files:
gh pr checkout <PR> # optional; still review-only
git show --stat
```
4. Validate the change is needed / valuable
- What user/customer/dev pain does this solve?
- Is this change the smallest reasonable fix?
- Are we introducing complexity for marginal benefit?
- Are we changing behavior/contract in a way that needs docs or a release note?
5. Evaluate implementation quality + optimality
- Correctness: edge cases, error handling, null/undefined, concurrency, ordering.
- Design: is the abstraction/architecture appropriate or over/under-engineered?
- Performance: hot paths, allocations, queries, network, N+1s, caching.
- Security/privacy: authz/authn, input validation, secrets, logging PII.
- Backwards compatibility: public APIs, config, migrations.
- Style consistency: formatting, naming, patterns used elsewhere.
6. Tests & verification
- Identify what's covered by tests (unit/integration/e2e).
- Are there regression tests for the bug fixed / scenario added?
- Missing tests? Call out exact cases that should be added.
- If tests are present, do they actually assert the important behavior (not just snapshots / happy path)?
7. Follow-up refactors / cleanup suggestions
- Any code that should be simplified before merge?
- Any TODOs that should be tickets vs addressed now?
- Any deprecations, docs, types, or lint rules we should adjust?
8. Key questions to answer explicitly
- Is the core claim substantiated by evidence, or is it likely invalid/hallucinated?
- Can we fix everything ourselves in a follow-up, or does the contributor need to update this PR?
- Any blocking concerns (must-fix before merge)?
- Is this PR ready to land, or does it need work?
9. Output (structured)
Produce a review with these sections:
A) TL;DR recommendation
- One of: READY FOR /landpr | NEEDS WORK | INVALID CLAIM (issue/bug not substantiated) | NEEDS DISCUSSION
- 13 sentence rationale.
B) Claim verification matrix (required)
- Fill this table:
| Field | Evidence |
| ----------------------------------------------- | -------- |
| Claimed problem | ... |
| Evidence observed (repro/log/test/code) | ... |
| Root cause location (`path:line`) | ... |
| Why this fix addresses that root cause | ... |
| Regression coverage (test name or manual proof) | ... |
- If any row is missing/weak, default to `NEEDS WORK` or `INVALID CLAIM`.
C) What changed
- Brief bullet summary of the diff/behavioral changes.
D) What's good
- Bullets: correctness, simplicity, tests, docs, ergonomics, etc.
E) Concerns / questions (actionable)
- Numbered list.
- Mark each item as:
- BLOCKER (must fix before merge)
- IMPORTANT (should fix before merge)
- NIT (optional)
- For each: point to the file/area and propose a concrete fix or alternative.
- If evidence for the core bug claim is missing, add a `BLOCKER` explicitly.
F) Tests
- What exists.
- What's missing (specific scenarios).
- State clearly whether there is a regression test for the claimed bug.
G) Follow-ups (optional)
- Non-blocking refactors/tickets to open later.
H) Suggested PR comment (optional)
- Offer: "Want me to draft a PR comment to the author?"
- If yes, provide a ready-to-paste comment summarizing the above, with clear asks.
Rules / Guardrails
- Review only: do not merge (`gh pr merge`), do not push branches, do not edit code.
- If you need clarification, ask questions rather than guessing.

View File

@@ -65,6 +65,7 @@ Scoped guides:
- Normal full prod sweep: `pnpm check` (prod typecheck/lint/guards, no tests)
- Full tests: `pnpm test`
- Changed tests only: `pnpm test:changed`
- Local serial loop: `pnpm test:serial`
- Extension tests: `pnpm test:extensions` or `pnpm test extensions` = all extension shards; `pnpm test extensions/<id>` = one extension lane. Heavy channels/OpenAI have dedicated shards.
- Shard timing artifact: `.artifacts/vitest-shard-timings.json`; auto-used for balanced shard ordering. Disable with `OPENCLAW_TEST_PROJECTS_TIMINGS=0`.
- Targeted tests: `pnpm test <path-or-filter> [vitest args...]`; do not call raw `vitest`.
@@ -84,6 +85,7 @@ Scoped guides:
- `pnpm lint:apps`: Swift/app surface, separate from TS lint
- `pnpm lint:all`: legacy comparison lane
- Local heavy-check behavior: `OPENCLAW_LOCAL_CHECK=1` default; `OPENCLAW_LOCAL_CHECK_MODE=throttled|full`; `OPENCLAW_LOCAL_CHECK=0` for CI/shared runs.
- Local validation is local-first. Do not default to Blacksmith/Testbox for routine OpenClaw iteration; it burns warm caches and startup time. Use repo `pnpm` lanes first, then reach for remote CI/Testbox only for parity-only failures, secrets/services, or when explicitly requested.
## Gates

View File

@@ -6,15 +6,147 @@ Docs: https://docs.openclaw.ai
### Changes
- Providers/Amazon Bedrock Mantle: add Claude Opus 4.7 through Mantle's Anthropic Messages route with provider-owned bearer-auth streaming, so the model is actually callable without treating AWS bearer tokens like Anthropic API keys. Thanks @wirjo.
- OpenAI/Responses: use OpenAI's native `web_search` tool automatically for direct OpenAI Responses models when web search is enabled and no managed search provider is pinned; explicit providers such as Brave keep the managed `web_search` tool.
- ACPX: add an explicit `openClawToolsMcpBridge` option that injects a core OpenClaw MCP server for selected built-in tools, starting with `cron`.
- Agents/sessions: add mailbox-style `sessions_list` filters for label, agent, and search plus visibility-scoped derived title and last-message previews. (#69839) Thanks @dangoZhang.
- Providers/GPT-5: move the GPT-5 prompt overlay into the shared provider runtime so compatible GPT-5 models receive the same behavior and heartbeat guidance through OpenAI, OpenRouter, OpenCode, Codex, and other GPT providers; add `agents.defaults.promptOverlays.gpt5.personality` as the global friendly-style toggle while keeping the OpenAI plugin setting as a fallback.
- Providers/xAI: add image generation, text-to-speech, and speech-to-text support, including `grok-imagine-image` / `grok-imagine-image-pro`, reference-image edits, six live xAI voices, MP3/WAV/PCM/G.711 TTS formats, `grok-stt` audio transcription, and xAI realtime transcription for Voice Call streaming. (#68694) Thanks @KateWilkins.
- Providers/STT: add Voice Call streaming transcription for Deepgram, ElevenLabs, and Mistral, and add ElevenLabs Scribe v2 batch audio transcription for inbound media.
- Models/commands: add `/models add <provider> <modelId>` so you can register a model from chat and use it without restarting the gateway; keep `/models` as a simple provider browser while adding clearer add guidance and copy-friendly command examples. (#70211) Thanks @Takhoffman.
- Pi/models: update the bundled pi packages to `0.68.1` and let the OpenCode Go catalog come from pi instead of plugin-maintained model aliases, adding the refreshed `opencode-go/kimi-k2.6`, Qwen, GLM, MiMo, and MiniMax entries.
- CLI/doctor plugins: lazy-load doctor plugin paths and prefer installed plugin `dist/*` runtime entries over source-adjacent JavaScript fallbacks, reducing the measured `doctor --non-interactive` runtime by about 74% while keeping cold doctor startup on built plugin artifacts. (#69840) Thanks @gumadeiras.
- WhatsApp/groups+direct: forward per-group and per-direct `systemPrompt` config into inbound context `GroupSystemPrompt` so configured per-chat behavioral instructions are injected on every turn. Supports `"*"` wildcard fallback and account-scoped overrides under `channels.whatsapp.accounts.<id>.{groups,direct}`; account maps fully replace root maps (no deep merge), matching the existing `requireMention` pattern. Closes #7011. (#59553) Thanks @Bluetegu.
- Plugins/startup: prefer native Jiti loading for built bundled plugin dist modules on supported runtimes, cutting measured bundled plugin load time by 82-90% while keeping source TypeScript on the transform path. (#69925) Thanks @aauren.
- Plugin SDK/Pi embedded runs: add a bundled-plugin embedded extension factory seam so native plugins can extend Pi embedded runs with async runtime hooks such as `tool_result` handling instead of falling back to the older synchronous persistence path. (#69946) Thanks @vincentkoc.
- Tokenjuice: add bundled native OpenClaw support for tokenjuice as an opt-in plugin that compacts noisy `exec` and `bash` tool results in Pi embedded runs. (#69946) Thanks @vincentkoc.
- Codex harness/hooks: route native Codex app-server turns through `before_prompt_build` and emit `before_compaction` / `after_compaction` for native compaction items so prompt and compaction hooks stop drifting from Pi. Thanks @vincentkoc.
- Codex harness/plugins: add a bundled-plugin Codex app-server extension seam for async `tool_result` middleware, fire `after_tool_call` for Codex tool runs, and route mirrored Codex transcript writes through `before_message_write` so tool integrations stop diverging from Pi. Thanks @vincentkoc.
- Codex harness/hooks: fire `llm_input`, `llm_output`, and `agent_end` for native Codex app-server turns so lifecycle hooks stop drifting from Pi. Thanks @vincentkoc.
- Providers/Tencent: add the bundled Tencent Cloud provider plugin with TokenHub and Token Plan onboarding, docs, `hy3-preview` model catalog entries, and tiered Hy3 pricing metadata. (#68460) Thanks @JuniperSling.
- TUI: add local embedded mode for running terminal chats without a Gateway while keeping plugin approval gates enforced. (#66767) Thanks @fuller-stack-dev.
- CLI/Claude: default `claude-cli` runs to warm stdio sessions, including custom configs that omit transport fields, and resume from the stored Claude session after Gateway restarts or idle exits. (#69679) Thanks @obviyus.
- Control UI/settings+chat: add a browser-local personal identity for the operator (name plus local-safe avatar), route user identity rendering through the shared chat/avatar path used by assistant and agent surfaces, and tighten Quick Settings, agent fallback chips, and narrow-screen chat layouts so personalization no longer wastes space or clips controls. (#70362) Thanks @BunsDev.
- Gateway/diagnostics: enable payload-free stability recording by default and add a support-ready diagnostics export with sanitized logs, status, health, config, and stability snapshots for bug reports. (#70324) Thanks @gumadeiras.
### Fixes
- Providers/OpenAI: harden Voice Call realtime transcription against OpenAI Realtime session-update drift, forward language and prompt hints, and add live coverage for realtime STT.
- Providers/Moonshot: stop strict-sanitizing Kimi's native tool_call IDs (shaped like `functions.<name>:<index>`) on the OpenAI-compatible transport, so multi-turn agentic flows through Kimi K2.6 no longer break after 2-3 tool-calling rounds when the serving layer fails to match mangled IDs against the original tool definitions. Adds a `sanitizeToolCallIds` opt-out to the shared `openai-compatible` replay family helper and wires Moonshot to it. Fixes #62319. (#70030) Thanks @LeoDu0314.
- Dependencies/security: override transitive `uuid` to `14.0.0`, clearing the runtime advisory across dependencies.
- Codex harness: ignore dynamic tool descriptions when deciding whether to reuse a native app-server thread while still fingerprinting tool schemas, so channel-specific copy changes no longer reset otherwise compatible Codex conversations. (#69976) Thanks @chen-zhang-cs-code.
- Codex harness: drop invalid legacy app-server `serviceTier` values such as `"priority"` before native thread and turn requests, while keeping supported Codex tiers limited to `"fast"` and `"flex"`. Fixes #64815.
- Codex harness: show bounded, sanitized permission target samples in app-server approval prompts, so native permission requests keep their specific hosts, roots, and paths visible without leaking home usernames or URL credentials. (#70340) Thanks @Lucenx9.
- Docs/Codex harness: narrow native compaction docs to the current start/completion signals, without promising a readable summary or kept-entry audit list yet. (#69612) Thanks @91wan.
- Providers/Amazon Bedrock: use known context-window metadata for discovered models while keeping the unknown-model fallback conservative, so compaction and overflow handling improve for newer Bedrock models without overstating unlisted model limits. Thanks @wirjo.
- Providers/Amazon Bedrock Mantle: refresh IAM-backed bearer tokens at runtime instead of baking discovery-time tokens into provider config, so long-lived Mantle sessions keep working after the initial token ages out. Thanks @wirjo.
- Config/includes: write through single-file top-level includes for isolated OpenClaw-owned mutations, so `plugins install` and `plugins update` update an included `plugins.json5` file instead of flattening modular `$include` configs. Fixes #41050 and #66048.
- Config/reload: plan gateway reloads from source-authored config instead of runtime-materialized snapshots, so plugin update writes no longer trigger false restarts from derived provider/plugin config paths. Fixes #68732.
- Plugins/update: skip npm plugin reinstall/config rewrites when the installed version and recorded artifact identity already match the registry target, let bare npm package names resolve back to tracked install records, and point already-installed `plugins install` attempts at `plugins update` / `--force` instead of a hook-pack fallback. Fixes #46955, #67957, and #68073.
- Agents/MCP: keep `mcp.servers` and bundle MCP tools available in Pi embedded
`coding` and `messaging` sessions while preserving `minimal` profile and
`tools.deny: ["bundle-mcp"]` opt-out behavior. Fixes #68875 and #68818.
- Plugins/startup: tolerate transient bundled-channel catalog/metadata drift while auto-enabling configured plugins, so CLI and gateway startup no longer crash when a channel id is known but its display metadata is unavailable.
- CLI/Claude: report CLI-backed reply runs as streaming while Claude/Codex CLI turns are still in flight, so WebChat keeps visible response state until the backend finishes. Fixes #70125.
- Slack/streaming: fall back to normal Slack replies for Slack Connect streams rejected before the SDK flushes its local buffer, so short replies no longer disappear or report success before Slack acknowledges delivery. Fixes #70295. (#70370) Thanks @mvanhorn.
- Codex harness: rotate the shared app-server websocket client when the configured bearer token changes, so auth-token refreshes reconnect with the new `Authorization` header instead of reusing a stale socket. (#70328) Thanks @Lucenx9.
- Channels/sandbox: derive runtime policy keys for external direct messages that share the main conversation, so sandbox/tool policy no longer treats channel-originated DMs as local main-session runs.
- Config/models: merge provider-scoped model allowlist updates and protect model/provider map writes from accidental full replacement, adding `config set --merge` for additive updates and `--replace` for intentional clobbers. Fixes #65920, #68392, and #68653.
- Agents/Pi auth: preserve AWS SDK-authenticated Bedrock runs for IMDS and task-role setups, clear stale refresh timers on sentinel fallback, and log unexpected runtime-auth prep failures instead of silently leaving the provider unauthenticated. Thanks @wirjo.
- Config/gateway: restore last-known-good config on critical clobber signatures such as missing metadata, missing `gateway.mode`, or sharp size drops, preventing gateway crash loops when a valid backup exists. Fixes #70336.
- Config/gateway: recover configs accidentally prefixed with non-JSON output during gateway startup or `openclaw doctor --fix`, preserving the clobbered file as a backup while leaving normal config reads read-only.
- Agents/GitHub Copilot: normalize connection-bound Responses item IDs in the Copilot provider wrapper so replayed histories no longer fail after the upstream connection changes. (#69362) Thanks @Menci.
- Pi embedded runs: pass real built-in tools into Pi session creation and then narrow active tool names after custom tool registration, so the runner and compaction paths compile cleanly and keep OpenClaw-managed custom tool allowlists without feeding string arrays into `createAgentSession`. Thanks @vincentkoc.
- Agents/OpenAI websocket: route native OpenAI websocket metadata and session-header decisions through the shared endpoint classifier so local mocks and custom `models.providers.openai.baseUrl` endpoints stay out of the native OpenAI path consistently across embedded-runner and websocket transport code. Thanks @vincentkoc.
- Cron/MCP: retire bundled MCP runtimes through one shared cleanup path for isolated cron run ends, persistent cron session rollover, and direct cron `deleteAfterRun` fallback cleanup. Fixes #69145, #68623, and #68827.
- MCP/gateway: tear down stdio MCP process trees on transport close and dispose bundled MCP runtimes during session delete/reset, preventing orphaned wrapper/server processes from accumulating. Fixes #68809 and #69465.
- Agents/MCP: retire bundled MCP runtimes after completed one-shot subagent cleanup and nested `sessions_send` steps, while keeping persistent subagent sessions warm.
- Config: render validation warnings with real line breaks instead of a literal `\n` sequence in CLI/audit output. Fixes #70140.
- Cron/doctor: repair malformed persisted cron job IDs through `openclaw doctor`, including legacy `jobId`, non-string `id`, and missing `id` rows, so `cron list` no longer needs display-layer coercion for corrupt store data. Fixes #70128.
- Discord: normalize prefixed channel targets only at the thread-binding API boundary, so `sessions_spawn({ runtime: "acp", thread: true })` can create child threads from Discord channels without breaking current-channel ACP bindings. (#68034) Thanks @Zetarcos.
- Discord: harden inbound thread metadata handling against partial Carbon channel getters, so non-command thread messages and queued jobs no longer crash when `name`, `parentId`, `parent`, or `ownerId` requires fetched raw data.
- Discord: let `message` tool reactions resolve `user:<id>` DM targets and preserve `channels.discord.guilds.<guild>.channels.<channel>.requireMention: false` during reply-stage activation fallback. Fixes #70165 and #69441.
- Plugins/startup: pre-normalize and cache Jiti alias maps before creating plugin loaders, so module-scoped loader filenames do not reintroduce per-plugin alias-normalization startup cost. Fixes #70186.
- ACP/Codex: run the bundled Codex ACP harness with an isolated `CODEX_HOME` and avoid writing incomplete ChatGPT auth bridge files, so Codex ACP sessions no longer clobber the user's real Codex CLI auth. Fixes #70234. Thanks @Lonobers88.
- Gateway/client: keep long-running RPCs such as ACP `agent.wait` calls in charge of their own timeout instead of closing the websocket on a missed app-level tick while work is still pending.
- Telegram/webhooks: lower the grammY webhook callback timeout to 5s so Telegram gets an early 200 response instead of retrying long-running updates as read timeouts. (#70146) Thanks @friday-james.
- Telegram/polling: rebuild the polling HTTP transport after `getUpdates` 409 conflicts, so retries use a fresh TCP connection instead of looping on a Telegram-terminated keep-alive socket. (#69873) Thanks @hclsys.
- Media delivery: strip persisted base64 audio payloads from webchat history, resolve stored `media://inbound/*` attachments before local-root checks, suppress duplicate Telegram voice/audio sends when TTS emits the same media twice, and support custom image-model IDs that already include their provider prefix.
- Slack/files: resolve `downloadFile` bot tokens from the runtime config when callers provide `cfg` without an explicit token or prebuilt client, preserving cfg-only file downloads outside the action runtime path. (#70160) Thanks @martingarramon.
- Slack/HTTP: dispatch registered Request URL webhooks through the same handler registry used by Slack monitor setup, so HTTP-mode Slack events no longer 404 after successful route registration. (#70275) Thanks @FroeMic.
- Slack/runtime bindings: route focused Slack thread replies through their bound ACP session instead of preparing replies against the default agent shell. Fixes #67739. Thanks @Frankla20.
- CLI/Claude: verify stored Claude CLI session ids have a readable project transcript before resuming, clearing phantom bindings with `reason=transcript-missing` instead of silently starting fresh under `--resume`. Fixes #70177.
- CLI sessions: persist CLI session clearing through the atomic session-store merge path, so expired Claude/Codex CLI bindings are actually removed before retrying without the stale session id. (#70298) Thanks @HFConsultant.
- ACP/sessions_spawn: honor explicit `model` overrides for ACP child sessions instead of silently falling back to the target agent default model. (#70210) Thanks @felix-miao.
- Diffs/viewer: re-read remote viewer access policy from live runtime config on each request, so toggling `plugins.entries.diffs.config.security.allowRemoteViewer` closes proxied viewer access immediately instead of waiting for a restart. Thanks @vincentkoc.
- Diffs/tooling: re-read `viewerBaseUrl`, presentation defaults, and viewer access policy from live runtime config, and fail closed when the live `diffs` plugin entry disappears instead of reviving startup viewer settings. Thanks @vincentkoc.
- Memory/LanceDB: stop resurrecting removed live `memory-lancedb` hook config from startup snapshots, so deleting or disabling the plugin entry shuts off auto-recall and auto-capture without a restart. Thanks @vincentkoc.
- Active Memory: stop reviving removed live `active-memory` config from startup snapshots, so removing the plugin entry turns the hook off immediately instead of waiting for a restart. Thanks @vincentkoc.
- Agents/subagents: drop bare `NO_REPLY` from the parent turn when the session still has pending spawned children, so direct-conversation surfaces such as Telegram DMs no longer rewrite the sentinel into visible fallback chatter while waiting for the child completion event. (#69942) Thanks @neeravmakwana.
- Plugins/install: keep bundled plugin dependencies off npm install while repairing them when plugins activate from a packaged install, including Feishu/Lark, Browser, and direct bundled channel setup-entry loads.
- CLI/channels: skip and cache bundled channel plugin, setup, and secrets load failures during read-only discovery, so one broken unused bundled channel cannot crash `openclaw status` or bootstrap secret scans.
- Memory/LanceDB: retry initialization after a failed LanceDB load and report unsupported Intel macOS native runtime clearly instead of caching the failure or repeatedly attempting an install that cannot work.
- CLI/Claude: hash only static extra system prompt parts when deciding whether to reuse a CLI session, so per-message inbound metadata no longer resets Claude CLI conversations on every turn. (#70122) Thanks @zijunl.
- Hooks/Slack: standardize shared message hook routing fields (`threadId` / `replyToId`) and stop Slack outbound delivery from re-running `message_sending` inside the channel adapter, so plugins like thread-ownership make one outbound routing decision per reply. Thanks @vincentkoc.
- Auto-reply/media: share one run-scoped reply media context between streamed block delivery and final payload filtering, so a local `MEDIA:` attachment is staged once and duplicate media sends are suppressed reliably. (#68111) Thanks @ayeshakhalid192007-dev.
- Plugins/gateway hooks: expose startup config, workspace dir, and a live cron getter on the typed `gateway_start` hook, and move memory-core managed dreaming off the internal `gateway:startup` bridge so cron reconciliation stays on the public plugin hook path. Thanks @vincentkoc.
- Plugins/config: read plugin trust decisions from the source config snapshot when a resolved runtime snapshot is active, so `plugins.allow` remains enforced and `doctor`/gateway startup no longer warn that the allowlist is empty when it is configured. Fixes #70161. Also fixes #70141.
- Gateway/restart: preserve group and channel chat context when resuming an agent turn after a Gateway restart, so continuation replies keep the same prompt, routing, and tool-status behavior as the original conversation.
- Gateway/pairing: shared-secret loopback CLI clients now silently auto-approve `metadata-upgrade` pairing (platform / device family refresh) instead of being disconnected with `1008 pairing required`. This matches the scope-upgrade and role-upgrade behavior added in #69431 and unblocks non-interactive CLI automation when a paired-device record has a stale platform string (e.g. device key replicated across hosts, install migrated between OSes, or platform-string format changed between OpenClaw versions). Browser / Control-UI clients keep the existing approval-required flow for metadata changes.
- Gateway/pairing: treat any forwarded-header evidence (`Forwarded`, `X-Forwarded-*`, or `X-Real-IP`) as proxied WebSocket traffic before pairing locality checks, so reverse-proxy topologies cannot use the loopback shared-secret helper auto-pairing path.
- Agents/OpenAI: treat exact `NO_REPLY` assistant output as a deliberate silent reply in embedded runs, so GPT-5.4 turns with signed reasoning plus a silent final no longer surface a false incomplete-turn error.
- Auto-reply/streaming: preserve streamed reply directives through chunk boundaries and phase-aware `final_answer` delivery, so split `MEDIA:<path>` lines, voice tags, and reply targets reach channel delivery instead of leaking as text or being dropped. (#70243) Thanks @zqchris.
- Anthropic/Claude Opus 4.7: normalize Opus 4.7 and `claude-cli` Opus 4.7 variants to a 1M context window in resolved runtime metadata and active-agent status/context reporting, so they no longer inherit the stale 200k fallback. Thanks @BunsDev.
- Gateway/pairing webchat: render `/pair qr` replies as structured media instead of raw markdown text, preserve inline reply threading and silent-control handling on media replies, avoid persisting sensitive QR images into transcript history, and keep local webchat media embedding behind internal-only trust markers. (#70047) Thanks @BunsDev.
- Codex harness: default app-server runs to unchained local execution, so OpenAI heartbeats can use network and shell tools without stalling behind native Codex approvals or the workspace-write sandbox.
- Codex harness: fail closed for unknown native app-server approval methods instead of routing unsupported future approval shapes through OpenClaw approval grants. (#70356) Thanks @Lucenx9.
- Codex harness: apply the GPT-5 behavior and heartbeat prompt overlay to native Codex app-server runs, so `codex/gpt-5.x` sessions get the same follow-through, tool-use, and proactive heartbeat guidance as OpenAI GPT-5 runs.
- Codex harness: add an explicit Guardian mode for Codex app-server approvals, plus a Docker live probe for approved and ask-back Guardian decisions, while keeping default app-server runs unchained for unattended local heartbeats. The legacy `OPENCLAW_CODEX_APP_SERVER_GUARDIAN` shortcut is removed; use plugin config `appServer.mode: "guardian"` or `OPENCLAW_CODEX_APP_SERVER_MODE=guardian`. Thanks @pashpashpash.
- OpenAI/Responses: keep embedded OpenAI Responses runs on HTTP when `models.providers.openai.baseUrl` points at a local mock or other non-public endpoint, so mocked/custom endpoints no longer drift onto the hardcoded public websocket transport. (#69815) Thanks @vincentkoc.
- Channels/config: require resolved runtime config on channel send/action/client helpers and block runtime helper `loadConfig()` calls, so SecretRefs are resolved at startup/boundaries instead of being re-read during sends.
- Discord: pass resolved runtime config through guild and moderation action helpers, so thread-originated Discord commands can run channel, member, role, and guild actions without falling back to runtime config reads. (#70215) Thanks @szponeczek.
- CLI/channels: preserve bundled setup promotion metadata when a loaded partial channel plugin omits it, so adding a non-default account still moves legacy single-account fields such as Telegram `streaming` into `accounts.default`.
- Telegram: keep the sent-message ownership cache isolated per configured session store, so own-message reaction filtering remains correct with custom `session.store` paths.
- Security/update: fail closed when exact pinned npm plugin or hook-pack updates detect integrity drift, and expose aborted plugin drift details in `openclaw update --json`.
- Ollama: forward OpenClaw thinking control to native `/api/chat` requests as top-level `think`, so `/think off` and `openclaw agent --thinking off` suppress thinking on models such as qwen3 instead of idling until the watchdog fires. Fixes #69902. (#69967) Thanks @WZH8898.
- Memory-core/dreaming: suppress the startup-only managed dreaming cron unavailable warning when the cron service is still attaching, while preserving the runtime warning if cron genuinely remains unavailable. Fixes #69939. (#69941) Thanks @Sanjays2402.
- Mattermost: suppress reasoning-only payloads even when they arrive as blockquoted `> Reasoning:` text, preventing `/reasoning on` from leaking thinking into channel posts. (#69927) Thanks @lawrence3699.
- Discord: read `channel.parentId` through a safe accessor in the slash-command, reaction, and model-picker paths so partial `GuildThreadChannel` prototype getters no longer throw `Cannot access rawData on partial Channel` when commands like `/new` run from inside a thread. Fixes #69861. (#69908) Thanks @neeravmakwana.
- Discord: use safe channel name and parent accessors across voice command authorization, so `/vc` commands from partial Discord thread channels no longer crash on Carbon rawData getters. (#70199) Thanks @hanamizuki.
- Discord: make auto-thread parent transcript inheritance opt-in via `channels.discord.thread.inheritParent`, keeping newly created Discord thread sessions isolated by default while preserving explicit inheritance for configured accounts. Fixes #69907. (#69986) Thanks @Blahdude.
- Browser/Chrome MCP: reset cached existing-session control sessions when a `navigate_page` call times out, so one stuck navigation no longer poisons the browser profile until a gateway restart. (#69733) Thanks @ayeshakhalid192007-dev.
- Browser/Chrome MCP: propagate click timeouts and abort signals to existing-session actions so a stuck click fails fast and reconnects instead of poisoning the browser tool until gateway restart. (#63524) Thanks @dongseok0.
- Amazon Bedrock/prompt caching: resolve opaque application inference profile targets before injecting Bedrock cache points, require every routed target to support explicit cache points, and retry transient profile lookups instead of caching a false negative for the rest of the process. (#69953) Thanks @anirudhmarc and @vincentkoc.
- Gateway/channel health: base stale-socket recovery on provider-proven transport activity instead of inbound app-event freshness, preventing quiet Slack, Discord, Telegram, Matrix, and local-style channels from being restarted solely because no user traffic arrived. (#69833) Thanks @bek91.
- OpenCode Go: canonicalize stale bundled `opencode-go` base URLs from `/go` or `/go/v1` to `/zen/go` or `/zen/go/v1`, so older generated model metadata stops hitting the 404 HTML endpoint. (#69898)
- CLI/channels: honor `channels.<id>.enabled=false` as a hard read-only presence opt-out, so env vars, manifest env vars, or stale persisted auth state no longer make disabled channel plugins appear in status, doctor, or setup-only discovery.
- Channels/preview streaming: centralize draft-preview finalization so Slack, Discord, Mattermost, and Matrix no longer flush temporary preview messages for media/error finals, and preserve first-reply threading for normal fallback delivery.
- Discord: keep slash command follow-up chunks ephemeral when the command is configured for ephemeral replies, so long `/status` output no longer leaks fallback model or runtime details into the public channel. (#69869) thanks @gumadeiras.
- Gateway/session history: re-check current auth and `chat.history` scope before later SSE keepalives and transcript updates, so active session-history streams close before delivering post-revocation events.
- Plugins/discovery: reject package plugin source entries that escape the package directory before explicit runtime entries or inferred built JavaScript peers can be used. (#69868) thanks @gumadeiras.
- CLI/channels: resolve channel presence through a shared policy that keeps ambient env vars and stale persisted auth from surfacing disabled bundled plugins in status, doctor, security audit, and cron delivery validation unless the channel or plugin is effectively enabled or explicitly configured. (#69862) Thanks @gumadeiras.
- Doctor/plugins: hydrate legacy partial interactive handler state before plugin reload clears dedupe caches, so `openclaw doctor` and post-update doctor runs no longer crash with `Cannot read properties of undefined (reading 'clear')`. (#70135) Thanks @ngutman.
- Control UI/config: preserve intentionally empty raw config snapshots when clearing pending updates so reset restores the original bytes instead of synthesizing JSON for blank config files. (#68178) Thanks @BunsDev.
- memory-core/dreaming: surface a `Dreaming status: blocked` line in `openclaw memory status` when dreaming is enabled but the heartbeat that drives the managed cron is not firing for the default agent, and add a Troubleshooting section to the dreaming docs covering the two common causes (per-agent `heartbeat` blocks excluding `main`, and `heartbeat.every` set to `0`/empty/invalid), so the silent failure described in #69843 becomes legible on the status surface.
- Cron/run-log: report generic `message` tool sends under the resolved delivery channel when they match the cron target, while preserving account-specific mismatch checks for delivery traces. (#69940) Thanks @davehappyminion.
- Doctor/channels: merge configured-channel doctor hooks across read-only, loaded, setup, and runtime plugin discovery so partial adapters no longer hide runtime-only compatibility repair or allowlist warnings, preserve disabled-channel opt-outs, and ignore malformed hook values before they can mask valid fallbacks. (#69919) Thanks @gumadeiras.
- Models/CLI: show bundled provider-owned static catalog rows in `models list --all` before auth is configured, including Kimi K2.6 rows for Moonshot, OpenRouter, and Vercel AI Gateway, while keeping local-only and workspace plugin catalog paths isolated. (#69909) Thanks @shakkernerd.
- Configure: skip generic CLI startup bootstrap for `openclaw configure` and bound hint-only gateway probes so the onboarding TUI reaches its first prompt faster when the Gateway is unavailable. (#69984) Thanks @obviyus.
- Agents/harness: surface selected plugin harness failures directly instead of replaying the same turn through embedded PI, preventing misleading secondary PI auth errors and avoiding duplicate side effects.
- OpenAI Codex: add a ChatGPT device-code auth option beside browser OAuth, so headless or callback-hostile setups can sign in without relying on the localhost browser callback. (#69557) Thanks @vincentkoc.
- CLI sessions: keep provider-owned CLI sessions through implicit daily expiry while preserving explicit reset behavior, and retain Claude CLI binding metadata across gateway agent requests. (#70106) Thanks @obviyus.
- fix(config): accept truncateAfterCompaction (#68395). Thanks @MonkeyLeeT
- CLI/Claude: keep Claude CLI session bindings stable across OAuth access-token refreshes, so gateway restarts continue the same Claude conversation instead of minting a fresh one. (#70132) Thanks @obviyus.
- QQBot: add `INTERACTION` intent (`1 << 26`) to the gateway constants and include it in the `FULL_INTENTS` mask so interaction events are received. (#70143) Thanks @cxyhhhhh.
- Gateway/restart: preserve one-shot continuation instructions across gateway restarts so agents can resume and reply back to the original chat after reboot. (#63406) Thanks @VACInc.
- Gateway/restart: write restart sentinel files atomically so interrupted writes cannot leave a truncated sentinel behind. (#70225) Thanks @obviyus.
- Pairing: remove stale pending requests for a device when that paired device is deleted, so an old repair approval cannot recreate the removed device from leftover state.
- Security/dotenv: block workspace `.env` overrides for Matrix, Mattermost, IRC, and Synology endpoint settings so cloned workspaces cannot redirect bundled connector traffic through local endpoint config. (#70240) Thanks @drobison00.
- Telegram: require the same `/models` authorization for group model-picker callbacks, so unauthorized participants can no longer browse or change the session model through inline buttons. (#70235) Thanks @drobison00.
- Agents/Pi: keep the filtered tool-name allowlist active for embedded OpenAI/OpenAI Codex GPT-5 runs and compaction sessions, so bundled and client tools still execute after the Pi `0.68.1` session-tool allowlist change instead of stopping at plan-only replies with no tool call. (#70281) Thanks @jalehman.
- Agents/Pi: honor explicit `strict-agentic` execution contracts for incomplete-turn retry guards across providers, so manually opted-in local or compatible models get the same retry behavior without relying on OpenAI model inference. (#66750) Thanks @ziomancer.
- OpenShell/sandbox: pin verified file reads to an already-opened descriptor, walk the ancestor chain for symlinked parents on platforms without fd-path readlink, and re-check file identity so parent symlink swaps cannot redirect in-sandbox reads to host files outside the allowed mount root. (#69798) Thanks @drobison00.
- Gateway/Control UI: require authenticated Control UI read access before serving `/__openclaw/control-ui-config.json` when `gateway.auth` is enabled, so unauthenticated callers can no longer read bootstrap metadata. (#70247) Thanks @drobison00.
## 2026.4.21
@@ -45,6 +177,7 @@ Docs: https://docs.openclaw.ai
- Synology Chat: validate outbound webhook `file_url` values against the shared SSRF policy before forwarding to the NAS, rejecting malformed URLs, non-`http(s)` schemes, and private/blocked network targets so the NAS cannot be used as a confused deputy to fetch internal addresses. (#69784) Thanks @eleqtrizit.
- LINE: validate outbound media URLs against the shared public-network guard before handing them to LINE, preserving arbitrary public HTTPS media while rejecting loopback, link-local, and private-network targets.
- Gateway/Control UI: require gateway auth on the Control UI avatar route (`GET /avatar/<agentId>` and `?meta=1` metadata) when auth is configured, matching the sibling assistant-media route, and propagate the existing gateway token through the UI avatar fetch (bearer header + authenticated blob URL) so authenticated dashboards still load local avatars. (#69775)
- Google Chat/auth: replace the Google auth `gaxios` shim with a scoped SSRF-guarded transport, validate service-account auth endpoints against trusted Google URLs, and let the plugin own its staged `gaxios` auth runtime instead of patching process-wide globals or the root CLI startup path. Thanks @vincentkoc.
- Exec/allowlist: reject POSIX parameter expansion forms such as `$VAR`, `$?`, `$$`, `$1`, and `$@` inside unquoted heredocs during shell approval analysis, so these heredocs no longer pass allowlist review as plain text. (#69795) Thanks @drobison00.
- Gateway/MCP loopback: derive owner-only tool visibility from distinct authenticated owner vs non-owner loopback bearers instead of the caller-controlled owner header, so non-owner MCP child processes cannot recover owner access by spoofing request metadata. (#69796)
- GitHub Copilot: update the default Opus model from `claude-opus-4.6` to `claude-opus-4.7` after GitHub removed Copilot support for 4.6. (#69818) Thanks @shakkernerd.
@@ -54,6 +187,7 @@ Docs: https://docs.openclaw.ai
- Usage/providers: keep plugin-owned usage auth enabled when manifest-declared provider auth env vars such as `MINIMAX_CODE_PLAN_KEY` are present, so `/usage` can resolve MiniMax billing credentials through the provider plugin.
- Tlon/uploads: route both hosted Memex upload targets and custom-S3 presigned upload URLs through the shared SSRF guard so blocked private or loopback destinations fail before upload, while public upload URLs continue through the existing hosted upload flow. (#69794) Thanks @drobison00.
- Channels/thread routing: keep outbound replies in existing Slack, Mattermost, Matrix, Telegram, Discord, and QA-channel thread sessions by sharing the Plugin SDK thread-aware route builder across bundled plugins.
- Agents/replay: normalize restored assistant text content before provider replay and prompt submission, so legacy or repaired sessions no longer crash on `assistantMsg.content.flatMap`. (#69850) Thanks @fuller-stack-dev.
## 2026.4.20
@@ -145,8 +279,8 @@ Docs: https://docs.openclaw.ai
- Agents/subagents: include requested role and runtime timing on subagent failure payloads so parent agents can correlate failed or timed-out child work. (#68726) Thanks @BKF-Gitty.
- Gateway/sessions: reject stale agent-scoped sessions after an agent is removed from config while preserving legacy default-agent main-session aliases. (#65986) Thanks @bittoby.
- Doctor/gateway: surface pending device pairing requests, scope-upgrade approval drift, and stale device-token mismatch repair steps so `openclaw doctor --fix` no longer leaves pairing/auth setup failures unexplained. (#69210) Thanks @obviyus.
- Cron/isolated-agent: preserve explicit `delivery.mode: "none"` message targets for isolated runs without inheriting implicit `last` routing, so agent-initiated Telegram sends keep their authored destination while bare `mode:none` jobs stay targetless. (#69153) Thanks @obviyus.
- Cron/isolated-agent: keep `delivery.mode: "none"` account-only or thread-only configs from inheriting a stale implicit recipient, so isolated runs only resolve message routing when the job authored an explicit `to` target. (#69163) Thanks @obviyus.
- Cron/isolated-agent: preserve explicit `delivery.mode: "none"` message targets for isolated runs without inheriting implicit `last` routing, so agent-initiated Telegram sends keep their authored destination while bare `mode:none` jobs stay targetless. (#69153) Thanks @davehappyminion and @nikilster.
- Cron/isolated-agent: keep `delivery.mode: "none"` account-only or thread-only configs from inheriting a stale implicit recipient, so isolated runs only resolve message routing when the job authored an explicit `to` target. (#69163) Thanks @davehappyminion and @nikilster.
- Gateway/TUI: retry session history while the local gateway is still finishing startup, so `openclaw tui` reconnects no longer fail on transient `chat.history unavailable during gateway startup` errors. (#69164) Thanks @shakkernerd.
- BlueBubbles/reactions: fall back to `love` when an agent reacts with an emoji outside the iMessage tapback set (`love`/`like`/`dislike`/`laugh`/`emphasize`/`question`), so wider-vocabulary model reactions like `👀` still produce a visible tapback instead of failing the whole reaction request. Configured ack reactions still validate strictly via the new `normalizeBlueBubblesReactionInputStrict` path. (#64693) Thanks @zqchris.
- BlueBubbles: prefer iMessage over SMS when both chats exist for the same handle, honor explicit `sms:` targets, and never silently downgrade iMessage-available recipients. (#61781) Thanks @rmartin.

View File

@@ -6,7 +6,7 @@ Welcome to the lobster tank! 🦞
- **GitHub:** https://github.com/openclaw/openclaw
- **Vision:** [`VISION.md`](VISION.md)
- **Discord:** https://discord.gg/qkhbAGHRBT
- **Discord:** https://discord.gg/clawd
- **X/Twitter:** [@steipete](https://x.com/steipete) / [@openclaw](https://x.com/openclaw)
## Maintainers

View File

@@ -165,7 +165,7 @@ Run `openclaw doctor` to surface risky/misconfigured DM policies.
- Chat commands: `/status`, `/new`, `/reset`, `/compact`, `/think <level>`, `/verbose on|off`, `/trace on|off`, `/usage off|tokens|full`, `/restart`, `/activation mention|always`
- Session tools: `sessions_list`, `sessions_history`, `sessions_send`
- Skills registry: [ClawHub](https://clawhub.com)
- Skills registry: [ClawHub](https://clawhub.ai)
- Architecture overview: [Architecture](https://docs.openclaw.ai/concepts/architecture)
## Docs by goal

View File

@@ -65,8 +65,8 @@ android {
applicationId = "ai.openclaw.app"
minSdk = 31
targetSdk = 36
versionCode = 2026042100
versionName = "2026.4.21"
versionCode = 2026042200
versionName = "2026.4.22"
ndk {
// Support all major ABIs — native libs are tiny (~47 KB per ABI)
abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
@@ -182,13 +182,13 @@ ktlint {
}
dependencies {
val composeBom = platform("androidx.compose:compose-bom:2026.02.00")
val composeBom = platform("androidx.compose:compose-bom:2026.03.01")
implementation(composeBom)
androidTestImplementation(composeBom)
implementation("androidx.core:core-ktx:1.17.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.10.0")
implementation("androidx.activity:activity-compose:1.12.2")
implementation("androidx.activity:activity-compose:1.13.0")
implementation("androidx.webkit:webkit:1.15.0")
implementation("androidx.compose.ui:ui")
@@ -204,17 +204,17 @@ dependencies {
implementation("com.google.android.material:material:1.13.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.10.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.11.0")
implementation("androidx.security:security-crypto:1.1.0")
implementation("androidx.exifinterface:exifinterface:1.4.2")
implementation("com.squareup.okhttp3:okhttp:5.3.2")
implementation("org.bouncycastle:bcprov-jdk18on:1.83")
implementation("org.commonmark:commonmark:0.27.1")
implementation("org.commonmark:commonmark-ext-autolink:0.27.1")
implementation("org.commonmark:commonmark-ext-gfm-strikethrough:0.27.1")
implementation("org.commonmark:commonmark-ext-gfm-tables:0.27.1")
implementation("org.commonmark:commonmark-ext-task-list-items:0.27.1")
implementation("org.bouncycastle:bcprov-jdk18on:1.84")
implementation("org.commonmark:commonmark:0.28.0")
implementation("org.commonmark:commonmark-ext-autolink:0.28.0")
implementation("org.commonmark:commonmark-ext-gfm-strikethrough:0.28.0")
implementation("org.commonmark:commonmark-ext-gfm-tables:0.28.0")
implementation("org.commonmark:commonmark-ext-task-list-items:0.28.0")
// CameraX (for node.invoke camera.* parity)
implementation("androidx.camera:camera-core:1.5.2")
@@ -228,11 +228,11 @@ dependencies {
testImplementation("junit:junit:4.13.2")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.2")
testImplementation("io.kotest:kotest-runner-junit5-jvm:6.1.3")
testImplementation("io.kotest:kotest-assertions-core-jvm:6.1.3")
testImplementation("io.kotest:kotest-runner-junit5-jvm:6.1.11")
testImplementation("io.kotest:kotest-assertions-core-jvm:6.1.11")
testImplementation("com.squareup.okhttp3:mockwebserver:5.3.2")
testImplementation("org.robolectric:robolectric:4.16.1")
testRuntimeOnly("org.junit.vintage:junit-vintage-engine:6.0.2")
testRuntimeOnly("org.junit.vintage:junit-vintage-engine:6.0.3")
}
tasks.withType<Test>().configureEach {

View File

@@ -403,7 +403,7 @@ class ChatController(
val ts = payload["ts"].asLongOrNull() ?: System.currentTimeMillis()
if (phase == "start") {
val args = data?.get("args").asObjectOrNull()
val args = data.get("args").asObjectOrNull()
pendingToolCallsById[toolCallId] =
ChatPendingToolCall(
toolCallId = toolCallId,

View File

@@ -468,7 +468,7 @@ class GatewayDiscovery(
for (r in records) {
val strings: List<String> =
try {
r.strings.mapNotNull { it as? String }
r.strings
} catch (_: Throwable) {
emptyList()
}

View File

@@ -1240,7 +1240,7 @@ private fun queryInstalledApps(
.mapNotNull { packageName ->
runCatching {
val appInfo = packageManager.getApplicationInfo(packageName, 0)
val label = packageManager.getApplicationLabel(appInfo)?.toString()?.trim().orEmpty()
val label = packageManager.getApplicationLabel(appInfo).toString().trim()
InstalledApp(
label = if (label.isEmpty()) packageName else label,
packageName = packageName,

View File

@@ -555,7 +555,7 @@ private fun InlineBase64Image(base64: String, mimeType: String?) {
if (image != null) {
Image(
bitmap = image!!,
bitmap = image,
contentDescription = mimeType ?: "image",
contentScale = ContentScale.Fit,
modifier = Modifier.fillMaxWidth(),

View File

@@ -246,7 +246,7 @@ private fun ChatBase64Image(base64: String, mimeType: String?) {
modifier = Modifier.fillMaxWidth(),
) {
Image(
bitmap = image!!,
bitmap = image,
contentDescription = mimeType ?: "attachment",
contentScale = ContentScale.Fit,
modifier = Modifier.fillMaxWidth(),

View File

@@ -40,6 +40,6 @@ ktlint {
dependencies {
implementation("androidx.benchmark:benchmark-macro-junit4:1.4.1")
implementation("androidx.test.ext:junit:1.2.1")
implementation("androidx.test.uiautomator:uiautomator:2.4.0-alpha06")
implementation("androidx.test.ext:junit:1.3.0")
implementation("androidx.test.uiautomator:uiautomator:2.4.0-beta02")
}

View File

@@ -1,7 +1,7 @@
plugins {
id("com.android.application") version "9.1.0" apply false
id("com.android.test") version "9.1.0" apply false
id("org.jlleitschuh.gradle.ktlint") version "14.0.1" apply false
id("org.jetbrains.kotlin.plugin.compose") version "2.2.21" apply false
id("org.jetbrains.kotlin.plugin.serialization") version "2.2.21" apply false
id("org.jlleitschuh.gradle.ktlint") version "14.2.0" apply false
id("org.jetbrains.kotlin.plugin.compose") version "2.3.20" apply false
id("org.jetbrains.kotlin.plugin.serialization") version "2.3.20" apply false
}

Binary file not shown.

View File

@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

17
apps/android/gradlew vendored
View File

@@ -1,7 +1,7 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
# Copyright © 2015 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
@@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -84,7 +86,7 @@ done
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -112,7 +114,6 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
@@ -170,7 +171,6 @@ fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
@@ -200,18 +200,17 @@ fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m" "--enable-native-access=ALL-UNNAMED"'
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.

View File

@@ -13,6 +13,8 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@@ -34,7 +36,7 @@ set APP_HOME=%DIRNAME%
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" "--enable-native-access=ALL-UNNAMED"
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
@@ -68,11 +70,10 @@ goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell

View File

@@ -1,5 +1,9 @@
# OpenClaw iOS Changelog
## 2026.4.22 - 2026-04-22
Maintenance update for the current OpenClaw development release.
## 2026.4.21 - 2026-04-21
Maintenance update for the current OpenClaw development release.

View File

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

View File

@@ -1,3 +1,3 @@
{
"version": "2026.4.21"
"version": "2026.4.22"
}

View File

@@ -1,5 +1,5 @@
{
"originHash" : "fb90e7b1977f43661ac91681d16da11f9ddd85630407ef170eaada0a6ee39972",
"originHash" : "7a8088405ec5e396c14d737c110ff5651ff25dabcd437a0fee92e57018c5360a",
"pins" : [
{
"identity" : "axorcist",
@@ -33,8 +33,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/orchetect/MenuBarExtraAccess",
"state" : {
"revision" : "707dff6f55217b3ef5b6be84ced3e83511d4df5c",
"version" : "1.2.2"
"revision" : "33bb0e4b1e407feac791e047dcaaf9c69b25fd26",
"version" : "1.3.0"
}
},
{
@@ -51,8 +51,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/sparkle-project/Sparkle",
"state" : {
"revision" : "21d8df80440b1ca3b65fa82e40782f1e5a9e6ba2",
"version" : "2.9.0"
"revision" : "066e75a8b3e99962685d6a90cdd5293ebffd9261",
"version" : "2.9.1"
}
},
{
@@ -78,8 +78,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-log.git",
"state" : {
"revision" : "bbd81b6725ae874c69e9b8c8804d462356b55523",
"version" : "1.10.1"
"revision" : "5073617dac96330a486245e4c0179cb0a6fd2256",
"version" : "1.12.0"
}
},
{

View File

@@ -15,7 +15,7 @@ let package = Package(
.executable(name: "openclaw-mac", targets: ["OpenClawMacCLI"]),
],
dependencies: [
.package(url: "https://github.com/orchetect/MenuBarExtraAccess", exact: "1.2.2"),
.package(url: "https://github.com/orchetect/MenuBarExtraAccess", exact: "1.3.0"),
.package(url: "https://github.com/swiftlang/swift-subprocess.git", from: "0.4.0"),
.package(url: "https://github.com/apple/swift-log.git", from: "1.10.1"),
.package(url: "https://github.com/sparkle-project/Sparkle", from: "2.9.0"),

View File

@@ -51,7 +51,6 @@ struct OpenClawApp: App {
animationsEnabled: self.state.iconAnimationsEnabled && !self.isGatewaySleeping,
iconState: self.effectiveIconState)
}
.menuBarExtraStyle(.menu)
.menuBarExtraAccess(isPresented: self.$isMenuPresented) { item in
self.statusItem = item
MenuSessionsInjector.shared.install(into: item)
@@ -59,6 +58,7 @@ struct OpenClawApp: App {
self.installStatusItemMouseHandler(for: item)
self.updateHoverHUDSuppression()
}
.menuBarExtraStyle(.menu)
.onChange(of: self.state.isPaused) { _, paused in
self.applyStatusItemAppearance(paused: paused, sleeping: self.isGatewaySleeping)
if self.state.connectionMode == .local {

View File

@@ -15,9 +15,9 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>2026.4.21</string>
<string>2026.4.22</string>
<key>CFBundleVersion</key>
<string>2026042100</string>
<string>2026042200</string>
<key>CFBundleIconFile</key>
<string>OpenClaw</string>
<key>CFBundleURLTypes</key>

View File

@@ -590,6 +590,7 @@ public struct AgentParams: Codable, Sendable {
public let timeout: Int?
public let besteffortdeliver: Bool?
public let lane: String?
public let cleanupbundlemcponrunend: Bool?
public let extrasystemprompt: String?
public let bootstrapcontextmode: AnyCodable?
public let bootstrapcontextrunkind: AnyCodable?
@@ -621,6 +622,7 @@ public struct AgentParams: Codable, Sendable {
timeout: Int?,
besteffortdeliver: Bool?,
lane: String?,
cleanupbundlemcponrunend: Bool?,
extrasystemprompt: String?,
bootstrapcontextmode: AnyCodable?,
bootstrapcontextrunkind: AnyCodable?,
@@ -651,6 +653,7 @@ public struct AgentParams: Codable, Sendable {
self.timeout = timeout
self.besteffortdeliver = besteffortdeliver
self.lane = lane
self.cleanupbundlemcponrunend = cleanupbundlemcponrunend
self.extrasystemprompt = extrasystemprompt
self.bootstrapcontextmode = bootstrapcontextmode
self.bootstrapcontextrunkind = bootstrapcontextrunkind
@@ -683,6 +686,7 @@ public struct AgentParams: Codable, Sendable {
case timeout
case besteffortdeliver = "bestEffortDeliver"
case lane
case cleanupbundlemcponrunend = "cleanupBundleMcpOnRunEnd"
case extrasystemprompt = "extraSystemPrompt"
case bootstrapcontextmode = "bootstrapContextMode"
case bootstrapcontextrunkind = "bootstrapContextRunKind"

View File

@@ -382,223 +382,6 @@
}
}
},
"whatsapp_login": {
"emoji": "🟢",
"title": "WhatsApp Login",
"actions": {
"start": {
"label": "start"
},
"wait": {
"label": "wait"
}
}
},
"discord": {
"emoji": "💬",
"title": "Discord",
"actions": {
"react": {
"label": "react",
"detailKeys": [
"channelId",
"messageId",
"emoji"
]
},
"reactions": {
"label": "reactions",
"detailKeys": [
"channelId",
"messageId"
]
},
"sticker": {
"label": "sticker",
"detailKeys": [
"to",
"stickerIds"
]
},
"poll": {
"label": "poll",
"detailKeys": [
"question",
"to"
]
},
"permissions": {
"label": "permissions",
"detailKeys": [
"channelId"
]
},
"readMessages": {
"label": "read messages",
"detailKeys": [
"channelId",
"limit"
]
},
"sendMessage": {
"label": "send",
"detailKeys": [
"to",
"content"
]
},
"editMessage": {
"label": "edit",
"detailKeys": [
"channelId",
"messageId"
]
},
"deleteMessage": {
"label": "delete",
"detailKeys": [
"channelId",
"messageId"
]
},
"threadCreate": {
"label": "thread create",
"detailKeys": [
"channelId",
"name"
]
},
"threadList": {
"label": "thread list",
"detailKeys": [
"guildId",
"channelId"
]
},
"threadReply": {
"label": "thread reply",
"detailKeys": [
"channelId",
"content"
]
},
"pinMessage": {
"label": "pin",
"detailKeys": [
"channelId",
"messageId"
]
},
"unpinMessage": {
"label": "unpin",
"detailKeys": [
"channelId",
"messageId"
]
},
"listPins": {
"label": "list pins",
"detailKeys": [
"channelId"
]
},
"searchMessages": {
"label": "search",
"detailKeys": [
"guildId",
"content"
]
},
"memberInfo": {
"label": "member",
"detailKeys": [
"guildId",
"userId"
]
},
"roleInfo": {
"label": "roles",
"detailKeys": [
"guildId"
]
},
"emojiList": {
"label": "emoji list",
"detailKeys": [
"guildId"
]
},
"roleAdd": {
"label": "role add",
"detailKeys": [
"guildId",
"userId",
"roleId"
]
},
"roleRemove": {
"label": "role remove",
"detailKeys": [
"guildId",
"userId",
"roleId"
]
},
"channelInfo": {
"label": "channel",
"detailKeys": [
"channelId"
]
},
"channelList": {
"label": "channels",
"detailKeys": [
"guildId"
]
},
"voiceStatus": {
"label": "voice",
"detailKeys": [
"guildId",
"userId"
]
},
"eventList": {
"label": "events",
"detailKeys": [
"guildId"
]
},
"eventCreate": {
"label": "event create",
"detailKeys": [
"guildId",
"name"
]
},
"timeout": {
"label": "timeout",
"detailKeys": [
"guildId",
"userId"
]
},
"kick": {
"label": "kick",
"detailKeys": [
"guildId",
"userId"
]
},
"ban": {
"label": "ban",
"detailKeys": [
"guildId",
"userId"
]
}
}
},
"exec": {
"emoji": "🛠️",
"title": "Exec",
@@ -629,8 +412,13 @@
"title": "Sessions",
"detailKeys": [
"kinds",
"label",
"agentId",
"search",
"limit",
"activeMinutes",
"includeDerivedTitles",
"includeLastMessage",
"messageLimit"
]
},

View File

@@ -590,6 +590,7 @@ public struct AgentParams: Codable, Sendable {
public let timeout: Int?
public let besteffortdeliver: Bool?
public let lane: String?
public let cleanupbundlemcponrunend: Bool?
public let extrasystemprompt: String?
public let bootstrapcontextmode: AnyCodable?
public let bootstrapcontextrunkind: AnyCodable?
@@ -621,6 +622,7 @@ public struct AgentParams: Codable, Sendable {
timeout: Int?,
besteffortdeliver: Bool?,
lane: String?,
cleanupbundlemcponrunend: Bool?,
extrasystemprompt: String?,
bootstrapcontextmode: AnyCodable?,
bootstrapcontextrunkind: AnyCodable?,
@@ -651,6 +653,7 @@ public struct AgentParams: Codable, Sendable {
self.timeout = timeout
self.besteffortdeliver = besteffortdeliver
self.lane = lane
self.cleanupbundlemcponrunend = cleanupbundlemcponrunend
self.extrasystemprompt = extrasystemprompt
self.bootstrapcontextmode = bootstrapcontextmode
self.bootstrapcontextrunkind = bootstrapcontextrunkind
@@ -683,6 +686,7 @@ public struct AgentParams: Codable, Sendable {
case timeout
case besteffortdeliver = "bestEffortDeliver"
case lane
case cleanupbundlemcponrunend = "cleanupBundleMcpOnRunEnd"
case extrasystemprompt = "extraSystemPrompt"
case bootstrapcontextmode = "bootstrapContextMode"
case bootstrapcontextrunkind = "bootstrapContextRunKind"

View File

@@ -1,4 +1,4 @@
e93b2f54b4d46da18d853f548658ea4c1d84a9ed391f5e0b44673b43adcc4396 config-baseline.json
7956c319e82d288d496a51cb2ff4485ab72ef4900cb089f99e1df8b9ef3bfb73 config-baseline.core.json
cd467228990cdbdebde2fa87d8b1384b94c149e791f2e67250bf17b13162d4a1 config-baseline.channel.json
a7f297a3461e807fd15f8a7c8c68e41071dfc09af2118c24a26d5f534301a654 config-baseline.plugin.json
b05357fa162ba1f1d4ed192671b758d3905602678ff61148568840c6544d6222 config-baseline.json
a4e167f169db58d71c385a31fa2b980772f9fee963e70dd9553f63536cae5aed config-baseline.core.json
35d132fe176bd2bf9f0e46b29de91baba63ec4db3317cc5b294a982b46d16ba9 config-baseline.channel.json
3703c5345288adb9eee8cda3b592147cf4fed25a7782bed21ca83c88c3ca1cc0 config-baseline.plugin.json

View File

@@ -1,2 +1,2 @@
d7f6e6ecdfb78c73760689af5a684c20ec7ca28509d4f63bf0d990a2d739c6ce plugin-sdk-api-baseline.json
584681e4436a4e84c2ff20196ff194a63915caf4dda70de9c27f34ab0d7bde0b plugin-sdk-api-baseline.jsonl
2b7093a57992029cc70126d33544e02eed6c3076a3a6b4ffa6aef7664da0f33d plugin-sdk-api-baseline.json
ea6a2f2326565517b6c42a4d334f615163fb434dbad5e0b8d134c92767714256 plugin-sdk-api-baseline.jsonl

View File

@@ -195,6 +195,10 @@
"source": "Doctor",
"target": "Doctor"
},
{
"source": "Config",
"target": "配置"
},
{
"source": "Memory Wiki",
"target": "Memory Wiki"

View File

@@ -23,10 +23,14 @@ host configuration.
## Session key shapes (examples)
Direct messages collapse to the agents **main** session:
Direct messages collapse to the agents **main** session by default:
- `agent:<agentId>:<mainKey>` (default: `agent:main:main`)
Even when direct-message conversation history is shared with main, sandbox and
tool policy use a derived per-account direct-chat runtime key for external DMs
so channel-originated messages are not treated like local main-session runs.
Groups and channels remain isolated per channel:
- Groups: `agent:<agentId>:<channel>:group:<id>`

View File

@@ -61,15 +61,18 @@ You will need to create a new application with a bot, add the bot to your server
- `bot`
- `applications.commands`
A **Bot Permissions** section will appear below. Enable:
A **Bot Permissions** section will appear below. Enable at least:
- View Channels
- Send Messages
- Read Message History
- Embed Links
- Attach Files
- Add Reactions (optional)
**General Permissions**
- View Channels
**Text Permissions**
- Send Messages
- Read Message History
- Embed Links
- Attach Files
- Add Reactions (optional)
This is the baseline set for normal text channels. If you plan to post in Discord threads, including forum or media channel workflows that create or continue a thread, also enable **Send Messages in Threads**.
Copy the generated URL at the bottom, paste it into your browser, select your server, and click **Continue** to connect. You should now see your bot in the Discord server.
</Step>
@@ -304,7 +307,7 @@ By default, components are single use. Set `components.reusable=true` to allow b
To restrict who can click a button, set `allowedUsers` on that button (Discord user IDs, tags, or `*`). When configured, unmatched users receive an ephemeral denial.
The `/model` and `/models` slash commands open an interactive model picker with provider and model dropdowns plus a Submit step. The picker reply is ephemeral and only the invoking user can use it.
The `/model` and `/models` slash commands open an interactive model picker with provider and model dropdowns plus a Submit step. Unless `commands.modelsWrite=false`, `/models add` also supports adding a new provider/model entry from chat, and newly added models show up without restarting the gateway. The picker reply is ephemeral and only the invoking user can use it.
File attachments:
@@ -520,13 +523,16 @@ Use `bindings[].match.roles` to route Discord guild members to different agents
Typical baseline permissions:
- View Channels
- Send Messages
- Read Message History
- Embed Links
- Attach Files
- Add Reactions (optional)
**General Permissions**
- View Channels
**Text Permissions**
- Send Messages
- Read Message History
- Embed Links
- Attach Files
- Add Reactions (optional)
This is the baseline set for normal text channels. If you plan to post in Discord threads, including forum or media channel workflows that create or continue a thread, also enable **Send Messages in Threads**.
Avoid `Administrator` unless explicitly needed.
</Accordion>

View File

@@ -361,8 +361,8 @@ Surface different features that extend the above defaults.
},
{
"command": "/models",
"description": "List providers or models for a provider",
"usage_hint": "[provider] [page] [limit=<n>|size=<n>|all]"
"description": "List providers/models or add a model",
"usage_hint": "[provider] [page] [limit=<n>|size=<n>|all] | add <provider> <modelId>"
},
{
"command": "/help",

View File

@@ -12,28 +12,28 @@ The CI runs on every push to `main` and every pull request. It uses smart scopin
## Job Overview
| Job | Purpose | When it runs |
| -------------------------------- | -------------------------------------------------------------------------------------------- | ----------------------------------- |
| `preflight` | Detect docs-only changes, changed scopes, changed extensions, and build the CI manifest | Always on non-draft pushes and PRs |
| `security-scm-fast` | Private key detection and workflow audit via `zizmor` | Always on non-draft pushes and PRs |
| `security-dependency-audit` | Dependency-free production lockfile audit against npm advisories | Always on non-draft pushes and PRs |
| `security-fast` | Required aggregate for the fast security jobs | Always on non-draft pushes and PRs |
| `build-artifacts` | Build `dist/` and the Control UI once, upload reusable artifacts for downstream jobs | Node-relevant changes |
| `checks-fast-core` | Fast Linux correctness lanes such as bundled/plugin-contract/protocol checks | Node-relevant changes |
| `checks-fast-contracts-channels` | Sharded channel contract checks with a stable aggregate check result | Node-relevant changes |
| `checks-node-extensions` | Full bundled-plugin test shards across the extension suite | Node-relevant changes |
| `checks-node-core-test` | Core Node test shards, excluding channel, bundled, contract, and extension lanes | Node-relevant changes |
| `extension-fast` | Focused tests for only the changed bundled plugins | When extension changes are detected |
| `check` | Sharded main local gate equivalent: prod types, lint, guards, test types, and strict smoke | Node-relevant changes |
| `check-additional` | Architecture, boundary, extension-surface guards, package-boundary, and gateway-watch shards | Node-relevant changes |
| `build-smoke` | Built-CLI smoke tests and startup-memory smoke | Node-relevant changes |
| `checks` | Remaining Linux Node lanes: channel tests and push-only Node 22 compatibility | Node-relevant changes |
| `check-docs` | Docs formatting, lint, and broken-link checks | Docs changed |
| `skills-python` | Ruff + pytest for Python-backed skills | Python-skill-relevant changes |
| `checks-windows` | Windows-specific test lanes | Windows-relevant changes |
| `macos-node` | macOS TypeScript test lane using the shared built artifacts | macOS-relevant changes |
| `macos-swift` | Swift lint, build, and tests for the macOS app | macOS-relevant changes |
| `android` | Android build and test matrix | Android-relevant changes |
| Job | Purpose | When it runs |
| -------------------------------- | -------------------------------------------------------------------------------------------- | ------------------------------------ |
| `preflight` | Detect docs-only changes, changed scopes, changed extensions, and build the CI manifest | Always on non-draft pushes and PRs |
| `security-scm-fast` | Private key detection and workflow audit via `zizmor` | Always on non-draft pushes and PRs |
| `security-dependency-audit` | Dependency-free production lockfile audit against npm advisories | Always on non-draft pushes and PRs |
| `security-fast` | Required aggregate for the fast security jobs | Always on non-draft pushes and PRs |
| `build-artifacts` | Build `dist/` and the Control UI once, upload reusable artifacts for downstream jobs | Node-relevant changes |
| `checks-fast-core` | Fast Linux correctness lanes such as bundled/plugin-contract/protocol checks | Node-relevant changes |
| `checks-fast-contracts-channels` | Sharded channel contract checks with a stable aggregate check result | Node-relevant changes |
| `checks-node-extensions` | Full bundled-plugin test shards across the extension suite | Node-relevant changes |
| `checks-node-core-test` | Core Node test shards, excluding channel, bundled, contract, and extension lanes | Node-relevant changes |
| `extension-fast` | Focused tests for only the changed bundled plugins | Pull requests with extension changes |
| `check` | Sharded main local gate equivalent: prod types, lint, guards, test types, and strict smoke | Node-relevant changes |
| `check-additional` | Architecture, boundary, extension-surface guards, package-boundary, and gateway-watch shards | Node-relevant changes |
| `build-smoke` | Built-CLI smoke tests and startup-memory smoke | Node-relevant changes |
| `checks` | Remaining Linux Node lanes: channel tests and push-only Node 22 compatibility | Node-relevant changes |
| `check-docs` | Docs formatting, lint, and broken-link checks | Docs changed |
| `skills-python` | Ruff + pytest for Python-backed skills | Python-skill-relevant changes |
| `checks-windows` | Windows-specific test lanes | Windows-relevant changes |
| `macos-node` | macOS TypeScript test lane using the shared built artifacts | macOS-relevant changes |
| `macos-swift` | Swift lint, build, and tests for the macOS app | macOS-relevant changes |
| `android` | Android unit tests for both flavors plus one debug APK build | Android-relevant changes |
## Fail-Fast Order
@@ -42,26 +42,34 @@ Jobs are ordered so cheap checks fail before expensive ones run:
1. `preflight` decides which lanes exist at all. The `docs-scope` and `changed-scope` logic are steps inside this job, not standalone jobs.
2. `security-scm-fast`, `security-dependency-audit`, `security-fast`, `check`, `check-additional`, `check-docs`, and `skills-python` fail quickly without waiting on the heavier artifact and platform matrix jobs.
3. `build-artifacts` overlaps with the fast Linux lanes so downstream consumers can start as soon as the shared build is ready.
4. Heavier platform and runtime lanes fan out after that: `checks-fast-core`, `checks-fast-contracts-channels`, `checks-node-extensions`, `checks-node-core-test`, `extension-fast`, `checks`, `checks-windows`, `macos-node`, `macos-swift`, and `android`.
4. Heavier platform and runtime lanes fan out after that: `checks-fast-core`, `checks-fast-contracts-channels`, `checks-node-extensions`, `checks-node-core-test`, PR-only `extension-fast`, `checks`, `checks-windows`, `macos-node`, `macos-swift`, and `android`.
Scope logic lives in `scripts/ci-changed-scope.mjs` and is covered by unit tests in `src/scripts/ci-changed-scope.test.ts`.
The separate `install-smoke` workflow reuses the same scope script through its own `preflight` job. It computes `run_install_smoke` from the narrower changed-smoke signal, so Docker/install smoke only runs for install, packaging, and container-relevant changes.
CI workflow edits validate the Node CI graph plus workflow linting, but do not force Windows, Android, or macOS native builds by themselves; those platform lanes stay scoped to platform source changes.
Windows Node checks are scoped to Windows-specific process/path wrappers, npm/pnpm/UI runner helpers, package manager config, and the CI workflow surfaces that execute that lane; unrelated source, plugin, install-smoke, and test-only changes stay on the Linux Node lanes so they do not reserve a 16-vCPU Windows worker for coverage that is already exercised by the normal test shards.
The separate `install-smoke` workflow reuses the same scope script through its own `preflight` job. It computes `run_install_smoke` from the narrower changed-smoke signal, so Docker/install smoke runs for install, packaging, container-relevant changes, bundled extension production changes, and the core plugin/channel/gateway/Plugin SDK surfaces that the Docker smoke jobs exercise. Test-only and docs-only edits do not reserve Docker workers. Its QR package smoke forces the Docker `pnpm install` layer to rerun while preserving the BuildKit pnpm store cache, so it still exercises installation without redownloading dependencies on every run. Its gateway-network e2e reuses the runtime image built earlier in the job, so it adds real container-to-container WebSocket coverage without adding another Docker build. A separate `docker-e2e-fast` job runs the bounded bundled-plugin Docker profile under a 120-second command timeout: setup-entry dependency repair plus synthetic bundled-loader failure isolation. The full bundled update/channel matrix remains manual/full-suite because it performs repeated real npm update and doctor repair passes.
Local changed-lane logic lives in `scripts/changed-lanes.mjs` and is executed by `scripts/check-changed.mjs`. That local gate is stricter about architecture boundaries than the broad CI platform scope: core production changes run core prod typecheck plus core tests, core test-only changes run only core test typecheck/tests, extension production changes run extension prod typecheck plus extension tests, and extension test-only changes run only extension test typecheck/tests. Public Plugin SDK or plugin-contract changes expand to extension validation because extensions depend on those core contracts. Release metadata-only version bumps run targeted version/config/root-dependency checks. Unknown root/config changes fail safe to all lanes.
On pushes, the `checks` matrix adds the push-only `compat-node22` lane. On pull requests, that lane is skipped and the matrix stays focused on the normal test/channel lanes.
The slowest Node test families are split into include-file shards so each job stays small: channel contracts split registry and core coverage into eight weighted shards each, auto-reply reply command tests split into four include-pattern shards, and the other large auto-reply reply prefix groups split into two shards each. `check-additional` also separates package-boundary compile/canary work from runtime topology gateway/architecture work.
The slowest Node test families are split or balanced so each job stays small: channel contracts split registry and core coverage into six weighted shards total, bundled plugin tests balance across six extension workers, auto-reply runs as three balanced workers instead of six tiny workers, and agentic gateway/plugin configs are spread across the existing source-only agentic Node jobs instead of waiting on built artifacts. Broad browser, QA, media, and miscellaneous plugin tests use their dedicated Vitest configs instead of the shared plugin catch-all. The broad agents lane uses the shared Vitest file-parallel scheduler because it is import/scheduling dominated rather than owned by a single slow test file. `runtime-config` runs with the infra core-runtime shard to keep the shared runtime shard from owning the tail. `check-additional` keeps package-boundary compile/canary work together and separates runtime topology architecture from gateway watch coverage; the boundary guard shard runs its small independent guards concurrently inside one job, and the gateway watch regression reuses a same-run built `dist/` and `dist-runtime/` tar artifact from `build-artifacts` so it measures watch stability without rebuilding runtime artifacts in its own worker.
Android CI runs both `testPlayDebugUnitTest` and `testThirdPartyDebugUnitTest`, then builds the Play debug APK. The third-party flavor has no separate source set or manifest; its unit-test lane still compiles that flavor with the SMS/call-log BuildConfig flags, while avoiding a duplicate debug APK packaging job on every Android-relevant push.
`extension-fast` is PR-only because push runs already execute the full bundled plugin shards. That keeps changed-plugin feedback for reviews without reserving an extra Blacksmith worker on `main` for coverage already present in `checks-node-extensions`.
GitHub may mark superseded jobs as `cancelled` when a newer push lands on the same PR or `main` ref. Treat that as CI noise unless the newest run for the same ref is also failing. The aggregate shard checks call out this cancellation case explicitly so it is easier to distinguish from a test failure.
GitHub may mark superseded jobs as `cancelled` when a newer push lands on the same PR or `main` ref. Treat that as CI noise unless the newest run for the same ref is also failing. Aggregate shard checks use `!cancelled() && always()` so they still report normal shard failures but do not queue after the whole workflow has already been superseded.
The CI concurrency key is versioned (`CI-v7-*`) so a GitHub-side zombie in an old queue group cannot indefinitely block newer main runs.
## Runners
| Runner | Jobs |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `blacksmith-16vcpu-ubuntu-2404` | `preflight`, `security-scm-fast`, `security-dependency-audit`, `security-fast`, `build-artifacts`, Linux checks, docs checks, Python skills, `android` |
| `blacksmith-32vcpu-windows-2025` | `checks-windows` |
| `blacksmith-12vcpu-macos-latest` | `macos-node`, `macos-swift` on `openclaw/openclaw`; forks fall back to `macos-latest` |
| Runner | Jobs |
| -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ubuntu-24.04` | `preflight`, fast security jobs and aggregates (`security-scm-fast`, `security-dependency-audit`, `security-fast`), fast protocol/contract/bundled checks, sharded channel contract checks, `check` shards except lint, `check-additional` shards and aggregates, Node test aggregate verifiers, docs checks, Python skills, workflow-sanity, labeler, auto-response; install-smoke preflight also uses GitHub-hosted Ubuntu so the Blacksmith matrix can queue earlier |
| `blacksmith-8vcpu-ubuntu-2404` | `build-artifacts`, build-smoke, Linux Node test shards, bundled plugin test shards, remaining built-artifact consumers, `android` |
| `blacksmith-16vcpu-ubuntu-2404` | `check-lint`, which remains CPU-sensitive enough that 8 vCPU cost more than it saved; install-smoke Docker builds, where 32-vCPU queue time cost more than it saved |
| `blacksmith-16vcpu-windows-2025` | `checks-windows` |
| `blacksmith-6vcpu-macos-latest` | `macos-node` on `openclaw/openclaw`; forks fall back to `macos-latest` |
| `blacksmith-12vcpu-macos-latest` | `macos-swift` on `openclaw/openclaw`; forks fall back to `macos-latest` |
## Local Equivalents
@@ -79,4 +87,5 @@ pnpm test:channels
pnpm test:contracts:channels
pnpm check:docs # docs format + lint + broken links
pnpm build # build dist when CI artifact/build-smoke lanes matter
node scripts/ci-run-timings.mjs <run-id> # summarize wall time, queue time, and slowest jobs
```

View File

@@ -166,9 +166,11 @@ Per-session `mcpServers` are not supported in bridge mode. If an ACP client
sends them during `newSession` or `loadSession`, the bridge returns a clear
error instead of silently ignoring them.
If you want ACPX-backed sessions to see OpenClaw plugin tools, enable the
gateway-side ACPX plugin bridge instead of trying to pass per-session
`mcpServers`. See [ACP Agents](/tools/acp-agents#plugin-tools-mcp-bridge).
If you want ACPX-backed sessions to see OpenClaw plugin tools or selected
built-in tools such as `cron`, enable the gateway-side ACPX MCP bridges instead
of trying to pass per-session `mcpServers`. See
[ACP Agents](/tools/acp-agents#plugin-tools-mcp-bridge) and
[OpenClaw tools MCP bridge](/tools/acp-agents#openclaw-tools-mcp-bridge).
## Use from `acpx` (Codex, Claude, other ACP clients)

View File

@@ -38,6 +38,7 @@ openclaw config get browser.executablePath
openclaw config set browser.executablePath "/usr/bin/google-chrome"
openclaw config set agents.defaults.heartbeat.every "2h"
openclaw config set agents.list[0].tools.exec.node "node-id-or-name"
openclaw config set agents.defaults.models '{"openai-codex/gpt-5.4":{}}' --strict-json --merge
openclaw config set channels.discord.token --ref-provider default --ref-source env --ref-id DISCORD_BOT_TOKEN
openclaw config set secrets.providers.vaultfile --provider-source file --provider-path /etc/openclaw/secrets.json --provider-mode json
openclaw config unset plugins.entries.brave.config.webSearch.apiKey
@@ -105,6 +106,22 @@ openclaw config set channels.whatsapp.groups '["*"]' --strict-json
`config get <path> --json` prints the raw value as JSON instead of terminal-formatted text.
Object assignment replaces the target path by default. Protected map/list paths
that commonly hold user-added entries, such as `agents.defaults.models`,
`models.providers`, `models.providers.<id>.models`, `plugins.entries`, and
`auth.profiles`, refuse replacements that would remove existing entries unless
you pass `--replace`.
Use `--merge` when adding entries to those maps:
```bash
openclaw config set agents.defaults.models '{"openai-codex/gpt-5.4":{}}' --strict-json --merge
openclaw config set models.providers.ollama.models '[{"id":"llama3.2","name":"Llama 3.2"}]' --strict-json --merge
```
Use `--replace` only when you intentionally want the provided value to become
the complete target value.
## `config set` modes
`openclaw config set` supports four assignment styles:
@@ -342,6 +359,9 @@ If dry-run fails:
post-change config before committing it to disk. If the new payload fails schema
validation or looks like a destructive clobber, the active config is left alone
and the rejected payload is saved beside it as `openclaw.json.rejected.*`.
The active config path must be a regular file. Symlinked `openclaw.json`
layouts are unsupported for writes; use `OPENCLAW_CONFIG_PATH` to point directly
at the real file instead.
Prefer CLI writes for small edits:
@@ -366,7 +386,7 @@ last-known-good backup during startup or hot reload. See
## Subcommands
- `config file`: Print the active config file path (resolved from `OPENCLAW_CONFIG_PATH` or default location).
- `config file`: Print the active config file path (resolved from `OPENCLAW_CONFIG_PATH` or default location). The path should name a regular file, not a symlink.
Restart the gateway after edits.
@@ -379,3 +399,31 @@ gateway.
openclaw config validate
openclaw config validate --json
```
After `openclaw config validate` is passing, you can use the local TUI to have
an embedded agent compare the active config against the docs while you validate
each change from the same terminal:
If validation is already failing, start with `openclaw configure` or
`openclaw doctor --fix`. `openclaw chat` does not bypass the invalid-config
guard.
```bash
openclaw chat
```
Then inside the TUI:
```text
!openclaw config file
!openclaw docs gateway auth token secretref
!openclaw config validate
!openclaw doctor
```
Typical repair loop:
- Ask the agent to compare your current config with the relevant docs page and suggest the smallest fix.
- Apply targeted edits with `openclaw config set` or `openclaw configure`.
- Rerun `openclaw config validate` after each change.
- If validation passes but the runtime is still unhealthy, run `openclaw doctor` or `openclaw doctor --fix` for migration and repair help.

View File

@@ -11,6 +11,8 @@ Interactive prompt to set up credentials, devices, and agent defaults.
Note: The **Model** section now includes a multi-select for the
`agents.defaults.models` allowlist (what shows up in `/model` and the model picker).
Provider-scoped setup choices merge their selected models into the existing
allowlist instead of replacing unrelated providers already in the config.
When configure starts from a provider auth choice, the default-model and
allowlist pickers prefer that provider automatically. For paired providers such

View File

@@ -42,6 +42,7 @@ Notes:
- `--fix` (alias for `--repair`) writes a backup to `~/.openclaw/openclaw.json.bak` and drops unknown config keys, listing each removal.
- State integrity checks now detect orphan transcript files in the sessions directory and can archive them as `.deleted.<timestamp>` to reclaim space safely.
- Doctor also scans `~/.openclaw/cron/jobs.json` (or `cron.store`) for legacy cron job shapes and can rewrite them in place before the scheduler has to auto-normalize them at runtime.
- Doctor repairs missing bundled plugin runtime dependencies without requiring write access to the installed OpenClaw package. For root-owned npm installs or hardened systemd units, set `OPENCLAW_PLUGIN_STAGE_DIR` to a writable directory such as `/var/lib/openclaw/plugin-runtime-deps`.
- Doctor auto-migrates legacy flat Talk config (`talk.voiceId`, `talk.modelId`, and friends) into `talk.provider` + `talk.providers.<provider>`.
- Repeat `doctor --fix` runs no longer report/apply Talk normalization when the only difference is object key order.
- Doctor includes a memory-search readiness check and can recommend `openclaw configure --section model` when embedding credentials are missing.

View File

@@ -111,6 +111,59 @@ Options:
- `--days <days>`: number of days to include (default `30`).
### `gateway stability`
Fetch the recent diagnostic stability recorder from a running Gateway.
```bash
openclaw gateway stability
openclaw gateway stability --type payload.large
openclaw gateway stability --bundle latest
openclaw gateway stability --bundle latest --export
openclaw gateway stability --json
```
Options:
- `--limit <limit>`: maximum number of recent events to include (default `25`, max `1000`).
- `--type <type>`: filter by diagnostic event type, such as `payload.large` or `diagnostic.memory.pressure`.
- `--since-seq <seq>`: include only events after a diagnostic sequence number.
- `--bundle [path]`: read a persisted stability bundle instead of calling the running Gateway. Use `--bundle latest` (or just `--bundle`) for the newest bundle under the state directory, or pass a bundle JSON path directly.
- `--export`: write a shareable support diagnostics zip instead of printing stability details.
- `--output <path>`: output path for `--export`.
Notes:
- The recorder is active by default. Set `diagnostics.enabled: false` only when you need to disable Gateway diagnostic heartbeat collection.
- Records keep operational metadata: event names, counts, byte sizes, memory readings, queue/session state, channel/plugin names, and redacted session summaries. They do not keep chat text, webhook bodies, tool outputs, raw request or response bodies, tokens, cookies, secret values, hostnames, or raw session ids.
- On fatal Gateway exits, shutdown timeouts, and restart startup failures, OpenClaw writes the same diagnostic snapshot to `~/.openclaw/logs/stability/openclaw-stability-*.json` when the recorder has events. Inspect the newest bundle with `openclaw gateway stability --bundle latest`; `--limit`, `--type`, and `--since-seq` also apply to bundle output.
### `gateway diagnostics export`
Write a local diagnostics zip that is designed to attach to bug reports.
```bash
openclaw gateway diagnostics export
openclaw gateway diagnostics export --output openclaw-diagnostics.zip
openclaw gateway diagnostics export --json
```
Options:
- `--output <path>`: output zip path. Defaults to a support export under the state directory.
- `--log-lines <count>`: maximum sanitized log lines to include (default `5000`).
- `--log-bytes <bytes>`: maximum log bytes to inspect (default `1000000`).
- `--url <url>`: Gateway WebSocket URL for the health snapshot.
- `--token <token>`: Gateway token for the health snapshot.
- `--password <password>`: Gateway password for the health snapshot.
- `--timeout <ms>`: status/health snapshot timeout (default `3000`).
- `--no-stability-bundle`: skip persisted stability bundle lookup.
- `--json`: print the written path, size, and manifest as JSON.
The export contains a manifest, a Markdown summary, config shape, sanitized config details, sanitized log summaries, sanitized Gateway status/health snapshots, and the newest stability bundle when one exists.
It is meant to be shared. It keeps operational details that help debugging, such as safe OpenClaw log fields, subsystem names, status codes, durations, configured modes, ports, plugin ids, provider ids, non-secret feature settings, and redacted operational log messages. It omits or redacts chat text, webhook bodies, tool outputs, credentials, cookies, account/message identifiers, prompt/instruction text, hostnames, and secret values. When a LogTape-style message looks like user/chat/tool payload text, the export keeps only that a message was omitted plus its byte count.
### `gateway status`
`gateway status` shows the Gateway service (launchd/systemd/schtasks) plus an optional probe of connectivity/auth capability.

View File

@@ -1532,6 +1532,9 @@ Options:
- `--json`
- `--plain`
`--all` includes bundled provider-owned static catalog rows before auth is
configured. Rows remain unavailable until matching provider credentials exist.
### `models status`
Options:

View File

@@ -369,6 +369,9 @@ Important behavior:
reachable right now
- runtime adapters decide which transport shapes they actually support at
execution time
- embedded Pi exposes configured MCP tools in normal `coding` and `messaging`
tool profiles; `minimal` still hides them, and `tools.deny: ["bundle-mcp"]`
disables them explicitly
## Saved MCP server definitions

View File

@@ -43,6 +43,9 @@ Probe rows can come from auth profiles, env credentials, or `models.json`.
Notes:
- `models set <model-or-alias>` accepts `provider/model` or an alias.
- `models list --all` includes bundled provider-owned static catalog rows even
when you have not authenticated with that provider yet. Those rows still show
as unavailable until matching auth is configured.
- Model refs are parsed by splitting on the **first** `/`. If the model ID includes `/` (OpenRouter-style), include the provider prefix (example: `openrouter/moonshotai/kimi-k2`).
- If you omit the provider, OpenClaw resolves the input as an alias first, then
as a unique configured-provider match for that exact model id, and only then

View File

@@ -33,7 +33,7 @@ openclaw plugins enable <id>
openclaw plugins disable <id>
openclaw plugins uninstall <id>
openclaw plugins doctor
openclaw plugins update <id>
openclaw plugins update <id-or-npm-spec>
openclaw plugins update --all
openclaw plugins marketplace list <marketplace>
openclaw plugins marketplace list <marketplace> --json
@@ -76,6 +76,8 @@ bundled-plugin recovery path for plugins that explicitly opt into
`--force` reuses the existing install target and overwrites an already-installed
plugin or hook pack in place. Use it when you are intentionally reinstalling
the same id from a new local path, archive, ClawHub package, or npm artifact.
For routine upgrades of an already tracked npm plugin, prefer
`openclaw plugins update <id-or-npm-spec>`.
`--pin` applies to npm installs only. It is not supported with `--marketplace`,
because marketplace installs persist marketplace source metadata instead of an
@@ -243,9 +245,20 @@ or exact version. OpenClaw resolves that package name back to the tracked plugin
record, updates that installed plugin, and records the new npm spec for future
id-based updates.
Passing the npm package name without a version or tag also resolves back to the
tracked plugin record. Use this when a plugin was pinned to an exact version and
you want to move it back to the registry's default release line.
Before a live npm update, OpenClaw checks the installed package version against
the npm registry metadata. If the installed version and recorded artifact
identity already match the resolved target, the update is skipped without
downloading, reinstalling, or rewriting `openclaw.json`.
When a stored integrity hash exists and the fetched artifact hash changes,
OpenClaw prints a warning and asks for confirmation before proceeding. Use
global `--yes` to bypass prompts in CI/non-interactive runs.
OpenClaw treats that as npm artifact drift. The interactive
`openclaw plugins update` command prints the expected and actual hashes and asks
for confirmation before proceeding. Non-interactive update helpers fail closed
unless the caller supplies an explicit continuation policy.
`--dangerously-force-unsafe-install` is also available on `plugins update` as a
break-glass override for built-in dangerous-code scan false positives during
@@ -292,6 +305,10 @@ openclaw plugins doctor
compatibility notices. When everything is clean it prints `No plugin issues
detected.`
For module-shape failures such as missing `register`/`activate` exports, rerun
with `OPENCLAW_PLUGIN_LOAD_DEBUG=1` to include a compact export-shape summary in
the diagnostic output.
### Marketplace
```bash

View File

@@ -1,14 +1,17 @@
---
summary: "CLI reference for `openclaw tui` (terminal UI connected to the Gateway)"
summary: "CLI reference for `openclaw tui` (Gateway-backed or local embedded terminal UI)"
read_when:
- You want a terminal UI for the Gateway (remote-friendly)
- You want to pass url/token/session from scripts
- You want to run the TUI in local embedded mode without a Gateway
- You want to use openclaw chat or openclaw tui --local
title: "tui"
---
# `openclaw tui`
Open the terminal UI connected to the Gateway.
Open the terminal UI connected to the Gateway, or run it in local embedded
mode.
Related:
@@ -16,15 +19,48 @@ Related:
Notes:
- `chat` and `terminal` are aliases for `openclaw tui --local`.
- `--local` cannot be combined with `--url`, `--token`, or `--password`.
- `tui` resolves configured gateway auth SecretRefs for token/password auth when possible (`env`/`file`/`exec` providers).
- When launched from inside a configured agent workspace directory, TUI auto-selects that agent for the session key default (unless `--session` is explicitly `agent:<id>:...`).
- Local mode uses the embedded agent runtime directly. Most local tools work, but Gateway-only features are unavailable.
- Local mode adds `/auth [provider]` inside the TUI command surface.
## Examples
```bash
openclaw chat
openclaw tui --local
openclaw tui
openclaw tui --url ws://127.0.0.1:18789 --token <token>
openclaw tui --session main --deliver
openclaw chat --message "Compare my config to the docs and tell me what to fix"
# when run inside an agent workspace, infers that agent automatically
openclaw tui --session bugfix
```
## Config repair loop
Use local mode when the current config already validates and you want the
embedded agent to inspect it, compare it against the docs, and help repair it
from the same terminal:
If `openclaw config validate` is already failing, use `openclaw configure` or
`openclaw doctor --fix` first. `openclaw chat` does not bypass the invalid-
config guard.
```bash
openclaw chat
```
Then inside the TUI:
```text
!openclaw config file
!openclaw docs gateway auth token secretref
!openclaw config validate
!openclaw doctor
```
Apply targeted fixes with `openclaw config set` or `openclaw configure`, then
rerun `openclaw config validate`. See [TUI](/web/tui) and [Config](/cli/config).

View File

@@ -36,7 +36,9 @@ openclaw --update
- `--channel <stable|beta|dev>`: set the update channel (git + npm; persisted in config).
- `--tag <dist-tag|version|spec>`: override the package target for this update only. For package installs, `main` maps to `github:openclaw/openclaw#main`.
- `--dry-run`: preview planned update actions (channel/tag/target/restart flow) without writing config, installing, syncing plugins, or restarting.
- `--json`: print machine-readable `UpdateRunResult` JSON.
- `--json`: print machine-readable `UpdateRunResult` JSON, including
`postUpdate.plugins.integrityDrifts` when npm plugin artifact drift is
detected during post-update plugin sync.
- `--timeout <seconds>`: per-step timeout (default is 1200s).
- `--yes`: skip confirmation prompts (for example downgrade confirmation)
@@ -80,6 +82,12 @@ install method aligned:
The Gateway core auto-updater (when enabled via config) reuses this same update path.
For package-manager installs, `openclaw update` resolves the target package
version before invoking the package manager. If the installed version exactly
matches the target and no update-channel change needs to be persisted, the
command exits as skipped before package install, plugin sync, completion refresh,
or gateway restart work.
## Git checkout flow
Channels:
@@ -101,6 +109,11 @@ High-level:
8. Runs `openclaw doctor` as the final “safe update” check.
9. Syncs plugins to the active channel (dev uses bundled extensions; stable/beta uses npm) and updates npm-installed plugins.
If an exact pinned npm plugin update resolves to an artifact whose integrity
differs from the stored install record, `openclaw update` aborts that plugin
artifact update instead of installing it. Reinstall or update the plugin
explicitly only after verifying that you trust the new artifact.
If pnpm bootstrap still fails, the updater now stops early with a package-manager-specific error instead of trying `npm run build` inside the checkout.
## `--update` shorthand

View File

@@ -229,8 +229,20 @@ When enabled, the Gateway **Dreams** tab shows:
- a distinct grounded Scene lane for staged historical replay entries
- an expandable Dream Diary reader backed by `doctor.memory.dreamDiary`
## Troubleshooting
### Dreaming never runs (status shows blocked)
The managed dreaming cron rides the default agent's heartbeat. If heartbeat is not firing for that agent, the cron enqueues a system event that nobody consumes and dreaming silently does not run. Both `openclaw memory status` and `/dreaming status` will report `blocked` in that case and name the agent whose heartbeat is the blocker.
Two common causes:
- Another agent declares an explicit `heartbeat:` block. When any entry in `agents.list` has its own `heartbeat` block, only those agents heartbeat — the defaults stop applying to everyone else, so the default agent can go silent. Move the heartbeat settings to `agents.defaults.heartbeat`, or add an explicit `heartbeat` block on the default agent. See [Scope and precedence](/gateway/heartbeat#scope-and-precedence).
- `heartbeat.every` is `0`, empty, or unparseable. The cron has no interval to schedule against, so the heartbeat is effectively disabled. Set `every` to a positive duration such as `30m`. See [Defaults](/gateway/heartbeat#defaults).
## Related
- [Heartbeat](/gateway/heartbeat)
- [Memory](/concepts/memory)
- [Memory Search](/concepts/memory-search)
- [memory CLI](/cli/memory)

View File

@@ -167,6 +167,10 @@ Defaults live under `agents.defaults.silentReply` and
`agents.defaults.silentReplyRewrite`; `surfaces.<id>.silentReply` and
`surfaces.<id>.silentReplyRewrite` can override them per surface.
When the parent session has one or more pending spawned subagent runs, bare
silent replies are dropped on all surfaces instead of being rewritten, so the
parent stays quiet until the child completion event delivers the real reply.
## Related
- [Streaming](/concepts/streaming) — real-time message delivery

View File

@@ -390,7 +390,8 @@ OpenClaw ships with the piai catalog. These providers require **no**
- Provider: `vercel-ai-gateway`
- Auth: `AI_GATEWAY_API_KEY`
- Example model: `vercel-ai-gateway/anthropic/claude-opus-4.6`
- Example models: `vercel-ai-gateway/anthropic/claude-opus-4.6`,
`vercel-ai-gateway/moonshotai/kimi-k2.6`
- CLI: `openclaw onboard --auth-choice ai-gateway-api-key`
### Kilo Gateway
@@ -411,7 +412,7 @@ See [/providers/kilocode](/providers/kilocode) for setup details.
### Other bundled provider plugins
- OpenRouter: `openrouter` (`OPENROUTER_API_KEY`)
- Example model: `openrouter/auto`
- Example models: `openrouter/auto`, `openrouter/moonshotai/kimi-k2.6`
- OpenClaw applies OpenRouter's documented app-attribution headers only when
the request actually targets `openrouter.ai`
- OpenRouter-specific Anthropic `cache_control` markers are likewise gated to

View File

@@ -67,6 +67,24 @@ to `zai/*`.
Provider configuration examples (including OpenCode) live in
[/providers/opencode](/providers/opencode).
### Safe allowlist edits
Use additive writes when updating `agents.defaults.models` by hand:
```bash
openclaw config set agents.defaults.models '{"openai-codex/gpt-5.4":{}}' --strict-json --merge
```
`openclaw config set` protects model/provider maps from accidental clobbers. A
plain object assignment to `agents.defaults.models`, `models.providers`, or
`models.providers.<id>.models` is rejected when it would remove existing
entries. Use `--merge` for additive changes; use `--replace` only when the
provided value should become the complete target value.
Interactive provider setup and `openclaw configure --section model` also merge
provider-scoped selections into the existing allowlist, so adding Codex,
Ollama, or another provider does not drop unrelated model entries.
## "Model is not allowed" (and why replies stop)
If `agents.defaults.models` is set, it becomes the **allowlist** for `/model` and for
@@ -114,6 +132,9 @@ Notes:
- `/model` (and `/model list`) is a compact, numbered picker (model family + available providers).
- On Discord, `/model` and `/models` open an interactive picker with provider and model dropdowns plus a Submit step.
- `/models add` is available by default and can be disabled with `commands.modelsWrite=false`.
- When enabled, `/models add <provider> <modelId>` is the fastest path; bare `/models add` starts a provider-first guided flow where supported.
- After `/models add`, the new model becomes available in `/models` and `/model` without restarting the gateway.
- `/model <#>` selects from that picker.
- `/model` persists the new session selection immediately.
- If the agent is idle, the next run uses the new model right away.
@@ -132,6 +153,14 @@ Notes:
Full command behavior/config: [Slash commands](/tools/slash-commands).
Examples:
```text
/models add
/models add ollama glm-5.1:cloud
/models add lmstudio qwen/qwen3.5-9b
```
## CLI commands
```bash
@@ -167,6 +196,10 @@ Shows configured models by default. Useful flags:
- `--plain`: one model per line
- `--json`: machinereadable output
`--all` includes bundled provider-owned static catalog rows before auth is
configured, so discovery-only views can show models that are unavailable until
you add matching provider credentials.
### `models status`
Shows the resolved primary model, fallbacks, image model, and an auth overview

View File

@@ -16,7 +16,7 @@ orchestrate sub-agents.
| Tool | What it does |
| ------------------ | --------------------------------------------------------------------------- |
| `sessions_list` | List sessions with optional filters (kind, recency) |
| `sessions_list` | List sessions with optional filters (kind, label, agent, recency, preview) |
| `sessions_history` | Read the transcript of a specific session |
| `sessions_send` | Send a message to another session and optionally wait |
| `sessions_spawn` | Spawn an isolated sub-agent session for background work |
@@ -26,9 +26,13 @@ orchestrate sub-agents.
## Listing and reading sessions
`sessions_list` returns sessions with their key, kind, channel, model, token
counts, and timestamps. Filter by kind (`main`, `group`, `cron`, `hook`,
`node`) or recency (`activeMinutes`).
`sessions_list` returns sessions with their key, agentId, kind, channel, model,
token counts, and timestamps. Filter by kind (`main`, `group`, `cron`, `hook`,
`node`), exact `label`, exact `agentId`, search text, or recency
(`activeMinutes`). When you need mailbox-style triage, it can also ask for
derived titles, last-message previews, or bounded recent messages. Preview
transcript reads are scoped to sessions visible under the configured session
tool visibility policy.
`sessions_history` fetches the conversation transcript for a specific session.
By default, tool results are excluded -- pass `includeTools: true` to see them.

View File

@@ -69,6 +69,10 @@ Sessions are reused until they expire:
When both daily and idle resets are configured, whichever expires first wins.
Sessions with an active provider-owned CLI session are not cut by the implicit
daily default. Use `/reset` or configure `session.reset` explicitly when those
sessions should expire on a timer.
## Where state lives
All session state is owned by the **gateway**. UI clients query the gateway for

View File

@@ -18,7 +18,8 @@ When `agents.defaults.typingMode` is **unset**, OpenClaw keeps the legacy behavi
- **Direct chats**: typing starts immediately once the model loop begins.
- **Group chats with a mention**: typing starts immediately.
- **Group chats without a mention**: typing starts only when message text begins streaming.
- **Heartbeat runs**: typing is disabled.
- **Heartbeat runs**: typing starts when the heartbeat run begins if the
resolved heartbeat target is a typing-capable chat and typing is not disabled.
## Modes
@@ -64,6 +65,11 @@ You can override mode or cadence per session:
matched case-insensitively).
- `thinking` only fires if the run streams reasoning (`reasoningLevel: "stream"`).
If the model doesnt emit reasoning deltas, typing wont start.
- Heartbeats never show typing, regardless of mode.
- Heartbeat typing is a liveness signal for the resolved delivery target. It
starts at heartbeat run start instead of following `message` or `thinking`
stream timing. Set `typingMode: "never"` to disable it.
- Heartbeats do not show typing when `target: "none"`, when the target cannot
be resolved, when chat delivery is disabled for the heartbeat, or when the
channel does not support typing.
- `typingIntervalSeconds` controls the **refresh cadence**, not the start time.
The default is 6 seconds.

View File

@@ -1226,6 +1226,7 @@
"tools/pdf",
"tools/reactions",
"tools/thinking",
"tools/tokenjuice",
"tools/video-generation"
]
},
@@ -1292,6 +1293,7 @@
"providers/sglang",
"providers/stepfun",
"providers/synthetic",
"providers/tencent",
"providers/together",
"providers/venice",
"providers/vercel-ai-gateway",

View File

@@ -143,6 +143,8 @@ The provider id becomes the left side of your model ref:
1. **Selects a backend** based on the provider prefix (`codex-cli/...`).
2. **Builds a system prompt** using the same OpenClaw prompt + workspace context.
3. **Executes the CLI** with a session id (if supported) so history stays consistent.
The bundled `claude-cli` backend keeps a Claude stdio process alive per
OpenClaw session and sends follow-up turns over stream-json stdin.
4. **Parses output** (JSON or plain text) and returns the final text.
5. **Persists session ids** per backend, so follow-ups reuse the same CLI session.
@@ -179,6 +181,13 @@ child process environment for the run.
- `always`: always send a session id (new UUID if none stored).
- `existing`: only send a session id if one was stored before.
- `none`: never send a session id.
- `claude-cli` defaults to `liveSession: "claude-stdio"`, `output: "jsonl"`,
and `input: "stdin"` so follow-up turns reuse the live Claude process while
it is active. If the Gateway restarts or the idle process exits, OpenClaw
resumes from the stored Claude session id.
- Stored CLI sessions are provider-owned continuity. The implicit daily session
reset does not cut them; `/reset` and explicit `session.reset` policies still
do.
Serialization notes:

View File

@@ -1238,6 +1238,8 @@ Time format in system prompt. Default: `auto` (OS preference).
- `elevatedDefault`: default elevated-output level for agents. Values: `"off"`, `"on"`, `"ask"`, `"full"`. Default: `"on"`.
- `model.primary`: format `provider/model` (e.g. `openai/gpt-5.4`). If you omit the provider, OpenClaw tries an alias first, then a unique configured-provider match for that exact model id, and only then falls back to the configured default provider (deprecated compatibility behavior, so prefer explicit `provider/model`). If that provider no longer exposes the configured default model, OpenClaw falls back to the first configured provider/model instead of surfacing a stale removed-provider default.
- `models`: the configured model catalog and allowlist for `/model`. Each entry can include `alias` (shortcut) and `params` (provider-specific, for example `temperature`, `maxTokens`, `cacheRetention`, `context1m`).
- Safe edits: use `openclaw config set agents.defaults.models '<json>' --strict-json --merge` to add entries. `config set` refuses replacements that would remove existing allowlist entries unless you pass `--replace`.
- Provider-scoped configure/onboarding flows merge selected provider models into this map and preserve unrelated providers already configured.
- `params`: global default provider parameters applied to all models. Set at `agents.defaults.params` (e.g. `{ cacheRetention: "long" }`).
- `params` merge precedence (config): `agents.defaults.params` (global base) is overridden by `agents.defaults.models["provider/model"].params` (per-model), then `agents.list[].params` (matching agent id) overrides by key. See [Prompt Caching](/reference/prompt-caching) for details.
- `embeddedHarness`: default low-level embedded agent runtime policy. Use `runtime: "auto"` to let registered plugin harnesses claim supported models, `runtime: "pi"` to force the built-in PI harness, or a registered harness id such as `runtime: "codex"`. Set `fallback: "none"` to disable automatic PI fallback.
@@ -1266,7 +1268,7 @@ Codex app-server harness.
```
- `runtime`: `"auto"`, `"pi"`, or a registered plugin harness id. The bundled Codex plugin registers `codex`.
- `fallback`: `"pi"` or `"none"`. `"pi"` keeps the built-in PI harness as the compatibility fallback. `"none"` makes missing or unsupported plugin harness selection fail instead of silently using PI.
- `fallback`: `"pi"` or `"none"`. `"pi"` keeps the built-in PI harness as the compatibility fallback when no plugin harness is selected. `"none"` makes missing or unsupported plugin harness selection fail instead of silently using PI. Selected plugin harness failures always surface directly.
- Environment overrides: `OPENCLAW_AGENT_RUNTIME=<id|auto|pi>` overrides `runtime`; `OPENCLAW_AGENT_HARNESS_FALLBACK=none` disables PI fallback for that process.
- For Codex-only deployments, set `model: "codex/gpt-5.4"`, `embeddedHarness.runtime: "codex"`, and `embeddedHarness.fallback: "none"`.
- This only controls the embedded chat harness. Media generation, vision, PDF, music, video, and TTS still use their provider/model settings.
@@ -1338,6 +1340,28 @@ Replace the entire OpenClaw-assembled system prompt with a fixed string. Set at
}
```
### `agents.defaults.promptOverlays`
Provider-independent prompt overlays applied by model family. GPT-5-family model ids receive the shared behavior contract across providers; `personality` controls only the friendly interaction-style layer.
```json5
{
agents: {
defaults: {
promptOverlays: {
gpt5: {
personality: "friendly", // friendly | on | off
},
},
},
},
}
```
- `"friendly"` (default) and `"on"` enable the friendly interaction-style layer.
- `"off"` disables only the friendly layer; the tagged GPT-5 behavior contract remains enabled.
- Legacy `plugins.entries.openai.config.personality` is still read when this shared setting is unset.
### `agents.defaults.heartbeat`
Periodic heartbeat runs.
@@ -2562,6 +2586,7 @@ OpenClaw uses the built-in model catalog. Add custom providers via `models.provi
- `models.mode`: provider catalog behavior (`merge` or `replace`).
- `models.providers`: custom provider map keyed by provider id.
- Safe edits: use `openclaw config set models.providers.<id> '<json>' --strict-json --merge` or `openclaw config set models.providers.<id>.models '<json-array>' --strict-json --merge` for additive updates. `config set` refuses destructive replacements unless you pass `--replace`.
- `models.providers.*.api`: request adapter (`openai-completions`, `openai-responses`, `anthropic-messages`, `google-generative-ai`, etc).
- `models.providers.*.apiKey`: provider credential (prefer SecretRef/env substitution).
- `models.providers.*.auth`: auth strategy (`api-key`, `token`, `oauth`, `aws-sdk`).
@@ -3878,6 +3903,8 @@ Split config into multiple files:
- Sibling keys: merged after includes (override included values).
- Nested includes: up to 10 levels deep.
- Paths: resolved relative to the including file, but must stay inside the top-level config directory (`dirname` of `openclaw.json`). Absolute/`../` forms are allowed only when they still resolve inside that boundary.
- OpenClaw-owned writes that change only one top-level section backed by a single-file include write through to that included file. For example, `plugins install` updates `plugins: { $include: "./plugins.json5" }` in `plugins.json5` and leaves `openclaw.json` intact.
- Root includes, include arrays, and includes with sibling overrides are read-only for OpenClaw-owned writes; those writes fail closed instead of flattening the config.
- Errors: clear messages for missing files, parse errors, and circular includes.
---

View File

@@ -10,6 +10,10 @@ title: "Configuration"
# Configuration
OpenClaw reads an optional <Tooltip tip="JSON5 supports comments and trailing commas">**JSON5**</Tooltip> config from `~/.openclaw/openclaw.json`.
The active config path must be a regular file. Symlinked `openclaw.json`
layouts are unsupported for OpenClaw-owned writes; an atomic write may replace
the path instead of preserving the symlink. If you keep config outside the
default state directory, point `OPENCLAW_CONFIG_PATH` directly at the real file.
If the file is missing, OpenClaw uses safe defaults. Common reasons to add a config:
@@ -100,6 +104,13 @@ The Gateway also keeps a trusted last-known-good copy after a successful startup
`openclaw.json` is later changed outside OpenClaw and no longer validates, startup
and hot reload preserve the broken file as a timestamped `.clobbered.*` snapshot,
restore the last-known-good copy, and log a loud warning with the recovery reason.
Startup read recovery also treats sharp size drops, missing config metadata, and a
missing `gateway.mode` as critical clobber signatures when the last-known-good
copy had those fields.
If a status/log line is accidentally prepended before an otherwise valid JSON
config, gateway startup and `openclaw doctor --fix` can strip the prefix,
preserve the polluted file as `.clobbered.*`, and continue with the recovered
JSON.
The next main-agent turn also receives a system-event warning telling it that the
config was restored and must not be blindly rewritten. Last-known-good promotion
is updated after validated startup and after accepted hot reloads, including
@@ -162,6 +173,7 @@ placeholders such as `***` or shortened token values.
```
- `agents.defaults.models` defines the model catalog and acts as the allowlist for `/model`.
- Use `openclaw config set agents.defaults.models '<json>' --strict-json --merge` to add allowlist entries without removing existing models. Plain replacements that would remove entries are rejected unless you pass `--replace`.
- Model refs use `provider/model` format (e.g. `anthropic/claude-opus-4-6`).
- `agents.defaults.imageMaxDimensionPx` controls transcript/tool image downscaling (default `1200`); lower values usually reduce vision-token usage on screenshot-heavy runs.
- See [Models CLI](/concepts/models) for switching models in chat and [Model Failover](/concepts/model-failover) for auth rotation and fallback behavior.
@@ -496,6 +508,12 @@ placeholders such as `***` or shortened token values.
- **Sibling keys**: merged after includes (override included values)
- **Nested includes**: supported up to 10 levels deep
- **Relative paths**: resolved relative to the including file
- **OpenClaw-owned writes**: when a write changes only one top-level section
backed by a single-file include such as `plugins: { $include: "./plugins.json5" }`,
OpenClaw updates that included file and leaves `openclaw.json` intact
- **Unsupported write-through**: root includes, include arrays, and includes
with sibling overrides fail closed for OpenClaw-owned writes instead of
flattening the config
- **Error handling**: clear errors for missing files, parse errors, and circular includes
</Accordion>

View File

@@ -26,6 +26,8 @@ Short guide to verify channel connectivity without guessing.
- Creds on disk: `ls -l ~/.openclaw/credentials/whatsapp/<accountId>/creds.json` (mtime should be recent).
- Session store: `ls -l ~/.openclaw/agents/<agentId>/sessions/sessions.json` (path can be overridden in config). Count and recent recipients are surfaced via `status`.
- Relink flow: `openclaw channels logout && openclaw channels login --verbose` when status codes 409515 or `loggedOut` appear in logs. (Note: the QR login flow auto-restarts once for status 515 after pairing.)
- Diagnostics are enabled by default. The gateway records operational facts unless `diagnostics.enabled: false` is set. Memory events record RSS/heap byte counts, threshold pressure, and growth pressure. Oversized-payload events record what was rejected, truncated, or chunked, plus sizes and limits when available. They do not record the message text, attachment contents, webhook body, raw request or response body, tokens, cookies, or secret values. The same heartbeat starts the bounded stability recorder, which is available through `openclaw gateway stability` or the `diagnostics.stability` Gateway RPC. Fatal Gateway exits, shutdown timeouts, and restart startup failures persist the latest recorder snapshot under `~/.openclaw/logs/stability/` when events exist; inspect the newest saved bundle with `openclaw gateway stability --bundle latest`.
- For bug reports, run `openclaw gateway diagnostics export` and attach the generated zip. The export combines a Markdown summary, the newest stability bundle, sanitized log metadata, sanitized Gateway status/health snapshots, and config shape. It is meant to be shared: chat text, webhook bodies, tool outputs, credentials, cookies, account/message identifiers, and secret values are omitted or redacted.
## Health monitor config

View File

@@ -262,6 +262,9 @@ Use `accountId` to target a specific account on multi-account channels like Tele
outbound message is sent.
- If `showOk`, `showAlerts`, and `useIndicator` are all disabled, the run is skipped up front as `reason=alerts-disabled`.
- If only alert delivery is disabled, OpenClaw can still run the heartbeat, update due-task timestamps, restore the session idle timestamp, and suppress the outward alert payload.
- If the resolved heartbeat target supports typing, OpenClaw shows typing while
the heartbeat run is active. This uses the same target the heartbeat would
send chat output to, and it is disabled by `typingMode: "never"`.
- Heartbeat-only replies do **not** keep the session alive; the last `updatedAt`
is restored so idle expiry behaves normally.
- Detached [background tasks](/automation/tasks) can enqueue a system event and wake heartbeat when the main session should notice something quickly. That wake does not make the heartbeat run a background task.

View File

@@ -18,6 +18,13 @@ handshake time.
- WebSocket, text frames with JSON payloads.
- First frame **must** be a `connect` request.
- Pre-connect frames are capped at 64 KiB. After a successful handshake, clients
should follow the `hello-ok.policy.maxPayload` and
`hello-ok.policy.maxBufferedBytes` limits. With diagnostics enabled,
oversized inbound frames and slow outbound buffers emit `payload.large` events
before the gateway closes or drops the affected frame. These events keep
sizes, limits, surfaces, and safe reason codes. They do not keep the message
body, attachment contents, raw frame body, tokens, cookies, or secret values.
## Handshake (connect)
@@ -265,6 +272,12 @@ implemented in `src/gateway/server-methods/*.ts`.
### System and identity
- `health` returns the cached or freshly probed gateway health snapshot.
- `diagnostics.stability` returns the recent bounded diagnostic stability
recorder. It keeps operational metadata such as event names, counts, byte
sizes, memory readings, queue/session state, channel/plugin names, and session
ids. It does not keep chat text, webhook bodies, tool outputs, raw request or
response bodies, tokens, cookies, or secret values. Operator read scope is
required.
- `status` returns the `/status`-style gateway summary; sensitive fields are
included only for admin-scoped operator clients.
- `gateway.identity.get` returns the gateway device identity used by relay and

View File

@@ -247,7 +247,7 @@ High-signal `checkId` values you will most likely see in real deployments (not e
| `fs.state_dir.perms_readable` | warn | State dir is readable by others | filesystem perms on `~/.openclaw` | yes |
| `fs.state_dir.symlink` | warn | State dir target becomes another trust boundary | state dir filesystem layout | no |
| `fs.config.perms_writable` | critical | Others can change auth/tool policy/config | filesystem perms on `~/.openclaw/openclaw.json` | yes |
| `fs.config.symlink` | warn | Config target becomes another trust boundary | config file filesystem layout | no |
| `fs.config.symlink` | warn | Symlinked config files are unsupported for writes and add another trust boundary | replace with a regular config file or point `OPENCLAW_CONFIG_PATH` at the real file | no |
| `fs.config.perms_group_readable` | warn | Group users can read config tokens/settings | filesystem perms on config file | yes |
| `fs.config.perms_world_readable` | critical | Config can expose tokens/settings | filesystem perms on config file | yes |
| `fs.config_include.perms_writable` | critical | Config include file can be modified by others | include-file perms referenced from `openclaw.json` | yes |

View File

@@ -303,6 +303,7 @@ Common signatures:
- `.clobbered.*` exists → an external direct edit or startup read was restored.
- `.rejected.*` exists → an OpenClaw-owned config write failed schema or clobber checks before commit.
- `Config write rejected:` → the write tried to drop required shape, shrink the file sharply, or persist invalid config.
- `missing-meta-vs-last-good`, `gateway-mode-missing-vs-last-good`, or `size-drop-vs-last-good:*` → startup treated the current file as clobbered because it lost fields or size compared with the last-known-good backup.
- `Config last-known-good promotion skipped` → the candidate contained redacted secret placeholders such as `***`.
Fix options:
@@ -475,7 +476,7 @@ Common signatures:
- `No Chrome tabs found for profile="user"` → the Chrome MCP attach profile has no open local Chrome tabs.
- `Remote CDP for profile "<name>" is not reachable` → the configured remote CDP endpoint is not reachable from the gateway host.
- `Browser attachOnly is enabled ... not reachable` or `Browser attachOnly is enabled and CDP websocket ... is not reachable` → attach-only profile has no reachable target, or the HTTP endpoint answered but the CDP WebSocket still could not be opened.
- `Playwright is not available in this gateway build; '<feature>' is unsupported.` → the current gateway install lacks the full Playwright package; ARIA snapshots and basic page screenshots can still work, but navigation, AI snapshots, CSS-selector element screenshots, and PDF export stay unavailable.
- `Playwright is not available in this gateway build; '<feature>' is unsupported.` → the current gateway install lacks the bundled browser plugin's `playwright-core` runtime dependency; run `openclaw doctor --fix`, then restart the gateway. ARIA snapshots and basic page screenshots can still work, but navigation, AI snapshots, CSS-selector element screenshots, and PDF export stay unavailable.
- `fullPage is not supported for element screenshots` → screenshot request mixed `--full-page` with `--ref` or `--element`.
- `element screenshots are not supported for existing-session profiles; use ref from snapshot.` → Chrome MCP / `existing-session` screenshot calls must use page capture or a snapshot `--ref`, not CSS `--element`.
- `existing-session file uploads do not support element selectors; use ref/inputRef.` → Chrome MCP upload hooks need snapshot refs, not CSS selectors.

View File

@@ -82,6 +82,10 @@ These commands sit beside the main test suites when you need QA-lab realism:
- Verifies the first Gateway restart installs each bundled channel plugin's
runtime dependencies on demand, and a second restart does not reinstall
dependencies that were already activated.
- Also installs a known older npm baseline, enables Telegram before running
`openclaw update --tag <candidate>`, and verifies the candidate's
post-update doctor repairs bundled channel runtime dependencies without a
harness-side postinstall repair.
- `pnpm openclaw qa aimock`
- Starts only the local AIMock provider server for direct protocol smoke
testing.
@@ -325,6 +329,20 @@ Think of the suites as “increasing realism” (and increasing flakiness/cost):
- `pnpm test:perf:profile:main` writes a main-thread CPU profile for Vitest/Vite startup and transform overhead.
- `pnpm test:perf:profile:runner` writes runner CPU+heap profiles for the unit suite with file parallelism disabled.
### Stability (gateway)
- Command: `pnpm test:stability:gateway`
- Config: `vitest.gateway.config.ts`, forced to one worker
- Scope:
- Starts a real loopback Gateway with diagnostics enabled by default
- Drives synthetic gateway message, memory, and large-payload churn through the diagnostic event path
- Queries `diagnostics.stability` over the Gateway WS RPC
- Covers diagnostic stability bundle persistence helpers
- Asserts the recorder remains bounded, synthetic RSS samples stay under the pressure budget, and per-session queue depths drain back to zero
- Expectations:
- CI-safe and keyless
- Narrow lane for stability-regression follow-up, not a substitute for the full Gateway suite
### E2E (gateway smoke)
- Command: `pnpm test:e2e`
@@ -604,11 +622,15 @@ Docker notes:
thread can resume
- run `/codex status` and `/codex models` through the same gateway command
path
- optionally run two Guardian-reviewed escalated shell probes: one benign
command that should be approved and one fake-secret upload that should be
denied so the agent asks back
- Test: `src/gateway/gateway-codex-harness.live.test.ts`
- Enable: `OPENCLAW_LIVE_CODEX_HARNESS=1`
- Default model: `codex/gpt-5.4`
- Optional image probe: `OPENCLAW_LIVE_CODEX_HARNESS_IMAGE_PROBE=1`
- Optional MCP/tool probe: `OPENCLAW_LIVE_CODEX_HARNESS_MCP_PROBE=1`
- Optional Guardian probe: `OPENCLAW_LIVE_CODEX_HARNESS_GUARDIAN_PROBE=1`
- The smoke sets `OPENCLAW_AGENT_HARNESS_FALLBACK=none` so a broken Codex
harness cannot pass by silently falling back to PI.
- Auth: `OPENAI_API_KEY` from the shell/profile, plus optional copied
@@ -621,6 +643,7 @@ source ~/.profile
OPENCLAW_LIVE_CODEX_HARNESS=1 \
OPENCLAW_LIVE_CODEX_HARNESS_IMAGE_PROBE=1 \
OPENCLAW_LIVE_CODEX_HARNESS_MCP_PROBE=1 \
OPENCLAW_LIVE_CODEX_HARNESS_GUARDIAN_PROBE=1 \
OPENCLAW_LIVE_CODEX_HARNESS_MODEL=codex/gpt-5.4 \
pnpm test:live -- src/gateway/gateway-codex-harness.live.test.ts
```
@@ -638,9 +661,11 @@ Docker notes:
- It sources the mounted `~/.profile`, passes `OPENAI_API_KEY`, copies Codex CLI
auth files when present, installs `@openai/codex` into a writable mounted npm
prefix, stages the source tree, then runs only the Codex-harness live test.
- Docker enables the image and MCP/tool probes by default. Set
- Docker enables the image, MCP/tool, and Guardian probes by default. Set
`OPENCLAW_LIVE_CODEX_HARNESS_IMAGE_PROBE=0` or
`OPENCLAW_LIVE_CODEX_HARNESS_MCP_PROBE=0` when you need a narrower debug run.
`OPENCLAW_LIVE_CODEX_HARNESS_MCP_PROBE=0` or
`OPENCLAW_LIVE_CODEX_HARNESS_GUARDIAN_PROBE=0` when you need a narrower debug
run.
- Docker also exports `OPENCLAW_AGENT_HARNESS_FALLBACK=none`, matching the live
test config so `openai-codex/*` or PI fallback cannot hide a Codex harness
regression.
@@ -777,10 +802,11 @@ If you want to rely on env keys (e.g. exported in your `~/.profile`), run local
- Current bundled providers covered:
- `openai`
- `google`
- `xai`
- Optional narrowing:
- `OPENCLAW_LIVE_IMAGE_GENERATION_PROVIDERS="openai,google"`
- `OPENCLAW_LIVE_IMAGE_GENERATION_MODELS="openai/gpt-image-2,google/gemini-3.1-flash-image-preview"`
- `OPENCLAW_LIVE_IMAGE_GENERATION_CASES="google:flash-generate,google:pro-edit"`
- `OPENCLAW_LIVE_IMAGE_GENERATION_PROVIDERS="openai,google,xai"`
- `OPENCLAW_LIVE_IMAGE_GENERATION_MODELS="openai/gpt-image-2,google/gemini-3.1-flash-image-preview,xai/grok-imagine-image"`
- `OPENCLAW_LIVE_IMAGE_GENERATION_CASES="google:flash-generate,google:pro-edit,xai:default-generate,xai:default-edit"`
- Optional auth behavior:
- `OPENCLAW_LIVE_REQUIRE_PROFILE_KEYS=1` to force profile-store auth and ignore env-only overrides
@@ -870,7 +896,7 @@ These Docker runners split into two buckets:
`OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=90000`. Override those env vars when you
explicitly want the larger exhaustive scan.
- `test:docker:all` builds the live Docker image once via `test:docker:live-build`, then reuses it for the two live Docker lanes.
- Container smoke runners: `test:docker:openwebui`, `test:docker:onboard`, `test:docker:gateway-network`, `test:docker:mcp-channels`, and `test:docker:plugins` boot one or more real containers and verify higher-level integration paths.
- Container smoke runners: `test:docker:openwebui`, `test:docker:onboard`, `test:docker:gateway-network`, `test:docker:mcp-channels`, `test:docker:pi-bundle-mcp-tools`, and `test:docker:plugins` boot one or more real containers and verify higher-level integration paths.
The live-model Docker runners also bind-mount only the needed CLI auth homes (or all supported ones when the run is not narrowed), then copy them into the container home before the run so external-CLI OAuth can refresh tokens without mutating the host auth store:
@@ -883,7 +909,12 @@ The live-model Docker runners also bind-mount only the needed CLI auth homes (or
- Onboarding wizard (TTY, full scaffolding): `pnpm test:docker:onboard` (script: `scripts/e2e/onboard-docker.sh`)
- Gateway networking (two containers, WS auth + health): `pnpm test:docker:gateway-network` (script: `scripts/e2e/gateway-network-docker.sh`)
- MCP channel bridge (seeded Gateway + stdio bridge + raw Claude notification-frame smoke): `pnpm test:docker:mcp-channels` (script: `scripts/e2e/mcp-channels-docker.sh`)
- Pi bundle MCP tools (real stdio MCP server + embedded Pi profile allow/deny smoke): `pnpm test:docker:pi-bundle-mcp-tools` (script: `scripts/e2e/pi-bundle-mcp-tools-docker.sh`)
- Cron/subagent MCP cleanup (real Gateway + stdio MCP child teardown after isolated cron and one-shot subagent runs): `pnpm test:docker:cron-mcp-cleanup` (script: `scripts/e2e/cron-mcp-cleanup-docker.sh`)
- Plugins (install smoke + `/plugin` alias + Claude-bundle restart semantics): `pnpm test:docker:plugins` (script: `scripts/e2e/plugins-docker.sh`)
- Bundled plugin runtime deps: `pnpm test:docker:bundled-channel-deps` builds a small Docker runner image by default, builds and packs OpenClaw once on the host, then mounts that tarball into each Linux install scenario. Reuse the image with `OPENCLAW_SKIP_DOCKER_BUILD=1`, skip the host rebuild after a fresh local build with `OPENCLAW_BUNDLED_CHANNEL_HOST_BUILD=0`, or point at an existing tarball with `OPENCLAW_BUNDLED_CHANNEL_PACKAGE_TGZ=/path/to/openclaw-*.tgz`.
- Narrow bundled plugin runtime deps while iterating by disabling unrelated scenarios, for example:
`OPENCLAW_BUNDLED_CHANNEL_SCENARIOS=0 OPENCLAW_BUNDLED_CHANNEL_UPDATE_SCENARIO=0 OPENCLAW_BUNDLED_CHANNEL_ROOT_OWNED_SCENARIO=0 OPENCLAW_BUNDLED_CHANNEL_SETUP_ENTRY_SCENARIO=0 pnpm test:docker:bundled-channel-deps`.
The live-model Docker runners also bind-mount the current checkout read-only and
stage it into a temporary workdir inside the container. This keeps the runtime
@@ -916,6 +947,15 @@ live event queue behavior, outbound send routing, and Claude-style channel +
permission notifications over the real stdio MCP bridge. The notification check
inspects the raw stdio MCP frames directly so the smoke validates what the
bridge actually emits, not just what a specific client SDK happens to surface.
`test:docker:pi-bundle-mcp-tools` is deterministic and does not need a live
model key. It builds the repo Docker image, starts a real stdio MCP probe server
inside the container, materializes that server through the embedded Pi bundle
MCP runtime, executes the tool, then verifies `coding` and `messaging` keep
`bundle-mcp` tools while `minimal` and `tools.deny: ["bundle-mcp"]` filter them.
`test:docker:cron-mcp-cleanup` is deterministic and does not need a live model
key. It starts a seeded Gateway with a real stdio MCP probe server, runs an
isolated cron turn and a `/subagents spawn` one-shot child turn, then verifies
the MCP child process exits after each run.
Manual ACP plain-language thread smoke (not CI):

View File

@@ -54,6 +54,25 @@ pnpm add -g openclaw@latest
bun add -g openclaw@latest
```
### Root-owned global npm installs
Some Linux npm setups install global packages under root-owned directories such as
`/usr/lib/node_modules/openclaw`. OpenClaw supports that layout: the installed
package is treated as read-only at runtime, and bundled plugin runtime
dependencies are staged into a writable runtime directory instead of mutating the
package tree.
For hardened systemd units, set a writable stage directory that is included in
`ReadWritePaths`:
```ini
Environment=OPENCLAW_PLUGIN_STAGE_DIR=/var/lib/openclaw/plugin-runtime-deps
ReadWritePaths=/var/lib/openclaw /home/openclaw/.openclaw /tmp
```
If `OPENCLAW_PLUGIN_STAGE_DIR` is not set, OpenClaw uses `$STATE_DIRECTORY` when
systemd provides it, then falls back to `~/.openclaw/plugin-runtime-deps`.
## Auto-updater
The auto-updater is off by default. Enable it in `~/.openclaw/openclaw.json`:

View File

@@ -164,7 +164,7 @@ working option**:
example through `agents.defaults.imageModel` or
`openclaw infer image describe --model ollama/<vision-model>`.
- Bundled fallback order:
- Audio: OpenAI → Groq → Deepgram → Google → Mistral
- Audio: OpenAI → Groq → xAI → Deepgram → Google → Mistral
- Image: OpenAI → Anthropic → Google → MiniMax → MiniMax Portal → Z.AI
- Video: Google → Qwen → Moonshot
@@ -212,6 +212,7 @@ lists, OpenClaw can infer defaults:
- `mistral`: **audio**
- `zai`: **image**
- `groq`: **audio**
- `xai`: **audio**
- `deepgram`: **audio**
- Any `models.providers.<id>.models[]` catalog with an image-capable model:
**image**

View File

@@ -25,10 +25,10 @@ OpenClaw uses the pi SDK to embed an AI coding agent into its messaging gateway
```json
{
"@mariozechner/pi-agent-core": "0.64.0",
"@mariozechner/pi-ai": "0.64.0",
"@mariozechner/pi-coding-agent": "0.64.0",
"@mariozechner/pi-tui": "0.64.0"
"@mariozechner/pi-agent-core": "0.68.1",
"@mariozechner/pi-ai": "0.68.1",
"@mariozechner/pi-coding-agent": "0.68.1",
"@mariozechner/pi-tui": "0.68.1"
}
```

View File

@@ -162,6 +162,7 @@ A single plugin can register any number of capabilities via the `api` object:
| 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) |
| Embedded Pi extension | `api.registerEmbeddedExtensionFactory(...)` | [SDK Overview](/plugins/sdk-overview#registration-api) |
| Agent tools | `api.registerTool(...)` | Below |
| Custom commands | `api.registerCommand(...)` | [Entry Points](/plugins/sdk-entrypoints) |
| Event hooks | `api.registerHook(...)` | [Entry Points](/plugins/sdk-entrypoints) |
@@ -170,6 +171,11 @@ A single plugin can register any number of capabilities via the `api` object:
For the full registration API, see [SDK Overview](/plugins/sdk-overview#registration-api).
Use `api.registerEmbeddedExtensionFactory(...)` when a plugin needs Pi-native
embedded-runner hooks such as async `tool_result` rewriting before the final
tool result message is emitted. Prefer regular OpenClaw plugin hooks when the
work does not need Pi extension timing.
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
@@ -184,6 +190,8 @@ Hook guard semantics to keep in mind:
- `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.

View File

@@ -104,6 +104,8 @@ loader. Cursor command markdown works through the same path.
`mcpServers`
- OpenClaw exposes supported bundle MCP tools during embedded Pi agent turns by
launching stdio servers or connecting to HTTP servers
- the `coding` and `messaging` tool profiles include bundle MCP tools by
default; use `tools.deny: ["bundle-mcp"]` to opt out for an agent or gateway
- project-local Pi settings still apply after bundle defaults, so workspace
settings can override bundle MCP entries when needed
- bundle MCP tool catalogs are sorted deterministically before registration, so
@@ -170,6 +172,9 @@ OpenClaw registers bundle MCP tools with provider-safe names in the form
- colliding sanitized names are disambiguated with numeric suffixes
- final exposed tool order is deterministic by safe name to keep repeated Pi
turns cache-stable
- profile filtering treats all tools from one bundle MCP server as plugin-owned
by `bundle-mcp`, so profile allowlists and deny lists can include either
individual exposed tool names or the `bundle-mcp` plugin key
#### Embedded Pi settings

View File

@@ -17,6 +17,14 @@ discovery, native thread resume, native compaction, and app-server execution.
OpenClaw still owns chat channels, session files, model selection, tools,
approvals, media delivery, and the visible transcript mirror.
Native Codex turns also respect the shared `before_prompt_build`,
`before_compaction`, and `after_compaction` plugin hooks, so prompt shims and
compaction-aware automation can stay aligned with the PI harness.
Native Codex turns also respect the shared `before_prompt_build`,
`before_compaction`, `after_compaction`, `llm_input`, `llm_output`, and
`agent_end` plugin hooks, so prompt shims, compaction-aware automation, and
lifecycle observers can stay aligned with the PI harness.
The harness is off by default. It is selected only when the `codex` plugin is
enabled and the resolved model is a `codex/*` model, or when you explicitly
force `embeddedHarness.runtime: "codex"` or `OPENCLAW_AGENT_RUNTIME=codex`.
@@ -263,9 +271,14 @@ By default, the plugin starts Codex locally with:
codex app-server --listen stdio://
```
By default, OpenClaw asks Codex to request native approvals. You can tune that
policy further, for example by tightening it and routing reviews through the
guardian:
By default, OpenClaw starts local Codex harness sessions in YOLO mode:
`approvalPolicy: "never"`, `approvalsReviewer: "user"`, and
`sandbox: "danger-full-access"`. This is the trusted local operator posture used
for autonomous heartbeats: Codex can use shell and network tools without
stopping on native approval prompts that nobody is around to answer.
To opt in to Codex guardian-reviewed approvals, set `appServer.mode:
"guardian"`:
```json5
{
@@ -275,10 +288,8 @@ guardian:
enabled: true,
config: {
appServer: {
approvalPolicy: "untrusted",
approvalsReviewer: "guardian_subagent",
sandbox: "workspace-write",
serviceTier: "priority",
mode: "guardian",
serviceTier: "fast",
},
},
},
@@ -287,6 +298,45 @@ guardian:
}
```
Guardian mode expands to:
```json5
{
plugins: {
entries: {
codex: {
enabled: true,
config: {
appServer: {
mode: "guardian",
approvalPolicy: "on-request",
approvalsReviewer: "guardian_subagent",
sandbox: "workspace-write",
},
},
},
},
},
}
```
Guardian is a native Codex approval reviewer. When Codex asks to leave the
sandbox, write outside the workspace, or add permissions such as network access,
Codex routes that approval request to a reviewer subagent instead of a human
prompt. The reviewer gathers context and applies Codex's risk framework, then
approves or denies the specific request. Guardian is useful when you want more
guardrails than YOLO mode but still need unattended agents and heartbeats to
make progress.
The Docker live harness includes a Guardian probe when
`OPENCLAW_LIVE_CODEX_HARNESS_GUARDIAN_PROBE=1`. It starts the Codex harness in
Guardian mode, verifies that a benign escalated shell command is approved, and
verifies that a fake-secret upload to an untrusted external destination is
denied so the agent asks back for explicit approval.
The individual policy fields still win over `mode`, so advanced deployments can
mix the preset with explicit choices.
For an already-running app-server, use WebSocket transport:
```json5
@@ -311,30 +361,35 @@ For an already-running app-server, use WebSocket transport:
Supported `appServer` fields:
| Field | Default | Meaning |
| ------------------- | ---------------------------------------- | ------------------------------------------------------------------------ |
| `transport` | `"stdio"` | `"stdio"` spawns Codex; `"websocket"` connects to `url`. |
| `command` | `"codex"` | Executable for stdio transport. |
| `args` | `["app-server", "--listen", "stdio://"]` | Arguments for stdio transport. |
| `url` | unset | WebSocket app-server URL. |
| `authToken` | unset | Bearer token for WebSocket transport. |
| `headers` | `{}` | Extra WebSocket headers. |
| `requestTimeoutMs` | `60000` | Timeout for app-server control-plane calls. |
| `approvalPolicy` | `"on-request"` | Native Codex approval policy sent to thread start/resume/turn. |
| `sandbox` | `"workspace-write"` | Native Codex sandbox mode sent to thread start/resume. |
| `approvalsReviewer` | `"user"` | Use `"guardian_subagent"` to let Codex guardian review native approvals. |
| `serviceTier` | unset | Optional Codex service tier, for example `"priority"`. |
| Field | Default | Meaning |
| ------------------- | ---------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| `transport` | `"stdio"` | `"stdio"` spawns Codex; `"websocket"` connects to `url`. |
| `command` | `"codex"` | Executable for stdio transport. |
| `args` | `["app-server", "--listen", "stdio://"]` | Arguments for stdio transport. |
| `url` | unset | WebSocket app-server URL. |
| `authToken` | unset | Bearer token for WebSocket transport. |
| `headers` | `{}` | Extra WebSocket headers. |
| `requestTimeoutMs` | `60000` | Timeout for app-server control-plane calls. |
| `mode` | `"yolo"` | Preset for YOLO or guardian-reviewed execution. |
| `approvalPolicy` | `"never"` | Native Codex approval policy sent to thread start/resume/turn. |
| `sandbox` | `"danger-full-access"` | Native Codex sandbox mode sent to thread start/resume. |
| `approvalsReviewer` | `"user"` | Use `"guardian_subagent"` to let Codex Guardian review prompts. |
| `serviceTier` | unset | Optional Codex app-server service tier: `"fast"`, `"flex"`, or `null`. Invalid legacy values are ignored. |
The older environment variables still work as fallbacks for local testing when
the matching config field is unset:
- `OPENCLAW_CODEX_APP_SERVER_BIN`
- `OPENCLAW_CODEX_APP_SERVER_ARGS`
- `OPENCLAW_CODEX_APP_SERVER_MODE=yolo|guardian`
- `OPENCLAW_CODEX_APP_SERVER_APPROVAL_POLICY`
- `OPENCLAW_CODEX_APP_SERVER_SANDBOX`
- `OPENCLAW_CODEX_APP_SERVER_GUARDIAN=1`
Config is preferred for repeatable deployments.
`OPENCLAW_CODEX_APP_SERVER_GUARDIAN=1` was removed. Use
`plugins.entries.codex.config.appServer.mode: "guardian"` instead, or
`OPENCLAW_CODEX_APP_SERVER_MODE=guardian` for one-off local testing. Config is
preferred for repeatable deployments because it keeps the plugin behavior in the
same reviewed file as the rest of the Codex harness setup.
## Common recipes
@@ -379,6 +434,7 @@ Guardian-reviewed Codex approvals:
enabled: true,
config: {
appServer: {
mode: "guardian",
approvalPolicy: "on-request",
approvalsReviewer: "guardian_subagent",
sandbox: "workspace-write",
@@ -457,7 +513,10 @@ When the selected model uses the Codex harness, native thread compaction is
delegated to Codex app-server. OpenClaw keeps a transcript mirror for channel
history, search, `/new`, `/reset`, and future model or harness switching. The
mirror includes the user prompt, final assistant text, and lightweight Codex
reasoning or plan records when the app-server emits them.
reasoning or plan records when the app-server emits them. Today, OpenClaw only
records native compaction start and completion signals. It does not yet expose a
human-readable compaction summary or an auditable list of which entries Codex
kept after compaction.
Media generation does not require PI. Image, video, music, PDF, TTS, and media
understanding continue to use the matching provider/model settings such as
@@ -469,8 +528,12 @@ understanding continue to use the matching provider/model settings such as
**Codex does not appear in `/model`:** enable `plugins.entries.codex.enabled`,
set a `codex/*` model ref, or check whether `plugins.allow` excludes `codex`.
**OpenClaw falls back to PI:** set `embeddedHarness.fallback: "none"` or
`OPENCLAW_AGENT_HARNESS_FALLBACK=none` while testing.
**OpenClaw uses PI instead of Codex:** if no Codex harness claims the run,
OpenClaw may use PI as the compatibility backend. Set
`embeddedHarness.runtime: "codex"` to force Codex selection while testing, or
`embeddedHarness.fallback: "none"` to fail when no plugin harness matches. Once
Codex app-server is selected, its failures surface directly without extra
fallback config.
**The app-server is rejected:** upgrade Codex so the app-server handshake
reports version `0.118.0` or newer.

View File

@@ -148,36 +148,37 @@ Those belong in your plugin code and `package.json`.
## Top-level field reference
| Field | Required | Type | What it means |
| ----------------------------------- | -------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `id` | Yes | `string` | Canonical plugin id. This is the id used in `plugins.entries.<id>`. |
| `configSchema` | Yes | `object` | Inline JSON Schema for this plugin's config. |
| `enabledByDefault` | No | `true` | Marks a bundled plugin as enabled by default. Omit it, or set any non-`true` value, to leave the plugin disabled by default. |
| `legacyPluginIds` | No | `string[]` | Legacy ids that normalize to this canonical plugin id. |
| `autoEnableWhenConfiguredProviders` | No | `string[]` | Provider ids that should auto-enable this plugin when auth, config, or model refs mention them. |
| `kind` | No | `"memory"` \| `"context-engine"` | Declares an exclusive plugin kind used by `plugins.slots.*`. |
| `channels` | No | `string[]` | Channel ids owned by this plugin. Used for discovery and config validation. |
| `providers` | No | `string[]` | Provider ids owned by this plugin. |
| `modelSupport` | No | `object` | Manifest-owned shorthand model-family metadata used to auto-load the plugin before runtime. |
| `providerEndpoints` | No | `object[]` | Manifest-owned endpoint host/baseUrl metadata for provider routes that core must classify before provider runtime loads. |
| `cliBackends` | No | `string[]` | CLI inference backend ids owned by this plugin. Used for startup auto-activation from explicit config refs. |
| `syntheticAuthRefs` | No | `string[]` | Provider or CLI backend refs whose plugin-owned synthetic auth hook should be probed during cold model discovery before runtime loads. |
| `nonSecretAuthMarkers` | No | `string[]` | Bundled-plugin-owned placeholder API key values that represent non-secret local, OAuth, or ambient credential state. |
| `commandAliases` | No | `object[]` | Command names owned by this plugin that should produce plugin-aware config and CLI diagnostics before runtime loads. |
| `providerAuthEnvVars` | No | `Record<string, string[]>` | Cheap provider-auth env metadata that OpenClaw can inspect without loading plugin code. |
| `providerAuthAliases` | No | `Record<string, string>` | Provider ids that should reuse another provider id for auth lookup, for example a coding provider that shares the base provider API key and auth profiles. |
| `channelEnvVars` | No | `Record<string, string[]>` | Cheap channel env metadata that OpenClaw can inspect without loading plugin code. Use this for env-driven channel setup or auth surfaces that generic startup/config helpers should see. |
| `providerAuthChoices` | No | `object[]` | Cheap auth-choice metadata for onboarding pickers, preferred-provider resolution, and simple CLI flag wiring. |
| `activation` | No | `object` | Cheap activation hints for provider, command, channel, route, and capability-triggered loading. Metadata only; plugin runtime still owns actual behavior. |
| `setup` | No | `object` | Cheap setup/onboarding descriptors that discovery and setup surfaces can inspect without loading plugin runtime. |
| `qaRunners` | No | `object[]` | Cheap QA runner descriptors used by the shared `openclaw qa` host before plugin runtime loads. |
| `contracts` | No | `object` | Static bundled capability snapshot for speech, realtime transcription, realtime voice, media-understanding, image-generation, music-generation, video-generation, web-fetch, web search, and tool ownership. |
| `channelConfigs` | No | `Record<string, object>` | Manifest-owned channel config metadata merged into discovery and validation surfaces before runtime loads. |
| `skills` | No | `string[]` | Skill directories to load, relative to the plugin root. |
| `name` | No | `string` | Human-readable plugin name. |
| `description` | No | `string` | Short summary shown in plugin surfaces. |
| `version` | No | `string` | Informational plugin version. |
| `uiHints` | No | `Record<string, object>` | UI labels, placeholders, and sensitivity hints for config fields. |
| Field | Required | Type | What it means |
| ------------------------------------ | -------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `id` | Yes | `string` | Canonical plugin id. This is the id used in `plugins.entries.<id>`. |
| `configSchema` | Yes | `object` | Inline JSON Schema for this plugin's config. |
| `enabledByDefault` | No | `true` | Marks a bundled plugin as enabled by default. Omit it, or set any non-`true` value, to leave the plugin disabled by default. |
| `legacyPluginIds` | No | `string[]` | Legacy ids that normalize to this canonical plugin id. |
| `autoEnableWhenConfiguredProviders` | No | `string[]` | Provider ids that should auto-enable this plugin when auth, config, or model refs mention them. |
| `kind` | No | `"memory"` \| `"context-engine"` | Declares an exclusive plugin kind used by `plugins.slots.*`. |
| `channels` | No | `string[]` | Channel ids owned by this plugin. Used for discovery and config validation. |
| `providers` | No | `string[]` | Provider ids owned by this plugin. |
| `modelSupport` | No | `object` | Manifest-owned shorthand model-family metadata used to auto-load the plugin before runtime. |
| `providerEndpoints` | No | `object[]` | Manifest-owned endpoint host/baseUrl metadata for provider routes that core must classify before provider runtime loads. |
| `cliBackends` | No | `string[]` | CLI inference backend ids owned by this plugin. Used for startup auto-activation from explicit config refs. |
| `syntheticAuthRefs` | No | `string[]` | Provider or CLI backend refs whose plugin-owned synthetic auth hook should be probed during cold model discovery before runtime loads. |
| `nonSecretAuthMarkers` | No | `string[]` | Bundled-plugin-owned placeholder API key values that represent non-secret local, OAuth, or ambient credential state. |
| `commandAliases` | No | `object[]` | Command names owned by this plugin that should produce plugin-aware config and CLI diagnostics before runtime loads. |
| `providerAuthEnvVars` | No | `Record<string, string[]>` | Cheap provider-auth env metadata that OpenClaw can inspect without loading plugin code. |
| `providerAuthAliases` | No | `Record<string, string>` | Provider ids that should reuse another provider id for auth lookup, for example a coding provider that shares the base provider API key and auth profiles. |
| `channelEnvVars` | No | `Record<string, string[]>` | Cheap channel env metadata that OpenClaw can inspect without loading plugin code. Use this for env-driven channel setup or auth surfaces that generic startup/config helpers should see. |
| `providerAuthChoices` | No | `object[]` | Cheap auth-choice metadata for onboarding pickers, preferred-provider resolution, and simple CLI flag wiring. |
| `activation` | No | `object` | Cheap activation hints for provider, command, channel, route, and capability-triggered loading. Metadata only; plugin runtime still owns actual behavior. |
| `setup` | No | `object` | Cheap setup/onboarding descriptors that discovery and setup surfaces can inspect without loading plugin runtime. |
| `qaRunners` | No | `object[]` | Cheap QA runner descriptors used by the shared `openclaw qa` host before plugin runtime loads. |
| `contracts` | No | `object` | Static bundled capability snapshot for speech, realtime transcription, realtime voice, media-understanding, image-generation, music-generation, video-generation, web-fetch, web search, and tool ownership. |
| `mediaUnderstandingProviderMetadata` | No | `Record<string, object>` | Cheap media-understanding defaults for provider ids declared in `contracts.mediaUnderstandingProviders`. |
| `channelConfigs` | No | `Record<string, object>` | Manifest-owned channel config metadata merged into discovery and validation surfaces before runtime loads. |
| `skills` | No | `string[]` | Skill directories to load, relative to the plugin root. |
| `name` | No | `string` | Human-readable plugin name. |
| `description` | No | `string` | Short summary shown in plugin surfaces. |
| `version` | No | `string` | Informational plugin version. |
| `uiHints` | No | `Record<string, object>` | UI labels, placeholders, and sensitivity hints for config fields. |
## providerAuthChoices reference
@@ -381,6 +382,7 @@ read without importing the plugin runtime.
```json
{
"contracts": {
"embeddedExtensionFactories": ["pi"],
"speechProviders": ["openai"],
"realtimeTranscriptionProviders": ["openai"],
"realtimeVoiceProviders": ["openai"],
@@ -396,17 +398,55 @@ read without importing the plugin runtime.
Each list is optional:
| Field | Type | What it means |
| -------------------------------- | ---------- | -------------------------------------------------------------- |
| `speechProviders` | `string[]` | Speech provider ids this plugin owns. |
| `realtimeTranscriptionProviders` | `string[]` | Realtime-transcription provider ids this plugin owns. |
| `realtimeVoiceProviders` | `string[]` | Realtime-voice provider ids this plugin owns. |
| `mediaUnderstandingProviders` | `string[]` | Media-understanding provider ids this plugin owns. |
| `imageGenerationProviders` | `string[]` | Image-generation provider ids this plugin owns. |
| `videoGenerationProviders` | `string[]` | Video-generation provider ids this plugin owns. |
| `webFetchProviders` | `string[]` | Web-fetch provider ids this plugin owns. |
| `webSearchProviders` | `string[]` | Web-search provider ids this plugin owns. |
| `tools` | `string[]` | Agent tool names this plugin owns for bundled contract checks. |
| Field | Type | What it means |
| -------------------------------- | ---------- | ----------------------------------------------------------------- |
| `embeddedExtensionFactories` | `string[]` | Embedded runtime ids a bundled plugin may register factories for. |
| `speechProviders` | `string[]` | Speech provider ids this plugin owns. |
| `realtimeTranscriptionProviders` | `string[]` | Realtime-transcription provider ids this plugin owns. |
| `realtimeVoiceProviders` | `string[]` | Realtime-voice provider ids this plugin owns. |
| `mediaUnderstandingProviders` | `string[]` | Media-understanding provider ids this plugin owns. |
| `imageGenerationProviders` | `string[]` | Image-generation provider ids this plugin owns. |
| `videoGenerationProviders` | `string[]` | Video-generation provider ids this plugin owns. |
| `webFetchProviders` | `string[]` | Web-fetch provider ids this plugin owns. |
| `webSearchProviders` | `string[]` | Web-search provider ids this plugin owns. |
| `tools` | `string[]` | Agent tool names this plugin owns for bundled contract checks. |
## mediaUnderstandingProviderMetadata reference
Use `mediaUnderstandingProviderMetadata` when a media-understanding provider has
default models, auto-auth fallback priority, or native document support that
generic core helpers need before runtime loads. Keys must also be declared in
`contracts.mediaUnderstandingProviders`.
```json
{
"contracts": {
"mediaUnderstandingProviders": ["example"]
},
"mediaUnderstandingProviderMetadata": {
"example": {
"capabilities": ["image", "audio"],
"defaultModels": {
"image": "example-vision-latest",
"audio": "example-transcribe-latest"
},
"autoPriority": {
"image": 40
},
"nativeDocumentInputs": ["pdf"]
}
}
}
```
Each provider entry can include:
| Field | Type | What it means |
| ---------------------- | ----------------------------------- | ---------------------------------------------------------------------------- |
| `capabilities` | `("image" \| "audio" \| "video")[]` | Media capabilities exposed by this provider. |
| `defaultModels` | `Record<string, string>` | Capability-to-model defaults used when config does not specify a model. |
| `autoPriority` | `Record<string, number>` | Lower numbers sort earlier for automatic credential-based provider fallback. |
| `nativeDocumentInputs` | `"pdf"[]` | Native document inputs supported by the provider. |
## channelConfigs reference

View File

@@ -94,10 +94,11 @@ OpenClaw chooses a harness after provider/model resolution:
4. If no registered harness matches, OpenClaw uses PI unless PI fallback is
disabled.
Forced plugin harness failures surface as run failures. In `auto` mode,
OpenClaw may fall back to PI when the selected plugin harness fails before a
turn has produced side effects. Set `OPENCLAW_AGENT_HARNESS_FALLBACK=none` or
`embeddedHarness.fallback: "none"` to make that fallback a hard failure instead.
Plugin harness failures surface as run failures. In `auto` mode, PI fallback is
only used when no registered plugin harness supports the resolved
provider/model. Once a plugin harness has claimed a run, OpenClaw does not
replay that same turn through PI because that can change auth/runtime semantics
or duplicate side effects.
The bundled Codex plugin registers `codex` as its harness id. Core treats that
as an ordinary plugin harness id; Codex-specific aliases belong in the plugin
@@ -133,6 +134,15 @@ OpenClaw requires Codex app-server `0.118.0` or newer. The Codex plugin checks
the app-server initialize handshake and blocks older or unversioned servers so
OpenClaw only runs against the protocol surface it has been tested with.
### Codex app-server tool-result middleware
Bundled plugins can also attach Codex app-server-specific `tool_result`
middleware through `api.registerCodexAppServerExtensionFactory(...)` when their
manifest declares `contracts.embeddedExtensionFactories: ["codex-app-server"]`.
This is the trusted-plugin seam for async tool-result transforms that need to
run inside the native Codex harness before the tool output is projected back
into the OpenClaw transcript.
### Native Codex harness mode
The bundled `codex` harness is the native Codex mode for embedded OpenClaw
@@ -149,19 +159,20 @@ When this mode runs, Codex owns the native thread id, resume behavior,
compaction, and app-server execution. OpenClaw still owns the chat channel,
visible transcript mirror, tool policy, approvals, media delivery, and session
selection. Use `embeddedHarness.runtime: "codex"` with
`embeddedHarness.fallback: "none"` when you need to prove that the Codex
app-server path is used and PI fallback is not hiding a broken native harness.
`embeddedHarness.fallback: "none"` when you need to prove that only the Codex
app-server path can claim the run. That config is only a selection guard:
Codex app-server failures already fail directly instead of retrying through PI.
## Disable PI fallback
By default, OpenClaw runs embedded agents with `agents.defaults.embeddedHarness`
set to `{ runtime: "auto", fallback: "pi" }`. In `auto` mode, registered plugin
harnesses can claim a provider/model pair. If none match, or if an auto-selected
plugin harness fails before producing output, OpenClaw falls back to PI.
harnesses can claim a provider/model pair. If none match, OpenClaw falls back
to PI.
Set `fallback: "none"` when you need to prove that a plugin harness is the only
runtime being exercised. This disables automatic PI fallback; it does not block
an explicit `runtime: "pi"` or `OPENCLAW_AGENT_RUNTIME=pi`.
Set `fallback: "none"` when you need missing plugin harness selection to fail
instead of using PI. Selected plugin harness failures already fail hard. This
does not block an explicit `runtime: "pi"` or `OPENCLAW_AGENT_RUNTIME=pi`.
For Codex-only embedded runs:

View File

@@ -31,10 +31,17 @@ shared `message` tool in core. Your plugin owns:
- **Session grammar** — how provider-specific conversation ids map to base chats, thread ids, and parent fallbacks
- **Outbound** — sending text, media, and polls to the platform
- **Threading** — how replies are threaded
- **Heartbeat typing** — optional typing/busy signals for heartbeat delivery targets
Core owns the shared message tool, prompt wiring, the outer session-key shape,
generic `:thread:` bookkeeping, and dispatch.
If your channel supports typing indicators outside inbound replies, expose
`heartbeat.sendTyping(...)` on the channel plugin. Core calls it with the
resolved heartbeat delivery target before the heartbeat model run starts and
uses the shared typing keepalive/cleanup lifecycle. Add `heartbeat.clearTyping(...)`
when the platform needs an explicit stop signal.
If your channel adds message-tool params that carry media sources, expose those
param names through `describeMessageTool(...).mediaSourceParams`. Core uses
that explicit list for sandbox path normalization and outbound media-access

View File

@@ -192,7 +192,7 @@ explicitly promotes one as public.
| `plugin-sdk/process-runtime` | Process exec helpers |
| `plugin-sdk/cli-runtime` | CLI formatting, wait, and version helpers |
| `plugin-sdk/gateway-runtime` | Gateway client and channel-status patch helpers |
| `plugin-sdk/config-runtime` | Config load/write helpers |
| `plugin-sdk/config-runtime` | Config load/write helpers and plugin-config lookup helpers |
| `plugin-sdk/telegram-command-config` | Telegram command-name/description normalization and duplicate/conflict checks, even when the bundled Telegram contract surface is unavailable |
| `plugin-sdk/text-autolink-runtime` | File-reference autolink detection without the broad text-runtime barrel |
| `plugin-sdk/approval-runtime` | Exec/plugin approval helpers, approval-capability builders, auth/profile helpers, native routing/runtime helpers |
@@ -343,22 +343,31 @@ methods:
### Infrastructure
| Method | What it registers |
| ---------------------------------------------- | --------------------------------------- |
| `api.registerHook(events, handler, opts?)` | Event hook |
| `api.registerHttpRoute(params)` | Gateway HTTP endpoint |
| `api.registerGatewayMethod(name, handler)` | Gateway RPC method |
| `api.registerCli(registrar, opts?)` | CLI subcommand |
| `api.registerService(service)` | Background service |
| `api.registerInteractiveHandler(registration)` | Interactive handler |
| `api.registerMemoryPromptSupplement(builder)` | Additive memory-adjacent prompt section |
| `api.registerMemoryCorpusSupplement(adapter)` | Additive memory search/read corpus |
| Method | What it registers |
| ----------------------------------------------- | --------------------------------------- |
| `api.registerHook(events, handler, opts?)` | Event hook |
| `api.registerHttpRoute(params)` | Gateway HTTP endpoint |
| `api.registerGatewayMethod(name, handler)` | Gateway RPC method |
| `api.registerCli(registrar, opts?)` | CLI subcommand |
| `api.registerService(service)` | Background service |
| `api.registerInteractiveHandler(registration)` | Interactive handler |
| `api.registerEmbeddedExtensionFactory(factory)` | Pi embedded-runner extension factory |
| `api.registerMemoryPromptSupplement(builder)` | Additive memory-adjacent prompt section |
| `api.registerMemoryCorpusSupplement(adapter)` | Additive memory search/read corpus |
Reserved core admin namespaces (`config.*`, `exec.approvals.*`, `wizard.*`,
`update.*`) always stay `operator.admin`, even if a plugin tries to assign a
narrower gateway method scope. Prefer plugin-specific prefixes for
plugin-owned methods.
Use `api.registerEmbeddedExtensionFactory(...)` when a plugin needs Pi-native
event timing during OpenClaw embedded runs, for example async `tool_result`
rewrites that must happen before the final tool-result message is emitted.
This is a bundled-plugin seam today: only bundled plugins may register one, and
they must declare `contracts.embeddedExtensionFactories: ["pi"]` in
`openclaw.plugin.json`. Keep normal OpenClaw plugin hooks for everything that
does not require that lower-level seam.
### CLI registration metadata
`api.registerCli(registrar, opts?)` accepts two kinds of top-level metadata:
@@ -451,6 +460,9 @@ AI CLI backend such as `codex-cli`.
- `reply_dispatch`: returning `{ handled: true, ... }` is terminal. Once any handler claims dispatch, lower-priority handlers and the default model dispatch path are skipped.
- `message_sending`: returning `{ cancel: true }` is terminal. Once any handler sets it, lower-priority handlers are skipped.
- `message_sending`: returning `{ cancel: false }` is treated as no decision (same as omitting `cancel`), not as an override.
- `message_received`: use the typed `threadId` field when you need inbound thread/topic routing. Keep `metadata` for channel-specific extras.
- `message_sending`: use typed `replyToId` / `threadId` routing fields before falling back to channel-specific `metadata`.
- `gateway_start`: use `ctx.config`, `ctx.workspaceDir`, and `ctx.getCron?.()` for gateway-owned startup state instead of relying on internal `gateway:startup` hooks.
### API object fields

View File

@@ -229,11 +229,24 @@ API key auth, and dynamic model resolution.
baseUrl: "https://api.acme-ai.com/v1",
models: [{ id: "acme-large", name: "Acme Large" }],
}),
buildStaticProvider: () => ({
api: "openai-completions",
baseUrl: "https://api.acme-ai.com/v1",
models: [{ id: "acme-large", name: "Acme Large" }],
}),
},
},
});
```
`buildProvider` is the live catalog path used when OpenClaw can resolve real
provider auth. It may perform provider-specific discovery. Use
`buildStaticProvider` only for offline rows that are safe to show before auth
is configured; it must not require credentials or make network requests.
OpenClaw's `models list --all` display currently executes static catalogs
only for bundled provider plugins, with an empty config, empty env, and no
agent/workspace paths.
If your auth flow also needs to patch `models.providers.*`, aliases, and
the agent default model during onboarding, use the preset helpers from
`openclaw/plugin-sdk/provider-onboard`. The narrowest helpers are

View File

@@ -155,8 +155,8 @@ Current runtime behavior:
- `streaming.provider` is optional. If unset, Voice Call uses the first
registered realtime transcription provider.
- Today the bundled provider is OpenAI, registered by the bundled `openai`
plugin.
- Bundled realtime transcription providers include OpenAI (`openai`) and xAI
(`xai`), registered by their provider plugins.
- Provider-owned raw config lives under `streaming.providers.<providerId>`.
- If `streaming.provider` points at an unregistered provider, or no realtime
transcription provider is registered at all, Voice Call logs a warning and
@@ -169,6 +169,15 @@ OpenAI streaming transcription defaults:
- `silenceDurationMs`: `800`
- `vadThreshold`: `0.5`
xAI streaming transcription defaults:
- API key: `streaming.providers.xai.apiKey` or `XAI_API_KEY`
- endpoint: `wss://api.x.ai/v1/stt`
- `encoding`: `mulaw`
- `sampleRate`: `8000`
- `endpointingMs`: `800`
- `interimResults`: `true`
Example:
```json5
@@ -197,6 +206,33 @@ Example:
}
```
Use xAI instead:
```json5
{
plugins: {
entries: {
"voice-call": {
config: {
streaming: {
enabled: true,
provider: "xai",
streamPath: "/voice/stream",
providers: {
xai: {
apiKey: "${XAI_API_KEY}", // optional if XAI_API_KEY is set
endpointingMs: 800,
language: "en",
},
},
},
},
},
},
},
}
```
Legacy keys are still auto-migrated by `openclaw doctor --fix`:
- `streaming.sttProvider``streaming.provider`

View File

@@ -2,18 +2,22 @@
summary: "Deepgram transcription for inbound voice notes"
read_when:
- You want Deepgram speech-to-text for audio attachments
- You want Deepgram streaming transcription for Voice Call
- You need a quick Deepgram config example
title: "Deepgram"
---
# Deepgram (Audio Transcription)
Deepgram is a speech-to-text API. In OpenClaw it is used for **inbound audio/voice note
transcription** via `tools.media.audio`.
Deepgram is a speech-to-text API. In OpenClaw it is used for inbound
audio/voice-note transcription through `tools.media.audio` and for Voice Call
streaming STT through `plugins.entries.voice-call.config.streaming`.
When enabled, OpenClaw uploads the audio file to Deepgram and injects the transcript
into the reply pipeline (`{{Transcript}}` + `[Audio]` block). This is **not streaming**;
it uses the pre-recorded transcription endpoint.
For batch transcription, OpenClaw uploads the complete audio file to Deepgram
and injects the transcript into the reply pipeline (`{{Transcript}}` +
`[Audio]` block). For Voice Call streaming, OpenClaw forwards live G.711
u-law frames over Deepgram's WebSocket `listen` endpoint and emits partial or
final transcripts as Deepgram returns them.
| Detail | Value |
| ------------- | ---------------------------------------------------------- |
@@ -101,6 +105,52 @@ it uses the pre-recorded transcription endpoint.
</Tab>
</Tabs>
## Voice Call streaming STT
The bundled `deepgram` plugin also registers a realtime transcription provider
for the Voice Call plugin.
| Setting | Config path | Default |
| --------------- | ----------------------------------------------------------------------- | -------------------------------- |
| API key | `plugins.entries.voice-call.config.streaming.providers.deepgram.apiKey` | Falls back to `DEEPGRAM_API_KEY` |
| Model | `...deepgram.model` | `nova-3` |
| Language | `...deepgram.language` | (unset) |
| Encoding | `...deepgram.encoding` | `mulaw` |
| Sample rate | `...deepgram.sampleRate` | `8000` |
| Endpointing | `...deepgram.endpointingMs` | `800` |
| Interim results | `...deepgram.interimResults` | `true` |
```json5
{
plugins: {
entries: {
"voice-call": {
config: {
streaming: {
enabled: true,
provider: "deepgram",
providers: {
deepgram: {
apiKey: "${DEEPGRAM_API_KEY}",
model: "nova-3",
endpointingMs: 800,
language: "en-US",
},
},
},
},
},
},
},
}
```
<Note>
Voice Call receives telephony audio as 8 kHz G.711 u-law. The Deepgram
streaming provider defaults to `encoding: "mulaw"` and `sampleRate: 8000`, so
Twilio media frames can be forwarded directly.
</Note>
## Notes
<AccordionGroup>
@@ -118,12 +168,6 @@ it uses the pre-recorded transcription endpoint.
</Accordion>
</AccordionGroup>
<Note>
Deepgram transcription is **pre-recorded only** (not real-time streaming). OpenClaw
uploads the complete audio file and waits for the full transcript before injecting
it into the conversation.
</Note>
## Related
<CardGroup cols={2}>

View File

@@ -0,0 +1,111 @@
---
summary: "Use ElevenLabs speech, Scribe STT, and realtime transcription with OpenClaw"
read_when:
- You want ElevenLabs text-to-speech in OpenClaw
- You want ElevenLabs Scribe speech-to-text for audio attachments
- You want ElevenLabs realtime transcription for Voice Call
title: "ElevenLabs"
---
# ElevenLabs
OpenClaw uses ElevenLabs for text-to-speech, batch speech-to-text with Scribe
v2, and Voice Call streaming STT with Scribe v2 Realtime.
| Capability | OpenClaw surface | Default |
| ------------------------ | --------------------------------------------- | ------------------------ |
| Text-to-speech | `messages.tts` / `talk` | `eleven_multilingual_v2` |
| Batch speech-to-text | `tools.media.audio` | `scribe_v2` |
| Streaming speech-to-text | Voice Call `streaming.provider: "elevenlabs"` | `scribe_v2_realtime` |
## Authentication
Set `ELEVENLABS_API_KEY` in the environment. `XI_API_KEY` is also accepted for
compatibility with existing ElevenLabs tooling.
```bash
export ELEVENLABS_API_KEY="..."
```
## Text-to-speech
```json5
{
messages: {
tts: {
providers: {
elevenlabs: {
apiKey: "${ELEVENLABS_API_KEY}",
voiceId: "pMsXgVXv3BLzUgSXRplE",
modelId: "eleven_multilingual_v2",
},
},
},
},
}
```
## Speech-to-text
Use Scribe v2 for inbound audio attachments and short recorded voice segments:
```json5
{
tools: {
media: {
audio: {
enabled: true,
models: [{ provider: "elevenlabs", model: "scribe_v2" }],
},
},
},
}
```
OpenClaw sends multipart audio to ElevenLabs `/v1/speech-to-text` with
`model_id: "scribe_v2"`. Language hints map to `language_code` when present.
## Voice Call streaming STT
The bundled `elevenlabs` plugin registers Scribe v2 Realtime for Voice Call
streaming transcription.
| Setting | Config path | Default |
| --------------- | ------------------------------------------------------------------------- | ------------------------------------------------- |
| API key | `plugins.entries.voice-call.config.streaming.providers.elevenlabs.apiKey` | Falls back to `ELEVENLABS_API_KEY` / `XI_API_KEY` |
| Model | `...elevenlabs.modelId` | `scribe_v2_realtime` |
| Audio format | `...elevenlabs.audioFormat` | `ulaw_8000` |
| Sample rate | `...elevenlabs.sampleRate` | `8000` |
| Commit strategy | `...elevenlabs.commitStrategy` | `vad` |
| Language | `...elevenlabs.languageCode` | (unset) |
```json5
{
plugins: {
entries: {
"voice-call": {
config: {
streaming: {
enabled: true,
provider: "elevenlabs",
providers: {
elevenlabs: {
apiKey: "${ELEVENLABS_API_KEY}",
audioFormat: "ulaw_8000",
commitStrategy: "vad",
languageCode: "en",
},
},
},
},
},
},
},
}
```
<Note>
Voice Call receives Twilio media as 8 kHz G.711 u-law. The ElevenLabs realtime
provider defaults to `ulaw_8000`, so telephony frames can be forwarded without
transcoding.
</Note>

View File

@@ -82,6 +82,10 @@ Looking for chat channel docs (WhatsApp/Telegram/Discord/Slack/Mattermost (plugi
## Transcription providers
- [Deepgram (audio transcription)](/providers/deepgram)
- [ElevenLabs](/providers/elevenlabs#speech-to-text)
- [Mistral](/providers/mistral#audio-transcription-voxtral)
- [OpenAI](/providers/openai#speech-to-text)
- [xAI](/providers/xai#speech-to-text)
## Community tools

View File

@@ -2,6 +2,7 @@
summary: "Use Mistral models and Voxtral transcription with OpenClaw"
read_when:
- You want to use Mistral models in OpenClaw
- You want Voxtral realtime transcription for Voice Call
- You need Mistral API key onboarding and model refs
title: "Mistral"
---
@@ -65,7 +66,8 @@ OpenClaw currently ships this bundled Mistral catalog:
## Audio transcription (Voxtral)
Use Voxtral for audio transcription through the media understanding pipeline.
Use Voxtral for batch audio transcription through the media understanding
pipeline.
```json5
{
@@ -84,6 +86,48 @@ Use Voxtral for audio transcription through the media understanding pipeline.
The media transcription path uses `/v1/audio/transcriptions`. The default audio model for Mistral is `voxtral-mini-latest`.
</Tip>
## Voice Call streaming STT
The bundled `mistral` plugin registers Voxtral Realtime as a Voice Call
streaming STT provider.
| Setting | Config path | Default |
| ------------ | ---------------------------------------------------------------------- | --------------------------------------- |
| API key | `plugins.entries.voice-call.config.streaming.providers.mistral.apiKey` | Falls back to `MISTRAL_API_KEY` |
| Model | `...mistral.model` | `voxtral-mini-transcribe-realtime-2602` |
| Encoding | `...mistral.encoding` | `pcm_mulaw` |
| Sample rate | `...mistral.sampleRate` | `8000` |
| Target delay | `...mistral.targetStreamingDelayMs` | `800` |
```json5
{
plugins: {
entries: {
"voice-call": {
config: {
streaming: {
enabled: true,
provider: "mistral",
providers: {
mistral: {
apiKey: "${MISTRAL_API_KEY}",
targetStreamingDelayMs: 800,
},
},
},
},
},
},
},
}
```
<Note>
OpenClaw defaults Mistral realtime STT to `pcm_mulaw` at 8 kHz so Voice Call
can forward Twilio media frames directly. Use `encoding: "pcm_s16le"` and a
matching `sampleRate` only if your upstream stream is already raw PCM.
</Note>
## Advanced configuration
<AccordionGroup>

View File

@@ -463,6 +463,8 @@ For the full setup and behavior details, see [Ollama Web Search](/tools/ollama-s
<Accordion title="Streaming configuration">
OpenClaw's Ollama integration uses the **native Ollama API** (`/api/chat`) by default, which fully supports streaming and tool calling simultaneously. No special configuration is needed.
For native `/api/chat` requests, OpenClaw also forwards thinking control directly to Ollama: `/think off` and `openclaw agent --thinking off` send top-level `think: false`, while non-`off` thinking levels send `think: true`.
<Tip>
If you need to use the OpenAI-compatible endpoint, see the "Legacy OpenAI-compatible mode" section above. Streaming and tool calling may not work simultaneously in that mode.
</Tip>

View File

@@ -16,6 +16,21 @@ OpenAI provides developer APIs for GPT models. OpenClaw supports two auth routes
OpenAI explicitly supports subscription OAuth usage in external tools and workflows like OpenClaw.
## OpenClaw feature coverage
| OpenAI capability | OpenClaw surface | Status |
| ------------------------- | ----------------------------------------- | ------------------------------------------------------ |
| Chat / Responses | `openai/<model>` model provider | Yes |
| Codex subscription models | `openai-codex/<model>` model provider | Yes |
| Server-side web search | Native OpenAI Responses tool | Yes, when web search is enabled and no provider pinned |
| Images | `image_generate` | Yes |
| Videos | `video_generate` | Yes |
| Text-to-speech | `messages.tts.provider: "openai"` / `tts` | Yes |
| Batch speech-to-text | `tools.media.audio` / media understanding | Yes |
| Streaming speech-to-text | Voice Call `streaming.provider: "openai"` | Yes |
| Realtime voice | Voice Call `realtime.provider: "openai"` | Yes |
| Embeddings | memory embedding provider | Yes |
## Getting started
Choose your preferred auth method and follow the setup steps.
@@ -222,7 +237,9 @@ See [Video Generation](/tools/video-generation) for shared tool parameters, prov
## GPT-5 prompt contribution
OpenClaw adds an OpenAI-specific GPT-5 prompt contribution for `openai/*` and `openai-codex/*` GPT-5-family runs. It lives in the bundled OpenAI plugin, applies to model ids such as `gpt-5`, `gpt-5.2`, `gpt-5.4`, and `gpt-5.4-mini`, and does not apply to older GPT-4.x models.
OpenClaw adds a shared GPT-5 prompt contribution for GPT-5-family runs across providers. It applies by model id, so `openai/gpt-5.4`, `openai-codex/gpt-5.4`, `openrouter/openai/gpt-5.4`, `opencode/gpt-5.4`, and other compatible GPT-5 refs receive the same overlay. Older GPT-4.x models do not.
The bundled native Codex harness provider (`codex/*`) uses the same GPT-5 behavior and heartbeat overlay through Codex app-server developer instructions, so `codex/gpt-5.x` sessions keep the same follow-through and proactive heartbeat guidance even though Codex owns the rest of the harness prompt.
The GPT-5 contribution adds a tagged behavior contract for persona persistence, execution safety, tool discipline, output shape, completion checks, and verification. Channel-specific reply and silent-message behavior stays in the shared OpenClaw system prompt and outbound delivery policy. The GPT-5 guidance is always enabled for matching models. The friendly interaction-style layer is separate and configurable.
@@ -236,9 +253,11 @@ The GPT-5 contribution adds a tagged behavior contract for persona persistence,
<Tab title="Config">
```json5
{
plugins: {
entries: {
openai: { config: { personality: "friendly" } },
agents: {
defaults: {
promptOverlays: {
gpt5: { personality: "friendly" },
},
},
},
}
@@ -246,7 +265,7 @@ The GPT-5 contribution adds a tagged behavior contract for persona persistence,
</Tab>
<Tab title="CLI">
```bash
openclaw config set plugins.entries.openai.config.personality off
openclaw config set agents.defaults.promptOverlays.gpt5.personality off
```
</Tab>
</Tabs>
@@ -255,6 +274,10 @@ The GPT-5 contribution adds a tagged behavior contract for persona persistence,
Values are case-insensitive at runtime, so `"Off"` and `"off"` both disable the friendly style layer.
</Tip>
<Note>
Legacy `plugins.entries.openai.config.personality` is still read as a compatibility fallback when the shared `agents.defaults.promptOverlays.gpt5.personality` setting is not set.
</Note>
## Voice and speech
<AccordionGroup>
@@ -291,18 +314,56 @@ Values are case-insensitive at runtime, so `"Off"` and `"off"` both disable the
</Accordion>
<Accordion title="Speech-to-text">
The bundled `openai` plugin registers batch speech-to-text through
OpenClaw's media-understanding transcription surface.
- Default model: `gpt-4o-transcribe`
- Endpoint: OpenAI REST `/v1/audio/transcriptions`
- Input path: multipart audio file upload
- Supported by OpenClaw wherever inbound audio transcription uses
`tools.media.audio`, including Discord voice-channel segments and channel
audio attachments
To force OpenAI for inbound audio transcription:
```json5
{
tools: {
media: {
audio: {
models: [
{
type: "provider",
provider: "openai",
model: "gpt-4o-transcribe",
},
],
},
},
},
}
```
Language and prompt hints are forwarded to OpenAI when supplied by the
shared audio media config or per-call transcription request.
</Accordion>
<Accordion title="Realtime transcription">
The bundled `openai` plugin registers realtime transcription for the Voice Call plugin.
| Setting | Config path | Default |
|---------|------------|---------|
| Model | `plugins.entries.voice-call.config.streaming.providers.openai.model` | `gpt-4o-transcribe` |
| Language | `...openai.language` | (unset) |
| Prompt | `...openai.prompt` | (unset) |
| Silence duration | `...openai.silenceDurationMs` | `800` |
| VAD threshold | `...openai.vadThreshold` | `0.5` |
| API key | `...openai.apiKey` | Falls back to `OPENAI_API_KEY` |
<Note>
Uses a WebSocket connection to `wss://api.openai.com/v1/realtime` with G.711 u-law audio.
Uses a WebSocket connection to `wss://api.openai.com/v1/realtime` with G.711 u-law (`g711_ulaw` / `audio/pcmu`) audio. This streaming provider is for Voice Call's realtime transcription path; Discord voice currently records short segments and uses the batch `tools.media.audio` transcription path instead.
</Note>
</Accordion>

View File

@@ -20,11 +20,23 @@ provider id `opencode-go` so upstream per-model routing stays correct.
## Supported models
| Model ref | Name |
| -------------------------- | ------------ |
| `opencode-go/kimi-k2.5` | Kimi K2.5 |
| `opencode-go/glm-5` | GLM 5 |
| `opencode-go/minimax-m2.5` | MiniMax M2.5 |
OpenClaw sources the Go catalog from the bundled pi model registry. Run
`openclaw models list --provider opencode-go` for the current model list.
As of the bundled pi catalog, the provider includes:
| Model ref | Name |
| -------------------------- | --------------------- |
| `opencode-go/glm-5` | GLM-5 |
| `opencode-go/glm-5.1` | GLM-5.1 |
| `opencode-go/kimi-k2.5` | Kimi K2.5 |
| `opencode-go/kimi-k2.6` | Kimi K2.6 (3x limits) |
| `opencode-go/mimo-v2-omni` | MiMo V2 Omni |
| `opencode-go/mimo-v2-pro` | MiMo V2 Pro |
| `opencode-go/minimax-m2.5` | MiniMax M2.5 |
| `opencode-go/minimax-m2.7` | MiniMax M2.7 |
| `opencode-go/qwen3.5-plus` | Qwen3.5 Plus |
| `opencode-go/qwen3.6-plus` | Qwen3.6 Plus |
## Getting started

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