mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-12 09:11:14 +08:00
Compare commits
510 Commits
codex-mode
...
codex/stab
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f6548ffb41 | ||
|
|
2a6fab9d22 | ||
|
|
c7d77f8c7b | ||
|
|
32aa631e19 | ||
|
|
8de02c318b | ||
|
|
e962381dbf | ||
|
|
b02cca4e00 | ||
|
|
06b3e4ef8a | ||
|
|
85148f3b20 | ||
|
|
4b9c85776d | ||
|
|
6bbb1b79e1 | ||
|
|
45bdfb5f72 | ||
|
|
60d4d5e1fa | ||
|
|
8c2f894d3a | ||
|
|
510718bedf | ||
|
|
332cdd7aca | ||
|
|
422fa99197 | ||
|
|
5e9a96fafb | ||
|
|
679e476183 | ||
|
|
3d59e8192b | ||
|
|
02dae3e1d1 | ||
|
|
835c6bc0c1 | ||
|
|
52249927ac | ||
|
|
b94ad7c9d8 | ||
|
|
32b1f0ce74 | ||
|
|
1ea12fe3e2 | ||
|
|
6038725501 | ||
|
|
a108169127 | ||
|
|
5bba899a70 | ||
|
|
9df7fe3986 | ||
|
|
5c3e2a6b44 | ||
|
|
51dbda3f3d | ||
|
|
488a1ee146 | ||
|
|
a167e687ce | ||
|
|
2dcc4605d4 | ||
|
|
05ebfa4146 | ||
|
|
86da88c120 | ||
|
|
9624d81bb3 | ||
|
|
751c7f32a5 | ||
|
|
6c49039a23 | ||
|
|
91e835ebe0 | ||
|
|
5d5c37775e | ||
|
|
377553e41a | ||
|
|
241d0cb88e | ||
|
|
dc8b881c11 | ||
|
|
f4129cdd2b | ||
|
|
6908bd3167 | ||
|
|
7564af24e6 | ||
|
|
748daa4857 | ||
|
|
6987132aed | ||
|
|
382e03a2d8 | ||
|
|
390b965460 | ||
|
|
edbcfe1a1d | ||
|
|
e2ecf292bc | ||
|
|
fd06aeac04 | ||
|
|
f83e424a5d | ||
|
|
0eac6432c3 | ||
|
|
ebbc7dcfeb | ||
|
|
8cd68487d9 | ||
|
|
4519b29419 | ||
|
|
c881d8da48 | ||
|
|
00300b85d0 | ||
|
|
7c0fdae9b9 | ||
|
|
e0956a0853 | ||
|
|
9c07579a95 | ||
|
|
166a6d9088 | ||
|
|
5a88d8502f | ||
|
|
4db066d102 | ||
|
|
3f1ce689a1 | ||
|
|
d4bb4912fc | ||
|
|
02455c0c52 | ||
|
|
d857989111 | ||
|
|
4c3c3abe1a | ||
|
|
716b3faf7e | ||
|
|
3e95927df7 | ||
|
|
cc79f4982c | ||
|
|
09107e0b7f | ||
|
|
720ab99307 | ||
|
|
0ff0c7ce57 | ||
|
|
a33a2c97a3 | ||
|
|
4cc572a813 | ||
|
|
3c8760f16d | ||
|
|
940f67e524 | ||
|
|
ef828d55af | ||
|
|
9626ef274a | ||
|
|
5e8cb77e79 | ||
|
|
461c10bb51 | ||
|
|
18b76e3995 | ||
|
|
6b6f8ab1aa | ||
|
|
36c08e0288 | ||
|
|
6590e0e872 | ||
|
|
4340cb74c2 | ||
|
|
5f9506f7fd | ||
|
|
e1cdaa3c88 | ||
|
|
2b40416314 | ||
|
|
3b74b913e3 | ||
|
|
99159f89da | ||
|
|
02d266c6c4 | ||
|
|
34f81c6a8a | ||
|
|
147f4f50f5 | ||
|
|
6a7980e984 | ||
|
|
831f03b814 | ||
|
|
b0c70786fd | ||
|
|
e6eea6cfe2 | ||
|
|
67650c4c0a | ||
|
|
f60378519c | ||
|
|
4878d3e059 | ||
|
|
6a05b9eec5 | ||
|
|
2c092a0eff | ||
|
|
76de167ca1 | ||
|
|
2a08848dd1 | ||
|
|
d3fd275aa5 | ||
|
|
6c1cffa7f8 | ||
|
|
e0141946b2 | ||
|
|
cbbd860ef9 | ||
|
|
9bd4200f3c | ||
|
|
a72522d05d | ||
|
|
313a19c940 | ||
|
|
29af4add2a | ||
|
|
d5063d5b16 | ||
|
|
6d0e84aadb | ||
|
|
ef31a333f7 | ||
|
|
0b3f13b337 | ||
|
|
9f9bd41f40 | ||
|
|
414fd41a1f | ||
|
|
8b27c489f5 | ||
|
|
f39f4629d9 | ||
|
|
348728c28c | ||
|
|
dc78d58448 | ||
|
|
ae89d44760 | ||
|
|
ead76f61d8 | ||
|
|
a5f6603e61 | ||
|
|
a313c4db92 | ||
|
|
b72c0bdfad | ||
|
|
bd42f35097 | ||
|
|
90ad79cbcd | ||
|
|
0b46227d6c | ||
|
|
1882a8e5ea | ||
|
|
f5f4f514d8 | ||
|
|
0c30d0d0b8 | ||
|
|
de0ece20d1 | ||
|
|
aa071e0b60 | ||
|
|
f4cf7e3b4f | ||
|
|
2dba9e6a76 | ||
|
|
fc3abc139b | ||
|
|
22c9e82e83 | ||
|
|
8c2bc951a9 | ||
|
|
c45a7d7a7a | ||
|
|
b96a75c95b | ||
|
|
20b71e18b2 | ||
|
|
9b79eef750 | ||
|
|
988cb1ebfe | ||
|
|
3e020a1650 | ||
|
|
5176dba8a0 | ||
|
|
d8c1140235 | ||
|
|
69daef8246 | ||
|
|
3f59cd0a09 | ||
|
|
90de4bd855 | ||
|
|
6a5ecb955c | ||
|
|
eed7b13b62 | ||
|
|
efec8a4a84 | ||
|
|
bf08dc2ed6 | ||
|
|
110fa97f2a | ||
|
|
8c18df02f3 | ||
|
|
e28ad0f84f | ||
|
|
c6617c3155 | ||
|
|
1316ca9aa8 | ||
|
|
acfa9877b3 | ||
|
|
6a20c83cf7 | ||
|
|
f0b758fba2 | ||
|
|
b99540964c | ||
|
|
b9c7a4306b | ||
|
|
658240de74 | ||
|
|
67d00826b2 | ||
|
|
3c95327b34 | ||
|
|
0a117b5960 | ||
|
|
ddac6f73e5 | ||
|
|
ffbb4d4ae7 | ||
|
|
3937d16c44 | ||
|
|
b109c1f99c | ||
|
|
92c1924d27 | ||
|
|
acd1bd7d31 | ||
|
|
11e17793e1 | ||
|
|
90b3cdb6a7 | ||
|
|
7ca2f9fed5 | ||
|
|
732a5842ee | ||
|
|
d7c173b694 | ||
|
|
6fed787297 | ||
|
|
7cecbe1002 | ||
|
|
0f672dcc73 | ||
|
|
b825c8d34b | ||
|
|
3b514ad5f3 | ||
|
|
82b928232e | ||
|
|
30d9e70988 | ||
|
|
a3e0674261 | ||
|
|
be56f172ab | ||
|
|
d2786fb969 | ||
|
|
fa0729e145 | ||
|
|
21c51bc140 | ||
|
|
265bc6b6ea | ||
|
|
42db865673 | ||
|
|
5d7c6e6bda | ||
|
|
560ddd2f9b | ||
|
|
998e37fcb3 | ||
|
|
3cc52d9050 | ||
|
|
7902c769da | ||
|
|
9be8d43c31 | ||
|
|
eccb79db99 | ||
|
|
09a635a28b | ||
|
|
5b257cb352 | ||
|
|
efe940e9cb | ||
|
|
8d909ed0da | ||
|
|
1bb46ce68a | ||
|
|
54e77a9ec4 | ||
|
|
43e651db9a | ||
|
|
e7d069edcf | ||
|
|
17094640f8 | ||
|
|
16c6a92c53 | ||
|
|
ef3309a986 | ||
|
|
95ae3c00bd | ||
|
|
97e64196a0 | ||
|
|
41ad03dda4 | ||
|
|
4a578740a2 | ||
|
|
20d6daaeaa | ||
|
|
6018f29dbf | ||
|
|
989cfd1e33 | ||
|
|
89ab39ca64 | ||
|
|
199d5f765f | ||
|
|
2fe11020d2 | ||
|
|
1ddf6b4e39 | ||
|
|
1a02d00eb4 | ||
|
|
cfe58387a7 | ||
|
|
6077941d0b | ||
|
|
b5714b90ed | ||
|
|
7a86448a6e | ||
|
|
6cba12caae | ||
|
|
a08b65a90a | ||
|
|
084dde89fd | ||
|
|
2efc4a8233 | ||
|
|
cd417f3b68 | ||
|
|
a2adb05f74 | ||
|
|
c9c0ab3a44 | ||
|
|
0472b6197a | ||
|
|
8a60e57846 | ||
|
|
c6cf37068c | ||
|
|
ff6044f441 | ||
|
|
5aa3779d8c | ||
|
|
ff9fefb79b | ||
|
|
3746e5b969 | ||
|
|
9f5bc5465c | ||
|
|
d108110a89 | ||
|
|
1b1eea238c | ||
|
|
d9e9e61e77 | ||
|
|
fc0e6e4650 | ||
|
|
e8df081a1f | ||
|
|
5c4c33c7de | ||
|
|
070b55f336 | ||
|
|
364d49889e | ||
|
|
baaad52389 | ||
|
|
3a8961af0f | ||
|
|
ff570f3a61 | ||
|
|
2cd23957c0 | ||
|
|
43a003b8a0 | ||
|
|
fa85e6c26e | ||
|
|
d46de6cff7 | ||
|
|
018f2e78ba | ||
|
|
b61954919c | ||
|
|
5abb717112 | ||
|
|
8226238765 | ||
|
|
b68b4b9151 | ||
|
|
a3c51f91c5 | ||
|
|
2edbdc42ae | ||
|
|
b28de9a7d9 | ||
|
|
824c3e2b71 | ||
|
|
2194a8c64c | ||
|
|
410783c126 | ||
|
|
3ae6f01d61 | ||
|
|
e3cbad4fb6 | ||
|
|
c082cf892a | ||
|
|
b4a9ac3516 | ||
|
|
f0566e410a | ||
|
|
c6e9849351 | ||
|
|
8e1755928c | ||
|
|
9eb071c3f1 | ||
|
|
522eedc754 | ||
|
|
71e361af8a | ||
|
|
487f8c5d3a | ||
|
|
7a4574376a | ||
|
|
8ba82534e6 | ||
|
|
ffa84cdc02 | ||
|
|
67ffa3df8b | ||
|
|
df542f75a9 | ||
|
|
edf40ab6c9 | ||
|
|
406ae72fd2 | ||
|
|
f99fb2af86 | ||
|
|
244628f467 | ||
|
|
637bd33e69 | ||
|
|
e53c068d78 | ||
|
|
4e181d30fa | ||
|
|
e60cc50dff | ||
|
|
f2dab9b334 | ||
|
|
fc6cfbd418 | ||
|
|
480a3f66c9 | ||
|
|
19e41a1e69 | ||
|
|
b4cdd55f62 | ||
|
|
6b6dcafcee | ||
|
|
303cde8f60 | ||
|
|
e672b61417 | ||
|
|
4a3030df9e | ||
|
|
30aa1b5223 | ||
|
|
b438a9cc08 | ||
|
|
a87edd732d | ||
|
|
79ad635515 | ||
|
|
7e51866d23 | ||
|
|
73affb491a | ||
|
|
ddc2036956 | ||
|
|
631552c554 | ||
|
|
dce35b90fe | ||
|
|
fc666cf42a | ||
|
|
67b9167b80 | ||
|
|
e97bd70264 | ||
|
|
9089e6b595 | ||
|
|
7e13f3f514 | ||
|
|
760a1525fb | ||
|
|
760dd98ddc | ||
|
|
ecf71da888 | ||
|
|
8a63c898c8 | ||
|
|
efaa66f70d | ||
|
|
4c40cf8783 | ||
|
|
6dfb03ab2e | ||
|
|
3a54bbb617 | ||
|
|
2a5d3ad5b9 | ||
|
|
a97ee5c1d3 | ||
|
|
647e557869 | ||
|
|
2a26c96000 | ||
|
|
fa4bd05a3a | ||
|
|
209522e2e0 | ||
|
|
652e8af81e | ||
|
|
c7a0d9b188 | ||
|
|
3013916232 | ||
|
|
5411f9d217 | ||
|
|
be388084c2 | ||
|
|
e76bac5d14 | ||
|
|
aec1bfa0bb | ||
|
|
8740ca7dee | ||
|
|
23710167cd | ||
|
|
3a9463edac | ||
|
|
fc483ef5d0 | ||
|
|
38ea99ec74 | ||
|
|
9c25c697dd | ||
|
|
b7533f5112 | ||
|
|
c3a81166fc | ||
|
|
ab0d0f677b | ||
|
|
06fe67d719 | ||
|
|
6a00be5f90 | ||
|
|
cd8187d7ce | ||
|
|
8344fae387 | ||
|
|
3fe0718932 | ||
|
|
cd3b871122 | ||
|
|
edcb2326a1 | ||
|
|
b11dbb49f9 | ||
|
|
44183de706 | ||
|
|
3fffa78164 | ||
|
|
2f81c5f580 | ||
|
|
26b203e573 | ||
|
|
c74fb78194 | ||
|
|
cd79e01be3 | ||
|
|
0e490a3c26 | ||
|
|
4506bb2e02 | ||
|
|
74a4ff1adc | ||
|
|
8a52c7b3d9 | ||
|
|
3979fce4f9 | ||
|
|
8f4f33be78 | ||
|
|
46d74c8f09 | ||
|
|
75c9b216e5 | ||
|
|
b40b85c21a | ||
|
|
6d60b035b4 | ||
|
|
bc49fb1cdf | ||
|
|
9694c0611c | ||
|
|
4b2056fcc1 | ||
|
|
a75c3adc4f | ||
|
|
b7404399ef | ||
|
|
f337c9019c | ||
|
|
8ba9c9098a | ||
|
|
8bc4d4bcd4 | ||
|
|
dc05c93c02 | ||
|
|
4ed97f7e35 | ||
|
|
f33a812c07 | ||
|
|
d22d6aed16 | ||
|
|
93f2d42259 | ||
|
|
861cd026d1 | ||
|
|
9a529ca78b | ||
|
|
9f0cd3514c | ||
|
|
bb2425e612 | ||
|
|
5baf90ffef | ||
|
|
3308347a43 | ||
|
|
22044af066 | ||
|
|
a9d243327c | ||
|
|
975fd5bc8d | ||
|
|
bd95baa4f7 | ||
|
|
1be39ac847 | ||
|
|
b67d9bf7f0 | ||
|
|
d1f40731e3 | ||
|
|
4bc5e183ef | ||
|
|
64af2feda0 | ||
|
|
8314b83f9d | ||
|
|
2aa375149f | ||
|
|
0b301e9af4 | ||
|
|
6bc5fe6952 | ||
|
|
893f070560 | ||
|
|
9eb0934492 | ||
|
|
87ac8b0456 | ||
|
|
a3483acaab | ||
|
|
0f2e7510cb | ||
|
|
6cd047e7c2 | ||
|
|
d58ede1b34 | ||
|
|
775c61ef5f | ||
|
|
57a77ecdf9 | ||
|
|
382c554786 | ||
|
|
e6c9123262 | ||
|
|
e400295969 | ||
|
|
da000ce511 | ||
|
|
a911eb748b | ||
|
|
a1b6567059 | ||
|
|
8741a86f93 | ||
|
|
ed537edacf | ||
|
|
91666fe194 | ||
|
|
c6b7444d16 | ||
|
|
42487d0dac | ||
|
|
832bdbc777 | ||
|
|
d9c5040fc5 | ||
|
|
6f50253a4d | ||
|
|
aad7b678b0 | ||
|
|
e29d3516bf | ||
|
|
5ab5b75348 | ||
|
|
2652c9eacf | ||
|
|
218636a0ea | ||
|
|
f164b8b357 | ||
|
|
abd5ec98ab | ||
|
|
eb6b35671a | ||
|
|
3b5463591b | ||
|
|
4ad8b613c9 | ||
|
|
1969452c3f | ||
|
|
134cc64aff | ||
|
|
0c020cdb7a | ||
|
|
2f5e5e9a71 | ||
|
|
1323683d72 | ||
|
|
7e376e5aba | ||
|
|
e2ef5e2329 | ||
|
|
c99d72575e | ||
|
|
5c0dc93d1e | ||
|
|
6cf5a5fbcd | ||
|
|
0b6ebf3343 | ||
|
|
d24c6095ce | ||
|
|
64a7a34c83 | ||
|
|
f2744978a0 | ||
|
|
5037298d82 | ||
|
|
0a82c819bb | ||
|
|
a434133aac | ||
|
|
4823288b3b | ||
|
|
164aaa48db | ||
|
|
878e1a2201 | ||
|
|
6360e1146f | ||
|
|
626313a397 | ||
|
|
606a7dbc75 | ||
|
|
7cbe271d08 | ||
|
|
06d409dc27 | ||
|
|
295bcde7b8 | ||
|
|
8d50cd82d3 | ||
|
|
32d3a820c8 | ||
|
|
1dc57d4c31 | ||
|
|
fe69b02951 | ||
|
|
3e2e26549a | ||
|
|
4c7a94aac4 | ||
|
|
434c8a1c91 | ||
|
|
04575333d3 | ||
|
|
50558e0d56 | ||
|
|
8fe449c883 | ||
|
|
8b32c31252 | ||
|
|
2e101e8413 | ||
|
|
a77996dc56 | ||
|
|
5e8fda4c64 | ||
|
|
76cf013df5 | ||
|
|
450dc3a206 | ||
|
|
7b438965bd | ||
|
|
c5bbf83904 | ||
|
|
f4f74a2391 | ||
|
|
0c8f0aacf5 | ||
|
|
1de4aff06d | ||
|
|
5b9be2cdb1 | ||
|
|
9d6e79019f | ||
|
|
b5e4e2f257 | ||
|
|
59d1fa65df | ||
|
|
6428440086 | ||
|
|
d419fb561d | ||
|
|
6c60cd2b72 | ||
|
|
1ee5654220 | ||
|
|
54f8e4145e | ||
|
|
d1e5f4bd3c | ||
|
|
3ad29972d0 | ||
|
|
43557b16a6 | ||
|
|
fd97f530e3 | ||
|
|
bbed91bf71 | ||
|
|
49b106d357 | ||
|
|
7a7728db13 | ||
|
|
aee4c92344 | ||
|
|
78fb0ade09 | ||
|
|
f48dc96d43 | ||
|
|
ff7f0df871 | ||
|
|
4ee537a04a |
@@ -16,6 +16,19 @@ warm caches, local build state, and fast feedback.
|
||||
|
||||
Testbox is the expensive path. Reach for it deliberately.
|
||||
|
||||
OpenClaw maintainers can opt into Testbox-first validation by setting
|
||||
`OPENCLAW_TESTBOX=1` in their environment or standing agent rules. This mode is
|
||||
maintainers-only and requires Blacksmith access.
|
||||
|
||||
When `OPENCLAW_TESTBOX=1` is set in OpenClaw:
|
||||
|
||||
- Pre-warm a Testbox early for longer, wider, or uncertain work.
|
||||
- Prefer Testbox for `pnpm` gates, e2e, package-like proof, and broad suites.
|
||||
- Reuse the same Testbox ID for every run command in the same task/session.
|
||||
- Use local commands only when the task explicitly sets
|
||||
`OPENCLAW_LOCAL_CHECK_MODE=throttled|full`, or when the user asks for local
|
||||
proof.
|
||||
|
||||
## Install the CLI
|
||||
|
||||
If `blacksmith` is not installed, install it:
|
||||
@@ -81,7 +94,8 @@ Prefer Testbox when:
|
||||
- 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:
|
||||
For OpenClaw specifically, normal local iteration stays local unless maintainer
|
||||
Testbox mode is enabled with `OPENCLAW_TESTBOX=1`:
|
||||
|
||||
- `pnpm check:changed`
|
||||
- `pnpm test:changed`
|
||||
@@ -89,27 +103,49 @@ For OpenClaw specifically, normal local iteration should stay local:
|
||||
- `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.
|
||||
If `OPENCLAW_TESTBOX=1` is enabled, run those same repo commands inside the
|
||||
warm Testbox. If the user wants laptop-friendly local proof for one command, use
|
||||
the explicit escape hatch `OPENCLAW_LOCAL_CHECK_MODE=throttled`.
|
||||
|
||||
For installable-package product proof, prefer the GitHub `Package Acceptance`
|
||||
workflow over an ad hoc Testbox command. It resolves one package candidate
|
||||
(`source=npm`, `source=ref`, `source=url`, or `source=artifact`), uploads it as
|
||||
`package-under-test`, and runs the reusable Docker E2E lanes against that exact
|
||||
tarball on GitHub/Blacksmith runners. Use `workflow_ref` for the trusted
|
||||
workflow/harness code and `package_ref` for the source ref to pack when testing
|
||||
an older trusted branch, tag, or SHA.
|
||||
|
||||
## Setup: Warmup before coding
|
||||
|
||||
If you decided Testbox is actually warranted, warm one up early. This returns
|
||||
an ID instantly and boots the CI environment in the background while you work:
|
||||
If you decided Testbox is warranted, warm one up early. This returns an ID
|
||||
instantly and boots the CI environment in the background while you work:
|
||||
|
||||
blacksmith testbox warmup ci-check-testbox.yml
|
||||
# → tbx_01jkz5b3t9...
|
||||
|
||||
Save this ID. You need it for every `run` command.
|
||||
|
||||
For OpenClaw maintainer Testbox mode, pre-warm at the start of longer or wider
|
||||
tasks:
|
||||
|
||||
blacksmith testbox warmup ci-check-testbox.yml --ref main --idle-timeout 90
|
||||
|
||||
Use the build-artifact warmup when e2e/package/build proof benefits from seeded
|
||||
`dist/`, `dist-runtime/`, and build-all caches:
|
||||
|
||||
blacksmith testbox warmup ci-build-artifacts-testbox.yml --ref main --idle-timeout 90
|
||||
|
||||
Warmup dispatches a GitHub Actions workflow that provisions a VM with the
|
||||
full CI environment: dependencies installed, services started, secrets
|
||||
injected, and a clean checkout of the repo at the default branch.
|
||||
|
||||
In OpenClaw, raw commit SHAs are not reliable dispatch refs for `warmup --ref`;
|
||||
use a branch or tag. The build-artifact workflow resolves `openclaw@beta` and
|
||||
`openclaw@latest` to SHA cache keys internally.
|
||||
|
||||
Options:
|
||||
|
||||
--ref <branch> Git ref to dispatch against (default: repo's default branch)
|
||||
--ref <branch|tag> Git ref to dispatch against (default: repo's default branch)
|
||||
--job <name> Specific job within the workflow (if it has multiple)
|
||||
--idle-timeout <min> Idle timeout in minutes (default: 30)
|
||||
|
||||
@@ -226,6 +262,11 @@ services, CI-only runners, or reproducibility against the workflow image.
|
||||
|
||||
If the repo says local tests/builds are the normal path, follow the repo.
|
||||
|
||||
OpenClaw maintainer exception: if `OPENCLAW_TESTBOX=1` is set by the user or
|
||||
agent environment, treat Testbox as the normal validation path for this repo.
|
||||
Use `OPENCLAW_LOCAL_CHECK_MODE=throttled|full` as the explicit local escape
|
||||
hatch.
|
||||
|
||||
## When to use
|
||||
|
||||
Use Testbox when:
|
||||
@@ -242,12 +283,13 @@ 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
|
||||
1. Decide whether the repo's local loop is the right default. For OpenClaw,
|
||||
`OPENCLAW_TESTBOX=1` makes Testbox the maintainer default.
|
||||
2. If Testbox is warranted, warm up early:
|
||||
`blacksmith testbox warmup ci-check-testbox.yml --ref main --idle-timeout 90` → save the ID
|
||||
3. Write code while the testbox boots in the background.
|
||||
4. Run the remote command when needed:
|
||||
`blacksmith testbox run --id <ID> "npm test"`
|
||||
`blacksmith testbox run --id <ID> "pnpm check:changed"`
|
||||
5. If tests fail, fix code and re-run against the same warm box.
|
||||
6. If you changed dependency manifests (package.json, etc.), prepend
|
||||
the install command: `blacksmith testbox run --id <ID> "npm install && npm test"`
|
||||
@@ -268,9 +310,9 @@ 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.
|
||||
When validating before commit/push in maintainer Testbox mode, run
|
||||
`pnpm check:changed` inside the warmed box first when appropriate, then the full
|
||||
suite with the profile above if broad confidence is needed.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -324,12 +366,14 @@ timeout is reached). Default timeout is 5m; use `--wait-timeout` for longer
|
||||
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:
|
||||
If you need a longer session, increase the timeout at warmup time. For OpenClaw
|
||||
maintainer work, use 90 minutes for long-running sessions:
|
||||
|
||||
blacksmith testbox warmup ci-check-testbox.yml --idle-timeout 60
|
||||
blacksmith testbox warmup ci-check-testbox.yml --idle-timeout 90
|
||||
blacksmith testbox warmup ci-build-artifacts-testbox.yml --idle-timeout 90
|
||||
|
||||
## With options
|
||||
|
||||
blacksmith testbox warmup ci-check-testbox.yml --ref main
|
||||
blacksmith testbox warmup ci-check-testbox.yml --idle-timeout 60
|
||||
blacksmith testbox warmup ci-check-testbox.yml --idle-timeout 90
|
||||
blacksmith testbox run --id <ID> "go test ./..."
|
||||
|
||||
@@ -62,6 +62,24 @@ scenario through qa-channel, decodes the emitted protobuf spans, and verifies
|
||||
the exported trace names and privacy contract. It does not require Opik,
|
||||
Langfuse, or external collector credentials.
|
||||
|
||||
## Matrix live profiles
|
||||
|
||||
`pnpm openclaw qa matrix` defaults to the full `all` profile. Use explicit
|
||||
profiles for faster CI/release proof:
|
||||
|
||||
```bash
|
||||
OPENCLAW_QA_MATRIX_NO_REPLY_WINDOW_MS=3000 \
|
||||
pnpm openclaw qa matrix --profile fast --fail-fast
|
||||
```
|
||||
|
||||
- `fast`: release-critical transport contract, excluding generated image and
|
||||
deep E2EE recovery inventory.
|
||||
- `transport`, `media`, `e2ee-smoke`, `e2ee-deep`, `e2ee-cli`: sharded full
|
||||
Matrix coverage.
|
||||
- `QA-Lab - All Lanes` uses explicit `fast` Matrix on scheduled runs. Manual
|
||||
dispatch keeps `matrix_profile=all` as the default and always shards that full
|
||||
Matrix selection.
|
||||
|
||||
## QA credentials and 1Password
|
||||
|
||||
- Use `op` only inside `tmux` for QA secret lookup in this repo.
|
||||
|
||||
@@ -25,15 +25,36 @@ Use this skill for release and publish-time workflow. Keep ordinary development
|
||||
- Before release branching, commit any dirty files in coherent groups, push,
|
||||
pull/rebase, then run `/changelog` on `main` and commit/push/pull that
|
||||
changelog rewrite immediately before creating the release branch.
|
||||
- During release planning, inspect both `src/plugins/compat/registry.ts` and
|
||||
`src/commands/doctor/shared/deprecation-compat.ts` before branching and again
|
||||
before final publish. For every deprecated or removal-pending compatibility
|
||||
record whose `removeAfter` date is on or before the release date, either
|
||||
remove the compatibility path where safe and validate the affected tests, or
|
||||
write down why removal is blocked and get explicit maintainer approval before
|
||||
shipping the expired compatibility path.
|
||||
- When removing deprecated runtime/config compatibility, preserve any doctor
|
||||
migration, repair, or hint that is still needed by supported upgrade paths.
|
||||
Doctor-side compatibility should stay tracked in
|
||||
`src/commands/doctor/shared/deprecation-compat.ts` until maintainers confirm
|
||||
the repair is no longer needed.
|
||||
- Revalidate compatibility replacement text during release planning. The
|
||||
recommended replacement can shift as plugin ownership, externalization, and
|
||||
config footprint move, so do not blindly copy stale replacement annotations
|
||||
into release notes.
|
||||
- Do not delete or rewrite beta tags after they leave the machine. If a
|
||||
published or pushed beta needs a fix, commit the fix on the release branch and
|
||||
increment to the next `-beta.N`.
|
||||
- For a beta release train, run the full pre-npm test roster before publishing
|
||||
each beta. After a beta is published, run the smaller published-install roster
|
||||
focused on install/update/Docker/Parallels. If anything fails, fix it on the
|
||||
release branch, commit/push/pull, increment beta number, and repeat. Operators
|
||||
may authorize up to 4 autonomous beta attempts; after 4 failed beta attempts,
|
||||
stop and report.
|
||||
- For a beta release train, run the fast local preflight first, publish the
|
||||
beta to npm `beta`, then run the expensive published-package roster focused
|
||||
on install/update/Docker/Parallels/NPM Telegram. If anything fails, fix it on
|
||||
the release branch, commit/push/pull, increment beta number, and repeat. Run
|
||||
the full expensive roster at least once before stable/latest promotion; for
|
||||
later beta attempts, rerun only lanes whose evidence changed unless the fix
|
||||
touches broad release, install/update, plugin, Docker, Parallels, or live QA
|
||||
behavior. After each beta is published, scan current `main` once for critical
|
||||
fixes that landed after the release branch cut and backport only important
|
||||
low-risk fixes. Operators may authorize up to 4 autonomous beta attempts;
|
||||
after 4 failed beta attempts, stop and report.
|
||||
- Use `/changelog` before version/tag preparation so the top changelog section
|
||||
is deduped and ordered by user impact.
|
||||
- Do not create beta-specific `CHANGELOG.md` headings. Beta releases use the
|
||||
@@ -75,6 +96,11 @@ Use this skill for release and publish-time workflow. Keep ordinary development
|
||||
parallel, publish npm from the successful npm preflight, then start published
|
||||
npm install/update, Docker, and Parallels verification while mac artifacts
|
||||
continue.
|
||||
- After a beta is published, overlap remote/manual release rosters where useful,
|
||||
but avoid piling local Docker, Parallels, and QA-Lab work onto the same host
|
||||
when it would create system-load noise. Use selective reruns after failures or
|
||||
fixes, but keep proof that Docker, Parallels, and QA-Lab each passed at least
|
||||
once before stable/latest promotion.
|
||||
- Mac packaging may be built from a slight release-branch variation of the
|
||||
tagged commit when the delta is mac packaging, signing, workflow, or
|
||||
validation-only release machinery. If mac packaging needs release-branch-only
|
||||
@@ -107,6 +133,13 @@ Use this skill for release and publish-time workflow. Keep ordinary development
|
||||
`CHANGELOG.md` version section, not highlights or an excerpt. When creating
|
||||
or editing a release, extract from `## YYYY.M.D` through the line before the
|
||||
next level-2 heading and use that complete block as the release notes.
|
||||
- When preparing release notes, scan `src/plugins/compat/registry.ts` and
|
||||
`src/commands/doctor/shared/deprecation-compat.ts` for compatibility records
|
||||
with `warningStarts` or `removeAfter` within 7 days after the release date.
|
||||
Add an `Upcoming deprecations` note to the release notes when any exist,
|
||||
including the compatibility code, target date, replacement, and a link to the
|
||||
record's `docsPath` or `/plugins/compatibility` when no more specific
|
||||
deprecation page exists.
|
||||
- When cutting a mac release with a beta GitHub prerelease:
|
||||
- tag `vYYYY.M.D-beta.N` from the release commit
|
||||
- create a prerelease titled `openclaw YYYY.M.D-beta.N`
|
||||
@@ -292,9 +325,11 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
|
||||
- Docker install/update coverage that exercises the published beta package
|
||||
- published npm Telegram proof: dispatch Actions > `NPM Telegram Beta E2E`
|
||||
from `main` with `package_spec=openclaw@<beta-version>` and
|
||||
`provider_mode=mock-openai`, approve `npm-release`, and require success.
|
||||
This is the default button path for installed-package onboarding,
|
||||
Telegram setup, and real Telegram E2E against the published npm package.
|
||||
`provider_mode=mock-openai`, and require success. This workflow is
|
||||
maintainer-dispatched and intentionally has no `npm-release` approval gate;
|
||||
`qa-live-shared` only supplies the shared QA secrets. This is the default
|
||||
button path for installed-package onboarding, Telegram setup, and real
|
||||
Telegram E2E against the published npm package.
|
||||
Use the local `pnpm test:docker:npm-telegram-live` lane with the matching
|
||||
`OPENCLAW_NPM_TELEGRAM_PACKAGE_SPEC` and Convex CI env only as a fallback
|
||||
or debugging path.
|
||||
@@ -491,8 +526,10 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
|
||||
6. Create `release/YYYY.M.D` from that post-changelog `main` commit.
|
||||
7. Make every repo version location match the beta tag before creating it.
|
||||
8. Commit release preparation changes on the release branch and push the branch.
|
||||
9. Run the local build, Docker, and Parallels parts of the full pre-npm beta
|
||||
test roster from the release branch before any npm preflight or publish.
|
||||
9. Run the fast local beta preflight from the release branch before any npm
|
||||
preflight or publish. Keep expensive Docker, Parallels, and published-package
|
||||
install/update lanes for after the beta is live unless the operator asks to
|
||||
run them before beta publication.
|
||||
10. For beta releases, skip mac app build/sign/notarize unless beta scope or a
|
||||
release blocker specifically requires it. For stable releases, include the
|
||||
mac app, signing, notarization, and appcast path.
|
||||
@@ -529,10 +566,16 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
|
||||
21. Wait for `npm-release` approval from `@openclaw/openclaw-release-managers`.
|
||||
22. Run postpublish verification:
|
||||
`node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>`.
|
||||
23. Run the post-published beta verification roster. If any lane fails after
|
||||
the beta tag/package is pushed or published, fix, commit/push/pull,
|
||||
increment to the next beta tag, and restart at the full pre-npm beta test
|
||||
roster for the new beta. The roster includes the manual Actions >
|
||||
23. Run the post-published beta verification roster. First scan current `main`
|
||||
for critical fixes that landed after the release branch cut; backport only
|
||||
important low-risk fixes before starting expensive lanes, or increment to
|
||||
the next beta if the fix must change the already-published package. If any
|
||||
lane fails after the beta tag/package is pushed or published, fix,
|
||||
commit/push/pull, increment to the next beta tag, and rerun the affected
|
||||
beta evidence. Once the beta is live, start remote/manual rosters where they
|
||||
can overlap safely, but keep local Docker and Parallels load controlled.
|
||||
Ensure the full expensive roster has passed at least once before
|
||||
stable/latest promotion. The roster includes the manual Actions >
|
||||
`NPM Telegram Beta E2E` workflow against the exact published beta package.
|
||||
If a pre-npm lane fails before any tag/package leaves the machine, fix and
|
||||
rerun the same intended beta attempt. Repeat up to the operator's
|
||||
|
||||
488
.agents/skills/openclaw-testing/SKILL.md
Normal file
488
.agents/skills/openclaw-testing/SKILL.md
Normal file
@@ -0,0 +1,488 @@
|
||||
---
|
||||
name: openclaw-testing
|
||||
description: Choose, run, rerun, or debug OpenClaw tests, CI checks, Docker E2E lanes, release validation, and the cheapest safe verification path.
|
||||
---
|
||||
|
||||
# OpenClaw Testing
|
||||
|
||||
Use this skill when deciding what to test, debugging failures, rerunning CI,
|
||||
or validating a change without wasting hours.
|
||||
|
||||
## Read First
|
||||
|
||||
- `docs/reference/test.md` for local test commands.
|
||||
- `docs/ci.md` for CI scope, release checks, Docker chunks, and runner behavior.
|
||||
- Scoped `AGENTS.md` files before editing code under a subtree.
|
||||
|
||||
## Default Rule
|
||||
|
||||
Prove the touched surface first. Do not reflexively run the whole suite.
|
||||
|
||||
1. Inspect the diff and classify the touched surface:
|
||||
- source: `pnpm changed:lanes --json`, then `pnpm check:changed`
|
||||
- tests only: `pnpm test:changed`
|
||||
- one failing file: `pnpm test <path-or-filter> -- --reporter=verbose`
|
||||
- workflow-only: `git diff --check`, workflow syntax/lint (`actionlint` when available)
|
||||
- docs-only: `pnpm docs:list`, docs formatter/lint only if docs tooling changed or requested
|
||||
2. Reproduce narrowly before fixing.
|
||||
3. Fix root cause.
|
||||
4. Rerun the same narrow proof.
|
||||
5. Broaden only when the touched contract demands it.
|
||||
|
||||
## Guardrails
|
||||
|
||||
- Do not kill unrelated processes or tests. If something is running elsewhere, treat it as owned by the user or another agent.
|
||||
- Do not run expensive local Docker, full release checks, full `pnpm test`, or full `pnpm check` unless the user asks or the change genuinely requires it.
|
||||
- Prefer GitHub Actions for release/Docker proof when the workflow already has the prepared image and secrets.
|
||||
- Use `scripts/committer "<msg>" <paths...>` when committing; stage only your files.
|
||||
- If deps are missing, run `pnpm install`, retry once, then report the first actionable error.
|
||||
|
||||
## Local Test Shortcuts
|
||||
|
||||
```bash
|
||||
pnpm changed:lanes --json
|
||||
pnpm check:changed # changed typecheck/lint/guards; no Vitest
|
||||
pnpm test:changed # cheap smart changed Vitest targets
|
||||
OPENCLAW_TEST_CHANGED_BROAD=1 pnpm test:changed
|
||||
pnpm test <path-or-filter> -- --reporter=verbose
|
||||
OPENCLAW_VITEST_MAX_WORKERS=1 pnpm test <path-or-filter>
|
||||
```
|
||||
|
||||
Use targeted file paths whenever possible. Avoid raw `vitest`; use the repo
|
||||
`pnpm test` wrapper so project routing, workers, and setup stay correct.
|
||||
|
||||
## Command Semantics
|
||||
|
||||
- `pnpm check` and `pnpm check:changed` do not run Vitest tests. They are for
|
||||
typecheck, lint, and guard proof.
|
||||
- `pnpm test` and `pnpm test:changed` run Vitest tests.
|
||||
- `pnpm test:changed` is intentionally cheap by default: direct test edits,
|
||||
sibling tests, explicit source mappings, and import-graph dependents.
|
||||
- `OPENCLAW_TEST_CHANGED_BROAD=1 pnpm test:changed` is the explicit broad
|
||||
fallback for harness/config/package edits that genuinely need it.
|
||||
- Do not run extension sweeps just because core changed. If a core edit is for a
|
||||
specific plugin bug, run that plugin's tests explicitly. If a public SDK or
|
||||
contract change needs consumer proof, choose the smallest representative
|
||||
plugin/contract tests first, then broaden only when the risk justifies it.
|
||||
- The test wrapper prints a short `[test] passed|failed|skipped ... in ...`
|
||||
line. Vitest's own duration is still the per-shard detail.
|
||||
|
||||
## Routing Model
|
||||
|
||||
- `pnpm changed:lanes --json` answers "which check lanes does this diff touch?"
|
||||
It is used by `pnpm check:changed` for typecheck/lint/guard selection.
|
||||
- `pnpm test:changed` answers "which Vitest targets are worth running now?" It
|
||||
uses the same changed path list, but applies a cheaper test-target resolver.
|
||||
- Direct test edits run themselves. Source edits prefer explicit mappings,
|
||||
sibling `*.test.ts`, then import-graph dependents. Shared harness/config/root
|
||||
edits are skipped by default unless they have precise mapped tests.
|
||||
- Public SDK or contract edits do not automatically run every plugin test.
|
||||
`check:changed` proves extension type contracts; the agent chooses the
|
||||
smallest plugin/contract Vitest proof that matches the actual risk.
|
||||
- Use `OPENCLAW_TEST_CHANGED_BROAD=1 pnpm test:changed` only when a harness,
|
||||
config, package, or unknown-root edit really needs the broad Vitest fallback.
|
||||
|
||||
## CI Debugging
|
||||
|
||||
Start with current run state, not logs for everything:
|
||||
|
||||
```bash
|
||||
gh run list --branch main --limit 10
|
||||
gh run view <run-id> --json status,conclusion,headSha,url,jobs
|
||||
gh run view <run-id> --job <job-id> --log
|
||||
```
|
||||
|
||||
- Check exact SHA. Ignore newer unrelated `main` unless asked.
|
||||
- For cancelled same-branch runs, confirm whether a newer run superseded it.
|
||||
- Fetch full logs only for failed or relevant jobs.
|
||||
|
||||
## GitHub Release Workflows
|
||||
|
||||
Use the smallest workflow that proves the current risk. The full umbrella is
|
||||
available, but it is usually the last step after narrower proof, not the first
|
||||
rerun after a focused patch.
|
||||
|
||||
### Full Release Validation
|
||||
|
||||
`Full Release Validation` (`.github/workflows/full-release-validation.yml`) is
|
||||
the manual "everything before release" umbrella. It resolves a target ref, then
|
||||
dispatches:
|
||||
|
||||
- manual `CI` for the full normal CI graph
|
||||
- `OpenClaw Release Checks` for install smoke, cross-OS release checks, live and
|
||||
E2E checks, Docker release-path suites, OpenWebUI, QA Lab, fast Matrix, and
|
||||
Telegram release lanes
|
||||
- optional post-publish Telegram E2E when a package spec is supplied
|
||||
|
||||
Run it only when validating an actual release candidate, after broad shared CI
|
||||
or release orchestration changes, or when explicitly asked:
|
||||
|
||||
```bash
|
||||
gh workflow run full-release-validation.yml \
|
||||
--repo openclaw/openclaw \
|
||||
--ref main \
|
||||
-f ref=<branch-or-sha> \
|
||||
-f workflow_ref=main \
|
||||
-f provider=openai \
|
||||
-f mode=both
|
||||
```
|
||||
|
||||
If a full run is already active on a newer `origin/main`, prefer watching that
|
||||
run over dispatching a duplicate. If you accidentally dispatch a stale duplicate,
|
||||
cancel it and monitor the current run.
|
||||
|
||||
### Release Evidence
|
||||
|
||||
After release-candidate validation or before a release decision, record the
|
||||
important run ids in the private `openclaw/releases-private` evidence ledger.
|
||||
Use the manual `OpenClaw Release Evidence`
|
||||
(`openclaw-release-evidence.yml`) workflow there. It writes durable summaries
|
||||
under `evidence/<release-id>/` and commits:
|
||||
|
||||
- `release-evidence.md`
|
||||
- `release-evidence.json`
|
||||
- `index.json`
|
||||
- `runs/<label>.json`
|
||||
|
||||
Use one run per line:
|
||||
|
||||
```text
|
||||
full-release-validation openclaw/openclaw <run-id> blocking
|
||||
package-acceptance openclaw/openclaw <run-id> blocking
|
||||
release-checks openclaw/openclaw <run-id> blocking
|
||||
```
|
||||
|
||||
Store summaries, run URLs, artifact metadata, timings, pass/fail state, and
|
||||
short release-manager notes there. Do not store raw logs, provider
|
||||
prompts/responses, channel transcripts, signing material, or secret-bearing
|
||||
config in git; raw logs stay in Actions artifacts.
|
||||
|
||||
When `Full Release Validation` completes and
|
||||
`OPENCLAW_RELEASES_PRIVATE_DISPATCH_TOKEN` is configured in the public repo, it
|
||||
requests the private `OpenClaw Release Evidence From Full Validation` workflow.
|
||||
That private workflow reads the parent full-validation run, extracts the child
|
||||
CI/release-checks/Telegram run ids from the parent logs, and opens the evidence
|
||||
PR automatically. If the token is absent or the run predates this wiring, trigger
|
||||
that private workflow manually with the full-validation run id.
|
||||
|
||||
### Release Checks
|
||||
|
||||
`OpenClaw Release Checks` (`openclaw-release-checks.yml`) is the release child
|
||||
workflow. It is broader than normal CI but narrower than the umbrella because it
|
||||
does not dispatch the separate full normal CI child. It runs Package Acceptance
|
||||
with `telegram_mode=mock-openai`, so the release package tarball also goes
|
||||
through Telegram package QA. Use it when release-path validation is needed
|
||||
without rerunning the entire umbrella.
|
||||
|
||||
```bash
|
||||
gh workflow run openclaw-release-checks.yml \
|
||||
--repo openclaw/openclaw \
|
||||
--ref main \
|
||||
-f ref=<branch-or-sha> \
|
||||
-f provider=openai \
|
||||
-f mode=both
|
||||
```
|
||||
|
||||
### QA Lab Matrix Profiles
|
||||
|
||||
`pnpm openclaw qa matrix` defaults to `--profile all`. Do not assume the CLI
|
||||
default is the fast release path. Use explicit profiles:
|
||||
|
||||
- `--profile fast --fail-fast`: release-critical Matrix transport contract
|
||||
- `--profile transport|media|e2ee-smoke|e2ee-deep|e2ee-cli`: sharded full
|
||||
Matrix proof
|
||||
- `OPENCLAW_QA_MATRIX_NO_REPLY_WINDOW_MS=3000`: CI-friendly no-reply quiet
|
||||
window when paired with fast or sharded gates
|
||||
|
||||
`QA-Lab - All Lanes` uses explicit fast Matrix on scheduled runs; manual
|
||||
dispatch keeps `matrix_profile=all` as the default and always shards that full
|
||||
Matrix selection. `OpenClaw Release Checks` uses explicit fast Matrix; run the
|
||||
all-lanes workflow when release investigation needs full Matrix media/E2EE
|
||||
inventory.
|
||||
|
||||
### Reusable Live/E2E Checks
|
||||
|
||||
`OpenClaw Live And E2E Checks (Reusable)`
|
||||
(`openclaw-live-and-e2e-checks-reusable.yml`) is the preferred entry point for
|
||||
targeted live, Docker, model, and E2E proof. Inputs let you turn off unrelated
|
||||
lanes:
|
||||
|
||||
```bash
|
||||
gh workflow run openclaw-live-and-e2e-checks-reusable.yml \
|
||||
--repo openclaw/openclaw \
|
||||
--ref main \
|
||||
-f ref=<sha> \
|
||||
-f include_repo_e2e=false \
|
||||
-f include_release_path_suites=false \
|
||||
-f include_openwebui=false \
|
||||
-f include_live_suites=true \
|
||||
-f live_models_only=true \
|
||||
-f live_model_providers=fireworks
|
||||
```
|
||||
|
||||
Useful knobs:
|
||||
|
||||
- `docker_lanes='<lane[,lane]>'`: run selected Docker scheduler lanes against
|
||||
prepared artifacts instead of the three release chunks.
|
||||
- `include_live_suites=false`: skip live/provider suites when testing Docker
|
||||
scheduler or release packaging only.
|
||||
- `live_models_only=true`: run only Docker live model coverage.
|
||||
- `live_model_providers=fireworks` (or comma/space separated providers): run one
|
||||
targeted Docker live model job instead of the full provider matrix.
|
||||
- blank `live_model_providers`: run the full live-model provider matrix.
|
||||
|
||||
For model-list or provider-selection fixes, use `live_models_only=true` plus the
|
||||
specific `live_model_providers` allowlist. Confirm logs show the expected
|
||||
`OPENCLAW_LIVE_PROVIDERS` and selected model ids before declaring proof.
|
||||
|
||||
## Docker
|
||||
|
||||
Docker is expensive. First inspect the scheduler without running Docker:
|
||||
|
||||
```bash
|
||||
OPENCLAW_DOCKER_ALL_DRY_RUN=1 pnpm test:docker:all
|
||||
OPENCLAW_DOCKER_ALL_DRY_RUN=1 OPENCLAW_DOCKER_ALL_LANES=install-e2e pnpm test:docker:all
|
||||
OPENCLAW_DOCKER_ALL_LANES=install-e2e node scripts/test-docker-all.mjs --plan-json
|
||||
```
|
||||
|
||||
Run one failed lane locally only when explicitly asked or when GitHub is not
|
||||
usable:
|
||||
|
||||
```bash
|
||||
OPENCLAW_DOCKER_ALL_LANES=<lane> \
|
||||
OPENCLAW_DOCKER_ALL_BUILD=0 \
|
||||
OPENCLAW_DOCKER_ALL_PREFLIGHT=0 \
|
||||
OPENCLAW_SKIP_DOCKER_BUILD=1 \
|
||||
OPENCLAW_DOCKER_E2E_BARE_IMAGE='<prepared-bare-image>' \
|
||||
OPENCLAW_DOCKER_E2E_FUNCTIONAL_IMAGE='<prepared-functional-image>' \
|
||||
pnpm test:docker:all
|
||||
```
|
||||
|
||||
For release validation, prefer the reusable GitHub workflow input:
|
||||
|
||||
```yaml
|
||||
docker_lanes: install-e2e
|
||||
```
|
||||
|
||||
Multiple lanes are allowed:
|
||||
|
||||
```yaml
|
||||
docker_lanes: install-e2e bundled-channel-update-acpx
|
||||
```
|
||||
|
||||
That skips the release chunk matrix and runs one targeted Docker job against the
|
||||
prepared GHCR images and the selected package artifact. Rerun commands
|
||||
generated inside GitHub artifacts include `package_artifact_run_id`,
|
||||
`package_artifact_name`, `docker_e2e_bare_image`, and
|
||||
`docker_e2e_functional_image` when available, so failed lanes can reuse the
|
||||
exact tarball and prepared images from the failed run. When the fix changes
|
||||
package contents, omit those reuse inputs so the workflow packs a new tarball.
|
||||
Live-only targeted reruns skip the E2E images and build only the live-test
|
||||
image. Release-path normal mode remains max three Docker chunk jobs:
|
||||
|
||||
- `core`
|
||||
- `package-update`
|
||||
- `plugins-integrations`
|
||||
|
||||
OpenWebUI is folded into `plugins-integrations` for full release-path coverage
|
||||
and keeps a standalone `openwebui` chunk only for OpenWebUI-only dispatches.
|
||||
|
||||
## Package Acceptance
|
||||
|
||||
Use the manual `Package Acceptance` workflow when the question is "does this
|
||||
installable package work as a product?" rather than "does this source diff pass
|
||||
Vitest?"
|
||||
|
||||
In release validation, treat Package Acceptance as the package-candidate shard
|
||||
inside the larger release umbrella, not as a competing full-test path. Full
|
||||
Release Validation and private release gauntlets should call Package Acceptance
|
||||
for tarball resolution, Docker product/package proof, and optional Telegram QA
|
||||
against the same resolved `package-under-test` artifact; keep orchestration,
|
||||
secret policy, blocking/advisory status, and evidence rollup in the caller.
|
||||
|
||||
Good defaults:
|
||||
|
||||
```bash
|
||||
gh workflow run package-acceptance.yml --ref main \
|
||||
-f source=npm \
|
||||
-f workflow_ref=main \
|
||||
-f package_spec=openclaw@beta \
|
||||
-f suite_profile=product \
|
||||
-f telegram_mode=mock-openai
|
||||
```
|
||||
|
||||
Npm candidate selection:
|
||||
|
||||
- Resolve the registry immediately before dispatch:
|
||||
`npm view openclaw dist-tags --json --prefer-online --cache /tmp/openclaw-npm-cache-verify-$$`
|
||||
and `npm view openclaw@beta version dist.tarball dist.integrity --json --prefer-online --cache /tmp/openclaw-npm-cache-verify-$$`.
|
||||
- If Peter asks for "latest beta", use `source=npm` with
|
||||
`package_spec=openclaw@beta`, then record the resolved version from `npm view`
|
||||
or the workflow summary.
|
||||
- For reruns, release proof, or comparing one known package, prefer the exact
|
||||
immutable spec: `package_spec=openclaw@YYYY.M.D-beta.N` or
|
||||
`package_spec=openclaw@YYYY.M.D`.
|
||||
- For stable package proof, use `package_spec=openclaw@latest` only when the
|
||||
question is explicitly the current stable dist-tag; otherwise pin the exact
|
||||
version.
|
||||
- `source=npm` only accepts registry specs for `openclaw@beta`,
|
||||
`openclaw@latest`, or exact OpenClaw release versions. Do not pass semver
|
||||
ranges, git refs, file paths, tarball URLs, or plugin package names there.
|
||||
- If the candidate is a tarball URL, use `source=url` with `package_sha256`. If
|
||||
it is an Actions tarball artifact, use `source=artifact`. If it is an
|
||||
unpublished source candidate, use `source=ref` with a trusted ref or SHA.
|
||||
- Package acceptance tests exactly the selected package candidate. Do not apply
|
||||
`openclaw update --channel beta` fallback semantics here; if `beta` is absent,
|
||||
stale, older than `latest`, or points at a broken tarball, report that tag
|
||||
state instead of silently testing `latest`.
|
||||
|
||||
Profiles:
|
||||
|
||||
- `smoke`: quick confidence that the tarball installs, can onboard a channel,
|
||||
can run an agent turn, and basic gateway/config lanes work.
|
||||
- `package`: release-package contract. Adds installer/update, doctor install
|
||||
switching, bundled plugin runtime deps, plugin install/update, and package
|
||||
repair lanes. This is the default native replacement for most Parallels
|
||||
package/update coverage.
|
||||
- `product`: package profile plus broader product surfaces: MCP channels,
|
||||
cron/subagent cleanup, OpenAI web search, and OpenWebUI.
|
||||
- `full`: split Docker release-path chunks with OpenWebUI.
|
||||
- `custom`: exact `docker_lanes` list for a focused rerun.
|
||||
|
||||
Candidate sources:
|
||||
|
||||
- `source=npm`: `openclaw@beta`, `openclaw@latest`, or an exact release version.
|
||||
- `source=ref`: pack `package_ref` using the trusted `workflow_ref` harness.
|
||||
This intentionally separates old package commits from new workflow/test code.
|
||||
- `source=url`: HTTPS `.tgz` plus required `package_sha256`.
|
||||
- `source=artifact`: download one `.tgz` from `artifact_run_id`/`artifact_name`.
|
||||
|
||||
Ref model:
|
||||
|
||||
- `gh workflow run ... --ref <workflow-ref>` selects the workflow file revision
|
||||
GitHub executes.
|
||||
- `workflow_ref` is the trusted harness/script ref passed to reusable Docker
|
||||
E2E.
|
||||
- `package_ref` is the source ref to build when `source=ref`. It can be an
|
||||
older branch/tag/SHA as long as it is reachable from an OpenClaw branch or
|
||||
release tag.
|
||||
|
||||
Example: run latest package acceptance harness against an older trusted commit:
|
||||
|
||||
```bash
|
||||
gh workflow run package-acceptance.yml --ref main \
|
||||
-f workflow_ref=main \
|
||||
-f source=ref \
|
||||
-f package_ref=<branch-or-sha> \
|
||||
-f suite_profile=package \
|
||||
-f telegram_mode=mock-openai
|
||||
```
|
||||
|
||||
Use `telegram_mode=mock-openai` or `telegram_mode=live-frontier` when the same
|
||||
resolved `package-under-test` tarball should also run through the Telegram QA
|
||||
workflow in the `qa-live-shared` environment. The standalone Telegram workflow
|
||||
still accepts a published npm spec for post-publish checks, but Package
|
||||
Acceptance passes the resolved artifact for `source=npm`, `ref`, `url`, and
|
||||
`artifact`. Use `telegram_mode=none` only when intentionally skipping Telegram
|
||||
credentialed package proof for a focused rerun.
|
||||
|
||||
Docker E2E images never copy repo sources as the app under test: the bare image
|
||||
is a Node/Git runner, and the functional image installs the same prebuilt npm
|
||||
tarball that bare lanes mount. `scripts/package-openclaw-for-docker.mjs` is the
|
||||
single packer for local scripts and CI and validates the tarball inventory
|
||||
before Docker consumes it. `scripts/test-docker-all.mjs --plan-json` is the
|
||||
scheduler-owned CI plan for image kind, package, live image, lane, and
|
||||
credential needs. Docker lane definitions live in the single scenario catalog
|
||||
`scripts/lib/docker-e2e-scenarios.mjs`; planner logic lives in
|
||||
`scripts/lib/docker-e2e-plan.mjs`. `scripts/docker-e2e.mjs` converts plan and
|
||||
summary JSON into GitHub outputs and step summaries. Every scheduler run writes
|
||||
`.artifacts/docker-tests/**/summary.json` plus `failures.json`. Read those
|
||||
before rerunning. Lane entries include `command`, `rerunCommand`, status,
|
||||
timing, timeout state, image kind, and log file path. The summary also includes
|
||||
top-level phase timings for preflight, image build, package prep, lane pools,
|
||||
and cleanup. Use `pnpm test:docker:timings <summary.json>` to rank slow lanes
|
||||
and phases before deciding whether a broader rerun is justified.
|
||||
|
||||
## Cheap Docker Reruns
|
||||
|
||||
First derive the smallest rerun command from artifacts:
|
||||
|
||||
```bash
|
||||
pnpm test:docker:rerun <github-run-id>
|
||||
pnpm test:docker:rerun .artifacts/docker-tests/<run>/failures.json
|
||||
```
|
||||
|
||||
The script downloads Docker E2E artifacts for a GitHub run, reads
|
||||
`summary.json`/`failures.json`, and prints a combined targeted workflow command
|
||||
plus per-lane commands. Prefer the combined targeted command when several lanes
|
||||
failed for the same patch:
|
||||
|
||||
```bash
|
||||
gh workflow run openclaw-live-and-e2e-checks-reusable.yml \
|
||||
-f ref=<sha> \
|
||||
-f include_repo_e2e=false \
|
||||
-f include_release_path_suites=false \
|
||||
-f include_openwebui=false \
|
||||
-f docker_lanes='install-e2e bundled-channel-update-acpx' \
|
||||
-f include_live_suites=false \
|
||||
-f live_models_only=false
|
||||
```
|
||||
|
||||
That path still runs the prepare job, so it creates a new tarball for `<sha>`.
|
||||
If the SHA-tagged GHCR bare/functional image already exists, CI skips rebuilding
|
||||
that image and only uploads the fresh package artifact before the targeted lane
|
||||
job. Do not rerun the full three-chunk release path unless the failed lane list
|
||||
or touched surface really requires it.
|
||||
|
||||
## Docker Expected Timings
|
||||
|
||||
Treat these as ballpark. Blacksmith queue time, GHCR pull speed, provider
|
||||
latency, npm cache state, and Docker daemon health can dominate.
|
||||
|
||||
Current local timing artifact (`.artifacts/docker-tests/lane-timings.json`) has
|
||||
these rough bands:
|
||||
|
||||
- Tiny lanes, seconds to under 1 minute:
|
||||
`agents-delete-shared-workspace` ~3s, `plugin-update` ~7s,
|
||||
`config-reload` ~14s, `pi-bundle-mcp-tools` ~15s, `onboard` ~18s,
|
||||
`session-runtime-context` ~20s, `gateway-network` ~34s, `qr` ~44s.
|
||||
- Medium deterministic lanes, ~1-5 minutes:
|
||||
`npm-onboard-channel-agent` ~96s, `openai-image-auth` ~99s,
|
||||
bundled channel/update lanes usually ~90-300s, `openwebui` ~225s,
|
||||
`mcp-channels` ~274s.
|
||||
- Heavy deterministic lanes, ~6-10 minutes:
|
||||
`bundled-channel-root-owned` ~429s,
|
||||
`bundled-channel-setup-entry` ~420s,
|
||||
`bundled-channel-load-failure` ~383s,
|
||||
`cron-mcp-cleanup` ~567s.
|
||||
- Live provider lanes, often ~15-20 minutes:
|
||||
`live-gateway` ~958s, `live-models` ~1054s.
|
||||
- Installer/release lanes:
|
||||
`install-e2e` and package-update paths can vary widely with npm, provider,
|
||||
and package registry behavior. Budget tens of minutes; prefer GitHub targeted
|
||||
reruns over local repeats.
|
||||
|
||||
Default fallback lane timeout is 120 minutes. A timeout usually means debug the
|
||||
lane log/artifacts first, not “run the whole thing again.”
|
||||
|
||||
## Failure Workflow
|
||||
|
||||
1. Identify exact failing job, SHA, lane, and artifact path.
|
||||
2. Read `failures.json`, `summary.json`, and the failed lane log tail.
|
||||
3. Use `pnpm test:docker:rerun <run-id|failures.json>` to generate targeted
|
||||
GitHub rerun commands.
|
||||
4. If the lane has `rerunCommand`, use that only as a local starting point.
|
||||
5. For Docker release failures, dispatch targeted `docker_lanes=<failed-lane>`
|
||||
on GitHub before considering local Docker.
|
||||
6. Patch narrowly, then rerun the failed file/lane only.
|
||||
7. Broaden to `pnpm check:changed` or CI only after the isolated proof passes.
|
||||
|
||||
## When To Escalate
|
||||
|
||||
- Public SDK/plugin contract changes: run changed gate plus relevant extension
|
||||
validation.
|
||||
- Build output, lazy imports, package boundaries, or published surfaces:
|
||||
include `pnpm build`.
|
||||
- Workflow edits: run `pnpm check:workflows`.
|
||||
- Release branch or tag validation: use release docs and GitHub workflows; avoid
|
||||
local Docker unless Peter explicitly asks.
|
||||
4
.agents/skills/openclaw-testing/agents/openai.yaml
Normal file
4
.agents/skills/openclaw-testing/agents/openai.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
interface:
|
||||
display_name: "OpenClaw Testing"
|
||||
short_description: "Choose cheap, targeted OpenClaw validation"
|
||||
default_prompt: "Use $openclaw-testing to choose the cheapest safe test or CI verification path, inspect failures, and rerun only the relevant OpenClaw lane."
|
||||
149
.github/actions/docker-e2e-plan/action.yml
vendored
Normal file
149
.github/actions/docker-e2e-plan/action.yml
vendored
Normal file
@@ -0,0 +1,149 @@
|
||||
name: Docker E2E plan and hydrate
|
||||
description: >
|
||||
Create a Docker E2E lane plan, expose GitHub outputs, and optionally hydrate
|
||||
the prebuilt package artifact plus shared Docker images needed by the plan.
|
||||
inputs:
|
||||
mode:
|
||||
description: prepare, chunk, or targeted.
|
||||
required: true
|
||||
chunk:
|
||||
description: Release-path chunk for mode=chunk.
|
||||
required: false
|
||||
default: ""
|
||||
lanes:
|
||||
description: Comma/space separated lane names for targeted or prepare mode.
|
||||
required: false
|
||||
default: ""
|
||||
include-openwebui:
|
||||
description: Whether Open WebUI is included when planning release/prepare coverage.
|
||||
required: false
|
||||
default: "true"
|
||||
include-release-path-suites:
|
||||
description: Whether prepare mode should plan all release-path suites.
|
||||
required: false
|
||||
default: "false"
|
||||
hydrate-artifacts:
|
||||
description: Whether to download/pull artifacts required by the plan.
|
||||
required: false
|
||||
default: "true"
|
||||
package-artifact-name:
|
||||
description: Workflow artifact name containing openclaw-current.tgz.
|
||||
required: false
|
||||
default: docker-e2e-package
|
||||
outputs:
|
||||
credentials:
|
||||
description: Comma-separated credential groups required by selected lanes.
|
||||
value: ${{ steps.plan.outputs.credentials }}
|
||||
needs_bare_image:
|
||||
description: "1 when selected lanes require the bare Docker E2E image."
|
||||
value: ${{ steps.plan.outputs.needs_bare_image }}
|
||||
needs_e2e_image:
|
||||
description: "1 when selected lanes require any Docker E2E image."
|
||||
value: ${{ steps.plan.outputs.needs_e2e_image }}
|
||||
needs_functional_image:
|
||||
description: "1 when selected lanes require the functional Docker E2E image."
|
||||
value: ${{ steps.plan.outputs.needs_functional_image }}
|
||||
needs_live_image:
|
||||
description: "1 when selected lanes require building the live Docker image."
|
||||
value: ${{ steps.plan.outputs.needs_live_image }}
|
||||
needs_package:
|
||||
description: "1 when selected lanes require the OpenClaw package tarball."
|
||||
value: ${{ steps.plan.outputs.needs_package }}
|
||||
plan_json:
|
||||
description: Path to the generated plan JSON.
|
||||
value: ${{ steps.plan.outputs.plan_json }}
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Plan Docker E2E lanes
|
||||
id: plan
|
||||
shell: bash
|
||||
env:
|
||||
MODE: ${{ inputs.mode }}
|
||||
CHUNK: ${{ inputs.chunk }}
|
||||
LANES: ${{ inputs.lanes }}
|
||||
INCLUDE_OPENWEBUI: ${{ inputs.include-openwebui }}
|
||||
INCLUDE_RELEASE_PATH_SUITES: ${{ inputs.include-release-path-suites }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p .artifacts/docker-tests
|
||||
|
||||
case "$MODE" in
|
||||
prepare)
|
||||
plan_path=".artifacts/docker-tests/plan.json"
|
||||
if [[ "$INCLUDE_RELEASE_PATH_SUITES" == "true" ]]; then
|
||||
export OPENCLAW_DOCKER_ALL_PROFILE=release-path
|
||||
export OPENCLAW_DOCKER_ALL_PLAN_RELEASE_ALL=1
|
||||
elif [[ -n "$LANES" ]]; then
|
||||
export OPENCLAW_DOCKER_ALL_LANES="$LANES"
|
||||
elif [[ "$INCLUDE_OPENWEBUI" == "true" ]]; then
|
||||
export OPENCLAW_DOCKER_ALL_LANES=openwebui
|
||||
fi
|
||||
;;
|
||||
chunk)
|
||||
if [[ -z "$CHUNK" ]]; then
|
||||
echo "chunk input is required for Docker E2E chunk planning." >&2
|
||||
exit 1
|
||||
fi
|
||||
export OPENCLAW_DOCKER_ALL_PROFILE=release-path
|
||||
export OPENCLAW_DOCKER_ALL_CHUNK="$CHUNK"
|
||||
plan_path=".artifacts/docker-tests/release-${CHUNK}-plan.json"
|
||||
;;
|
||||
targeted)
|
||||
if [[ -z "$LANES" ]]; then
|
||||
echo "lanes input is required for Docker E2E targeted planning." >&2
|
||||
exit 1
|
||||
fi
|
||||
export OPENCLAW_DOCKER_ALL_LANES="$LANES"
|
||||
plan_path=".artifacts/docker-tests/targeted-plan.json"
|
||||
;;
|
||||
*)
|
||||
echo "mode must be prepare, chunk, or targeted. Got: $MODE" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
export OPENCLAW_DOCKER_ALL_INCLUDE_OPENWEBUI="$INCLUDE_OPENWEBUI"
|
||||
node scripts/test-docker-all.mjs --plan-json > "$plan_path"
|
||||
node scripts/docker-e2e.mjs github-outputs "$plan_path" >> "$GITHUB_OUTPUT"
|
||||
echo "plan_json=$plan_path" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Download OpenClaw Docker E2E package
|
||||
if: inputs.hydrate-artifacts == 'true' && steps.plan.outputs.needs_package == '1'
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: ${{ inputs.package-artifact-name }}
|
||||
path: .artifacts/docker-e2e-package
|
||||
|
||||
- name: Pull shared bare Docker E2E image
|
||||
if: inputs.hydrate-artifacts == 'true' && steps.plan.outputs.needs_bare_image == '1'
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
docker pull "${OPENCLAW_DOCKER_E2E_BARE_IMAGE}"
|
||||
|
||||
- name: Pull shared functional Docker E2E image
|
||||
if: inputs.hydrate-artifacts == 'true' && steps.plan.outputs.needs_functional_image == '1'
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
docker pull "${OPENCLAW_DOCKER_E2E_FUNCTIONAL_IMAGE}"
|
||||
|
||||
- name: Validate Docker E2E credentials
|
||||
if: inputs.hydrate-artifacts == 'true'
|
||||
shell: bash
|
||||
env:
|
||||
CREDENTIALS: ${{ steps.plan.outputs.credentials }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
credentials=",$CREDENTIALS,"
|
||||
if [[ "$credentials" == *",openai,"* ]]; then
|
||||
[[ -n "${OPENAI_API_KEY:-}" ]] || {
|
||||
echo "OPENAI_API_KEY is required for selected Docker E2E lanes." >&2
|
||||
exit 1
|
||||
}
|
||||
fi
|
||||
if [[ "$credentials" == *",anthropic,"* && -z "${ANTHROPIC_API_TOKEN:-}" && -z "${ANTHROPIC_API_KEY:-}" ]]; then
|
||||
echo "ANTHROPIC_API_TOKEN or ANTHROPIC_API_KEY is required for selected Docker E2E lanes." >&2
|
||||
exit 1
|
||||
fi
|
||||
4
.github/labeler.yml
vendored
4
.github/labeler.yml
vendored
@@ -233,6 +233,10 @@
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/diagnostics-otel/**"
|
||||
"extensions: diagnostics-prometheus":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/diagnostics-prometheus/**"
|
||||
"extensions: llm-task":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
|
||||
198
.github/workflows/ci-build-artifacts-testbox.yml
vendored
Normal file
198
.github/workflows/ci-build-artifacts-testbox.yml
vendored
Normal file
@@ -0,0 +1,198 @@
|
||||
name: Blacksmith Build Artifacts 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:
|
||||
build-artifacts:
|
||||
permissions:
|
||||
contents: read
|
||||
name: "build-artifacts"
|
||||
runs-on: blacksmith-8vcpu-ubuntu-2404
|
||||
timeout-minutes: 35
|
||||
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}/5 succeeded"
|
||||
}
|
||||
|
||||
for attempt in 1 2 3 4 5; do
|
||||
if checkout_attempt "$attempt"; then
|
||||
exit 0
|
||||
fi
|
||||
echo "checkout attempt ${attempt}/5 failed"
|
||||
sleep $((attempt * 5))
|
||||
done
|
||||
|
||||
echo "checkout failed after 5 attempts" >&2
|
||||
exit 1
|
||||
|
||||
- name: Setup Node environment
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
install-bun: "false"
|
||||
|
||||
- name: Resolve release dist cache seeds
|
||||
id: dist-cache-seeds
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
cache_prefix="${RUNNER_OS}-dist-build-"
|
||||
declare -A seen=()
|
||||
|
||||
resolve_tag_sha() {
|
||||
local tag="$1"
|
||||
local direct=""
|
||||
local peeled=""
|
||||
|
||||
while read -r sha ref; do
|
||||
if [[ "$ref" == "refs/tags/${tag}^{}" ]]; then
|
||||
peeled="$sha"
|
||||
elif [[ "$ref" == "refs/tags/${tag}" ]]; then
|
||||
direct="$sha"
|
||||
fi
|
||||
done < <(git ls-remote --tags origin "refs/tags/${tag}" "refs/tags/${tag}^{}")
|
||||
|
||||
printf '%s\n' "${peeled:-$direct}"
|
||||
}
|
||||
|
||||
{
|
||||
echo "restore-keys<<EOF"
|
||||
for dist_tag in beta latest; do
|
||||
version="$(npm view "openclaw@${dist_tag}" version 2>/dev/null || true)"
|
||||
if [[ -z "$version" ]]; then
|
||||
echo "Could not resolve npm dist-tag ${dist_tag}; skipping cache seed." >&2
|
||||
continue
|
||||
fi
|
||||
|
||||
sha="$(resolve_tag_sha "v${version}")"
|
||||
if [[ -z "$sha" ]]; then
|
||||
echo "Could not resolve git tag v${version}; skipping cache seed." >&2
|
||||
continue
|
||||
fi
|
||||
|
||||
key="${cache_prefix}${sha}"
|
||||
if [[ -z "${seen[$key]+x}" ]]; then
|
||||
echo "$key"
|
||||
seen[$key]=1
|
||||
fi
|
||||
done
|
||||
echo "${cache_prefix}"
|
||||
echo "EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Restore dist build cache
|
||||
id: dist-cache
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: |
|
||||
.artifacts/build-all-cache/
|
||||
dist/
|
||||
dist-runtime/
|
||||
key: ${{ runner.os }}-dist-build-${{ github.sha }}
|
||||
restore-keys: ${{ steps.dist-cache-seeds.outputs.restore-keys }}
|
||||
|
||||
- name: Build dist on cache miss
|
||||
if: steps.dist-cache.outputs.cache-hit != 'true'
|
||||
run: pnpm build:ci-artifacts
|
||||
|
||||
- name: Build Control UI on cache miss
|
||||
if: steps.dist-cache.outputs.cache-hit != 'true'
|
||||
run: pnpm ui:build
|
||||
|
||||
- name: Verify build artifacts
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
test -d dist
|
||||
test -d dist-runtime
|
||||
if [[ ! -f dist/index.js && ! -f dist/index.mjs ]]; then
|
||||
echo "Missing dist/index.js or dist/index.mjs" >&2
|
||||
exit 1
|
||||
fi
|
||||
test -f dist/build-info.json
|
||||
test -f dist/control-ui/index.html
|
||||
|
||||
- name: Save dist build cache
|
||||
if: steps.dist-cache.outputs.cache-hit != 'true'
|
||||
uses: actions/cache/save@v5
|
||||
with:
|
||||
path: |
|
||||
.artifacts/build-all-cache/
|
||||
dist/
|
||||
dist-runtime/
|
||||
key: ${{ runner.os }}-dist-build-${{ github.sha }}
|
||||
|
||||
- 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"
|
||||
209
.github/workflows/ci.yml
vendored
209
.github/workflows/ci.yml
vendored
@@ -1,6 +1,13 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
target_ref:
|
||||
description: Optional branch, tag, or full commit SHA to validate instead of the workflow ref
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
push:
|
||||
branches: [main]
|
||||
paths-ignore:
|
||||
@@ -13,8 +20,8 @@ permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
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
|
||||
group: ${{ github.event_name == 'workflow_dispatch' && format('{0}-manual-v1-{1}', github.workflow, github.run_id) || (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: ${{ github.event_name != 'workflow_dispatch' }}
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
@@ -29,6 +36,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 20
|
||||
outputs:
|
||||
checkout_sha: ${{ steps.checkout_ref.outputs.sha }}
|
||||
docs_only: ${{ steps.manifest.outputs.docs_only }}
|
||||
docs_changed: ${{ steps.manifest.outputs.docs_changed }}
|
||||
run_node: ${{ steps.manifest.outputs.run_node }}
|
||||
@@ -37,8 +45,6 @@ jobs:
|
||||
run_skills_python: ${{ steps.manifest.outputs.run_skills_python }}
|
||||
run_skills_python_job: ${{ steps.manifest.outputs.run_skills_python_job }}
|
||||
run_windows: ${{ steps.manifest.outputs.run_windows }}
|
||||
has_changed_extensions: ${{ steps.manifest.outputs.has_changed_extensions }}
|
||||
changed_extensions_matrix: ${{ steps.manifest.outputs.changed_extensions_matrix }}
|
||||
run_build_artifacts: ${{ steps.manifest.outputs.run_build_artifacts }}
|
||||
run_checks_fast_core: ${{ steps.manifest.outputs.run_checks_fast_core }}
|
||||
run_checks_fast: ${{ steps.manifest.outputs.run_checks_fast }}
|
||||
@@ -51,8 +57,6 @@ jobs:
|
||||
checks_node_core_nondist_matrix: ${{ steps.manifest.outputs.checks_node_core_nondist_matrix }}
|
||||
run_checks_node_core_dist: ${{ steps.manifest.outputs.run_checks_node_core_dist }}
|
||||
checks_node_core_dist_matrix: ${{ steps.manifest.outputs.checks_node_core_dist_matrix }}
|
||||
run_extension_fast: ${{ steps.manifest.outputs.run_extension_fast }}
|
||||
extension_fast_matrix: ${{ steps.manifest.outputs.extension_fast_matrix }}
|
||||
run_check: ${{ steps.manifest.outputs.run_check }}
|
||||
run_check_additional: ${{ steps.manifest.outputs.run_check_additional }}
|
||||
run_build_smoke: ${{ steps.manifest.outputs.run_build_smoke }}
|
||||
@@ -69,12 +73,18 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ inputs.target_ref || github.sha }}
|
||||
fetch-depth: 1
|
||||
fetch-tags: false
|
||||
persist-credentials: false
|
||||
submodules: false
|
||||
|
||||
- name: Resolve checkout SHA
|
||||
id: checkout_ref
|
||||
run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Ensure preflight base commit
|
||||
if: github.event_name != 'workflow_dispatch'
|
||||
uses: ./.github/actions/ensure-base-commit
|
||||
with:
|
||||
base-sha: ${{ github.event_name == 'push' && github.event.before || github.event.pull_request.base.sha }}
|
||||
@@ -82,11 +92,12 @@ jobs:
|
||||
|
||||
- name: Detect docs-only changes
|
||||
id: docs_scope
|
||||
if: github.event_name != 'workflow_dispatch'
|
||||
uses: ./.github/actions/detect-docs-changes
|
||||
|
||||
- name: Detect changed scopes
|
||||
id: changed_scope
|
||||
if: steps.docs_scope.outputs.docs_only != 'true'
|
||||
if: github.event_name != 'workflow_dispatch' && steps.docs_scope.outputs.docs_only != 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@@ -99,45 +110,20 @@ jobs:
|
||||
|
||||
node scripts/ci-changed-scope.mjs --base "$BASE" --head HEAD
|
||||
|
||||
- name: Detect changed extensions
|
||||
id: changed_extensions
|
||||
if: steps.docs_scope.outputs.docs_only != 'true' && steps.changed_scope.outputs.run_node == 'true'
|
||||
env:
|
||||
BASE_SHA: ${{ github.event_name == 'push' && github.event.before || github.event.pull_request.base.sha }}
|
||||
BASE_REF: ${{ github.event_name == 'push' && github.ref_name || github.event.pull_request.base.ref }}
|
||||
run: |
|
||||
node --input-type=module <<'EOF'
|
||||
import { appendFileSync } from "node:fs";
|
||||
import { listChangedExtensionIds } from "./scripts/lib/changed-extensions.mjs";
|
||||
|
||||
const extensionIds = listChangedExtensionIds({
|
||||
base: process.env.BASE_SHA,
|
||||
head: "HEAD",
|
||||
fallbackBaseRef: process.env.BASE_REF,
|
||||
unavailableBaseBehavior: "all",
|
||||
});
|
||||
const matrix = JSON.stringify({ include: extensionIds.map((extension) => ({ extension })) });
|
||||
|
||||
appendFileSync(process.env.GITHUB_OUTPUT, `has_changed_extensions=${extensionIds.length > 0}\n`, "utf8");
|
||||
appendFileSync(process.env.GITHUB_OUTPUT, `changed_extensions_matrix=${matrix}\n`, "utf8");
|
||||
EOF
|
||||
|
||||
- name: Build CI manifest
|
||||
id: manifest
|
||||
env:
|
||||
OPENCLAW_CI_DOCS_ONLY: ${{ steps.docs_scope.outputs.docs_only }}
|
||||
OPENCLAW_CI_DOCS_CHANGED: ${{ steps.docs_scope.outputs.docs_changed }}
|
||||
OPENCLAW_CI_RUN_NODE: ${{ steps.changed_scope.outputs.run_node || 'false' }}
|
||||
OPENCLAW_CI_RUN_MACOS: ${{ steps.changed_scope.outputs.run_macos || 'false' }}
|
||||
OPENCLAW_CI_RUN_ANDROID: ${{ steps.changed_scope.outputs.run_android || 'false' }}
|
||||
OPENCLAW_CI_RUN_WINDOWS: ${{ steps.changed_scope.outputs.run_windows || 'false' }}
|
||||
OPENCLAW_CI_RUN_NODE_FAST_ONLY: ${{ steps.changed_scope.outputs.run_node_fast_only || 'false' }}
|
||||
OPENCLAW_CI_RUN_NODE_FAST_PLUGIN_CONTRACTS: ${{ steps.changed_scope.outputs.run_node_fast_plugin_contracts || 'false' }}
|
||||
OPENCLAW_CI_RUN_NODE_FAST_CI_ROUTING: ${{ steps.changed_scope.outputs.run_node_fast_ci_routing || 'false' }}
|
||||
OPENCLAW_CI_RUN_SKILLS_PYTHON: ${{ steps.changed_scope.outputs.run_skills_python || 'false' }}
|
||||
OPENCLAW_CI_RUN_CONTROL_UI_I18N: ${{ steps.changed_scope.outputs.run_control_ui_i18n || 'false' }}
|
||||
OPENCLAW_CI_HAS_CHANGED_EXTENSIONS: ${{ steps.changed_extensions.outputs.has_changed_extensions || 'false' }}
|
||||
OPENCLAW_CI_CHANGED_EXTENSIONS_MATRIX: ${{ steps.changed_extensions.outputs.changed_extensions_matrix || '{"include":[]}' }}
|
||||
OPENCLAW_CI_DOCS_ONLY: ${{ github.event_name == 'workflow_dispatch' && 'false' || steps.docs_scope.outputs.docs_only }}
|
||||
OPENCLAW_CI_DOCS_CHANGED: ${{ github.event_name == 'workflow_dispatch' && 'true' || steps.docs_scope.outputs.docs_changed }}
|
||||
OPENCLAW_CI_RUN_NODE: ${{ github.event_name == 'workflow_dispatch' && 'true' || steps.changed_scope.outputs.run_node || 'false' }}
|
||||
OPENCLAW_CI_RUN_MACOS: ${{ github.event_name == 'workflow_dispatch' && 'true' || steps.changed_scope.outputs.run_macos || 'false' }}
|
||||
OPENCLAW_CI_RUN_ANDROID: ${{ github.event_name == 'workflow_dispatch' && 'true' || steps.changed_scope.outputs.run_android || 'false' }}
|
||||
OPENCLAW_CI_RUN_WINDOWS: ${{ github.event_name == 'workflow_dispatch' && 'true' || steps.changed_scope.outputs.run_windows || 'false' }}
|
||||
OPENCLAW_CI_RUN_NODE_FAST_ONLY: ${{ github.event_name == 'workflow_dispatch' && 'false' || steps.changed_scope.outputs.run_node_fast_only || 'false' }}
|
||||
OPENCLAW_CI_RUN_NODE_FAST_PLUGIN_CONTRACTS: ${{ github.event_name == 'workflow_dispatch' && 'false' || steps.changed_scope.outputs.run_node_fast_plugin_contracts || 'false' }}
|
||||
OPENCLAW_CI_RUN_NODE_FAST_CI_ROUTING: ${{ github.event_name == 'workflow_dispatch' && 'false' || steps.changed_scope.outputs.run_node_fast_ci_routing || 'false' }}
|
||||
OPENCLAW_CI_RUN_SKILLS_PYTHON: ${{ github.event_name == 'workflow_dispatch' && 'true' || steps.changed_scope.outputs.run_skills_python || 'false' }}
|
||||
OPENCLAW_CI_RUN_CONTROL_UI_I18N: ${{ github.event_name == 'workflow_dispatch' && 'true' || steps.changed_scope.outputs.run_control_ui_i18n || 'false' }}
|
||||
OPENCLAW_CI_REPOSITORY: ${{ github.repository }}
|
||||
run: |
|
||||
node --input-type=module <<'EOF'
|
||||
@@ -161,18 +147,8 @@ jobs:
|
||||
return fallback;
|
||||
};
|
||||
|
||||
const parseJson = (value, fallback) => {
|
||||
try {
|
||||
return value ? JSON.parse(value) : fallback;
|
||||
} catch {
|
||||
return fallback;
|
||||
}
|
||||
};
|
||||
|
||||
const createMatrix = (include) => ({ include });
|
||||
const outputPath = process.env.GITHUB_OUTPUT;
|
||||
const eventName = process.env.GITHUB_EVENT_NAME ?? "pull_request";
|
||||
const isPush = eventName === "push";
|
||||
const isCanonicalRepository = process.env.OPENCLAW_CI_REPOSITORY === "openclaw/openclaw";
|
||||
const docsOnly = parseBoolean(process.env.OPENCLAW_CI_DOCS_ONLY);
|
||||
const docsChanged = parseBoolean(process.env.OPENCLAW_CI_DOCS_CHANGED);
|
||||
@@ -197,11 +173,6 @@ jobs:
|
||||
const runSkillsPython = parseBoolean(process.env.OPENCLAW_CI_RUN_SKILLS_PYTHON) && !docsOnly;
|
||||
const runControlUiI18n =
|
||||
parseBoolean(process.env.OPENCLAW_CI_RUN_CONTROL_UI_I18N) && !docsOnly;
|
||||
const hasChangedExtensions =
|
||||
parseBoolean(process.env.OPENCLAW_CI_HAS_CHANGED_EXTENSIONS) && !docsOnly;
|
||||
const changedExtensionsMatrix = hasChangedExtensions
|
||||
? parseJson(process.env.OPENCLAW_CI_CHANGED_EXTENSIONS_MATRIX, { include: [] })
|
||||
: { include: [] };
|
||||
const extensionTestShardCount = isCanonicalRepository
|
||||
? DEFAULT_EXTENSION_TEST_SHARD_COUNT
|
||||
: Math.max(DEFAULT_EXTENSION_TEST_SHARD_COUNT, 36);
|
||||
@@ -271,8 +242,6 @@ jobs:
|
||||
run_android: runAndroid,
|
||||
run_skills_python: runSkillsPython,
|
||||
run_windows: runWindows,
|
||||
has_changed_extensions: hasChangedExtensions,
|
||||
changed_extensions_matrix: changedExtensionsMatrix,
|
||||
run_build_artifacts: runNodeFull,
|
||||
run_checks_fast_core: runChecksFastCore,
|
||||
run_checks_fast: runNodeFull,
|
||||
@@ -293,15 +262,6 @@ 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 && !isPush,
|
||||
extension_fast_matrix: createMatrix(
|
||||
hasChangedExtensions && !isPush
|
||||
? (changedExtensionsMatrix.include ?? []).map((entry) => ({
|
||||
check_name: `extension-fast-${entry.extension}`,
|
||||
extension: entry.extension,
|
||||
}))
|
||||
: [],
|
||||
),
|
||||
run_check: runNodeFull,
|
||||
run_check_additional: runNodeFull,
|
||||
run_build_smoke: runNodeFull,
|
||||
@@ -354,12 +314,14 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ inputs.target_ref || github.sha }}
|
||||
fetch-depth: 1
|
||||
fetch-tags: false
|
||||
persist-credentials: false
|
||||
submodules: false
|
||||
|
||||
- name: Ensure security base commit
|
||||
if: github.event_name != 'workflow_dispatch'
|
||||
uses: ./.github/actions/ensure-base-commit
|
||||
with:
|
||||
base-sha: ${{ github.event_name == 'push' && github.event.before || github.event.pull_request.base.sha }}
|
||||
@@ -443,6 +405,7 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ inputs.target_ref || github.sha }}
|
||||
fetch-depth: 1
|
||||
fetch-tags: false
|
||||
persist-credentials: false
|
||||
@@ -505,7 +468,7 @@ jobs:
|
||||
shell: bash
|
||||
env:
|
||||
CHECKOUT_REPO: ${{ github.repository }}
|
||||
CHECKOUT_SHA: ${{ github.sha }}
|
||||
CHECKOUT_SHA: ${{ needs.preflight.outputs.checkout_sha }}
|
||||
CHECKOUT_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@@ -577,7 +540,7 @@ jobs:
|
||||
path: |
|
||||
dist/
|
||||
dist-runtime/
|
||||
key: ${{ runner.os }}-dist-build-${{ github.sha }}
|
||||
key: ${{ runner.os }}-dist-build-${{ needs.preflight.outputs.checkout_sha }}
|
||||
|
||||
- name: Pack built runtime artifacts
|
||||
run: tar --posix -cf dist-runtime-build.tar.zst --use-compress-program zstdmt dist dist-runtime
|
||||
@@ -706,7 +669,7 @@ jobs:
|
||||
shell: bash
|
||||
env:
|
||||
CHECKOUT_REPO: ${{ github.repository }}
|
||||
CHECKOUT_SHA: ${{ github.sha }}
|
||||
CHECKOUT_SHA: ${{ needs.preflight.outputs.checkout_sha }}
|
||||
CHECKOUT_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@@ -801,7 +764,7 @@ jobs:
|
||||
shell: bash
|
||||
env:
|
||||
CHECKOUT_REPO: ${{ github.repository }}
|
||||
CHECKOUT_SHA: ${{ github.sha }}
|
||||
CHECKOUT_SHA: ${{ needs.preflight.outputs.checkout_sha }}
|
||||
CHECKOUT_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@@ -904,7 +867,7 @@ jobs:
|
||||
shell: bash
|
||||
env:
|
||||
CHECKOUT_REPO: ${{ github.repository }}
|
||||
CHECKOUT_SHA: ${{ github.sha }}
|
||||
CHECKOUT_SHA: ${{ needs.preflight.outputs.checkout_sha }}
|
||||
CHECKOUT_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@@ -972,7 +935,7 @@ jobs:
|
||||
shell: bash
|
||||
env:
|
||||
CHECKOUT_REPO: ${{ github.repository }}
|
||||
CHECKOUT_SHA: ${{ github.sha }}
|
||||
CHECKOUT_SHA: ${{ needs.preflight.outputs.checkout_sha }}
|
||||
CHECKOUT_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@@ -1084,7 +1047,7 @@ jobs:
|
||||
contents: read
|
||||
name: checks-node-compat-node22
|
||||
needs: [preflight]
|
||||
if: needs.preflight.outputs.run_build_artifacts == 'true' && github.event_name == 'push'
|
||||
if: needs.preflight.outputs.run_build_artifacts == 'true' && github.event_name == 'workflow_dispatch'
|
||||
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
@@ -1092,7 +1055,7 @@ jobs:
|
||||
shell: bash
|
||||
env:
|
||||
CHECKOUT_REPO: ${{ github.repository }}
|
||||
CHECKOUT_SHA: ${{ github.sha }}
|
||||
CHECKOUT_SHA: ${{ needs.preflight.outputs.checkout_sha }}
|
||||
CHECKOUT_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@@ -1172,7 +1135,7 @@ jobs:
|
||||
shell: bash
|
||||
env:
|
||||
CHECKOUT_REPO: ${{ github.repository }}
|
||||
CHECKOUT_SHA: ${{ github.sha }}
|
||||
CHECKOUT_SHA: ${{ needs.preflight.outputs.checkout_sha }}
|
||||
CHECKOUT_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@@ -1323,84 +1286,6 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
extension-fast:
|
||||
permissions:
|
||||
contents: read
|
||||
name: "extension-fast"
|
||||
needs: [preflight]
|
||||
if: needs.preflight.outputs.run_extension_fast == 'true'
|
||||
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.extension_fast_matrix) }}
|
||||
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}/5 succeeded"
|
||||
}
|
||||
|
||||
for attempt in 1 2 3 4 5; do
|
||||
if checkout_attempt "$attempt"; then
|
||||
exit 0
|
||||
fi
|
||||
echo "checkout attempt ${attempt}/5 failed"
|
||||
sleep $((attempt * 5))
|
||||
done
|
||||
|
||||
echo "checkout failed after 5 attempts" >&2
|
||||
exit 1
|
||||
|
||||
- name: Setup Node environment
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
install-bun: "false"
|
||||
|
||||
- name: Run changed extension tests
|
||||
env:
|
||||
OPENCLAW_CHANGED_EXTENSION: ${{ matrix.extension }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ "$OPENCLAW_CHANGED_EXTENSION" = "telegram" ]; then
|
||||
export OPENCLAW_VITEST_MAX_WORKERS=1
|
||||
export NODE_OPTIONS="${NODE_OPTIONS:+$NODE_OPTIONS }--max-old-space-size=6144"
|
||||
pnpm test:extension "$OPENCLAW_CHANGED_EXTENSION" -- --pool=forks
|
||||
exit 0
|
||||
fi
|
||||
pnpm test:extension "$OPENCLAW_CHANGED_EXTENSION"
|
||||
|
||||
# Types, lint, and format check shards.
|
||||
check-shard:
|
||||
permissions:
|
||||
@@ -1437,7 +1322,7 @@ jobs:
|
||||
shell: bash
|
||||
env:
|
||||
CHECKOUT_REPO: ${{ github.repository }}
|
||||
CHECKOUT_SHA: ${{ github.sha }}
|
||||
CHECKOUT_SHA: ${{ needs.preflight.outputs.checkout_sha }}
|
||||
CHECKOUT_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@@ -1569,7 +1454,7 @@ jobs:
|
||||
shell: bash
|
||||
env:
|
||||
CHECKOUT_REPO: ${{ github.repository }}
|
||||
CHECKOUT_SHA: ${{ github.sha }}
|
||||
CHECKOUT_SHA: ${{ needs.preflight.outputs.checkout_sha }}
|
||||
CHECKOUT_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@@ -1767,7 +1652,7 @@ jobs:
|
||||
shell: bash
|
||||
env:
|
||||
CHECKOUT_REPO: ${{ github.repository }}
|
||||
CHECKOUT_SHA: ${{ github.sha }}
|
||||
CHECKOUT_SHA: ${{ needs.preflight.outputs.checkout_sha }}
|
||||
CHECKOUT_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@@ -1830,6 +1715,7 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ needs.preflight.outputs.checkout_sha }}
|
||||
persist-credentials: false
|
||||
submodules: false
|
||||
|
||||
@@ -1872,6 +1758,7 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ needs.preflight.outputs.checkout_sha }}
|
||||
persist-credentials: false
|
||||
submodules: false
|
||||
|
||||
@@ -1976,6 +1863,7 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ needs.preflight.outputs.checkout_sha }}
|
||||
persist-credentials: false
|
||||
submodules: false
|
||||
|
||||
@@ -2016,6 +1904,7 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ needs.preflight.outputs.checkout_sha }}
|
||||
persist-credentials: false
|
||||
submodules: false
|
||||
|
||||
@@ -2116,7 +2005,7 @@ jobs:
|
||||
shell: bash
|
||||
env:
|
||||
CHECKOUT_REPO: ${{ github.repository }}
|
||||
CHECKOUT_SHA: ${{ github.sha }}
|
||||
CHECKOUT_SHA: ${{ needs.preflight.outputs.checkout_sha }}
|
||||
CHECKOUT_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
171
.github/workflows/docker-release.yml
vendored
171
.github/workflows/docker-release.yml
vendored
@@ -63,7 +63,7 @@ jobs:
|
||||
|
||||
# KEEP THIS WORKFLOW ON GITHUB-HOSTED RUNNERS.
|
||||
# DO NOT MOVE IT BACK TO BLACKSMITH WITHOUT RE-VALIDATING TAG BUILDS AND BACKFILLS.
|
||||
# Build amd64 images (default + slim share the build stage cache)
|
||||
# Build amd64 image. Default and slim tags point to the same slim runtime.
|
||||
build-amd64:
|
||||
needs: [approve_manual_backfill]
|
||||
if: ${{ always() && (github.event_name != 'workflow_dispatch' || needs.approve_manual_backfill.result == 'success') }}
|
||||
@@ -74,7 +74,6 @@ jobs:
|
||||
contents: read
|
||||
outputs:
|
||||
digest: ${{ steps.build.outputs.digest }}
|
||||
slim-digest: ${{ steps.build-slim.outputs.digest }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
@@ -117,12 +116,7 @@ jobs:
|
||||
fi
|
||||
{
|
||||
echo "value<<EOF"
|
||||
printf "%s\n" "${tags[@]}"
|
||||
echo "EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
{
|
||||
echo "slim<<EOF"
|
||||
printf "%s\n" "${slim_tags[@]}"
|
||||
printf "%s\n" "${tags[@]}" "${slim_tags[@]}"
|
||||
echo "EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
@@ -159,28 +153,15 @@ jobs:
|
||||
platforms: linux/amd64
|
||||
cache-from: type=gha,scope=docker-release-amd64
|
||||
cache-to: type=gha,mode=max,scope=docker-release-amd64
|
||||
build-args: |
|
||||
OPENCLAW_EXTENSIONS=diagnostics-otel
|
||||
tags: ${{ steps.tags.outputs.value }}
|
||||
labels: ${{ steps.labels.outputs.value }}
|
||||
provenance: false
|
||||
sbom: true
|
||||
provenance: mode=max
|
||||
push: true
|
||||
|
||||
- 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@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64
|
||||
cache-from: type=gha,scope=docker-release-amd64
|
||||
cache-to: type=gha,mode=max,scope=docker-release-amd64
|
||||
build-args: |
|
||||
OPENCLAW_VARIANT=slim
|
||||
tags: ${{ steps.tags.outputs.slim }}
|
||||
labels: ${{ steps.labels.outputs.value }}
|
||||
provenance: false
|
||||
push: true
|
||||
|
||||
# Build arm64 images (default + slim share the build stage cache)
|
||||
# Build arm64 image. Default and slim tags point to the same slim runtime.
|
||||
build-arm64:
|
||||
needs: [approve_manual_backfill]
|
||||
if: ${{ always() && (github.event_name != 'workflow_dispatch' || needs.approve_manual_backfill.result == 'success') }}
|
||||
@@ -191,7 +172,6 @@ jobs:
|
||||
contents: read
|
||||
outputs:
|
||||
digest: ${{ steps.build.outputs.digest }}
|
||||
slim-digest: ${{ steps.build-slim.outputs.digest }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
@@ -234,12 +214,7 @@ jobs:
|
||||
fi
|
||||
{
|
||||
echo "value<<EOF"
|
||||
printf "%s\n" "${tags[@]}"
|
||||
echo "EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
{
|
||||
echo "slim<<EOF"
|
||||
printf "%s\n" "${slim_tags[@]}"
|
||||
printf "%s\n" "${tags[@]}" "${slim_tags[@]}"
|
||||
echo "EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
@@ -276,25 +251,12 @@ jobs:
|
||||
platforms: linux/arm64
|
||||
cache-from: type=gha,scope=docker-release-arm64
|
||||
cache-to: type=gha,mode=max,scope=docker-release-arm64
|
||||
build-args: |
|
||||
OPENCLAW_EXTENSIONS=diagnostics-otel
|
||||
tags: ${{ steps.tags.outputs.value }}
|
||||
labels: ${{ steps.labels.outputs.value }}
|
||||
provenance: false
|
||||
push: true
|
||||
|
||||
- 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@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/arm64
|
||||
cache-from: type=gha,scope=docker-release-arm64
|
||||
cache-to: type=gha,mode=max,scope=docker-release-arm64
|
||||
build-args: |
|
||||
OPENCLAW_VARIANT=slim
|
||||
tags: ${{ steps.tags.outputs.slim }}
|
||||
labels: ${{ steps.labels.outputs.value }}
|
||||
provenance: false
|
||||
sbom: true
|
||||
provenance: mode=max
|
||||
push: true
|
||||
|
||||
# Create multi-platform manifests
|
||||
@@ -351,16 +313,11 @@ jobs:
|
||||
fi
|
||||
{
|
||||
echo "value<<EOF"
|
||||
printf "%s\n" "${tags[@]}"
|
||||
echo "EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
{
|
||||
echo "slim<<EOF"
|
||||
printf "%s\n" "${slim_tags[@]}"
|
||||
printf "%s\n" "${tags[@]}" "${slim_tags[@]}"
|
||||
echo "EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Create and push default manifest
|
||||
- name: Create and push manifest
|
||||
shell: bash
|
||||
env:
|
||||
TAGS: ${{ steps.tags.outputs.value }}
|
||||
@@ -378,20 +335,94 @@ jobs:
|
||||
"${AMD64_DIGEST}" \
|
||||
"${ARM64_DIGEST}"
|
||||
|
||||
- name: Create and push slim manifest
|
||||
verify-attestations:
|
||||
needs: [create-manifest]
|
||||
if: ${{ always() && needs.create-manifest.result == 'success' }}
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: read
|
||||
packages: read
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Set up Docker Builder
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Resolve image refs
|
||||
id: refs
|
||||
shell: bash
|
||||
env:
|
||||
SLIM_TAGS: ${{ steps.tags.outputs.slim }}
|
||||
AMD64_SLIM_DIGEST: ${{ needs.build-amd64.outputs.slim-digest }}
|
||||
ARM64_SLIM_DIGEST: ${{ needs.build-arm64.outputs.slim-digest }}
|
||||
IMAGE: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
SOURCE_REF: ${{ github.event_name == 'workflow_dispatch' && format('refs/tags/{0}', inputs.tag) || github.ref }}
|
||||
IS_MANUAL_BACKFILL: ${{ github.event_name == 'workflow_dispatch' && '1' || '0' }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mapfile -t tags <<< "${SLIM_TAGS}"
|
||||
args=()
|
||||
for tag in "${tags[@]}"; do
|
||||
[ -z "$tag" ] && continue
|
||||
args+=("-t" "$tag")
|
||||
done
|
||||
docker buildx imagetools create "${args[@]}" \
|
||||
"${AMD64_SLIM_DIGEST}" \
|
||||
"${ARM64_SLIM_DIGEST}"
|
||||
multi_refs=()
|
||||
slim_multi_refs=()
|
||||
amd64_refs=()
|
||||
arm64_refs=()
|
||||
if [[ "${SOURCE_REF}" == "refs/heads/main" ]]; then
|
||||
multi_refs+=("${IMAGE}:main")
|
||||
slim_multi_refs+=("${IMAGE}:main-slim")
|
||||
amd64_refs+=("${IMAGE}:main-amd64" "${IMAGE}:main-slim-amd64")
|
||||
arm64_refs+=("${IMAGE}:main-arm64" "${IMAGE}:main-slim-arm64")
|
||||
fi
|
||||
if [[ "${SOURCE_REF}" == refs/tags/v* ]]; then
|
||||
version="${SOURCE_REF#refs/tags/v}"
|
||||
multi_refs+=("${IMAGE}:${version}")
|
||||
slim_multi_refs+=("${IMAGE}:${version}-slim")
|
||||
amd64_refs+=("${IMAGE}:${version}-amd64" "${IMAGE}:${version}-slim-amd64")
|
||||
arm64_refs+=("${IMAGE}:${version}-arm64" "${IMAGE}:${version}-slim-arm64")
|
||||
if [[ "${IS_MANUAL_BACKFILL}" != "1" && "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9]+)?$ ]]; then
|
||||
multi_refs+=("${IMAGE}:latest")
|
||||
slim_multi_refs+=("${IMAGE}:slim")
|
||||
fi
|
||||
fi
|
||||
if [[ ${#multi_refs[@]} -eq 0 || ${#amd64_refs[@]} -eq 0 || ${#arm64_refs[@]} -eq 0 ]]; then
|
||||
echo "::error::No Docker image refs resolved for ref ${SOURCE_REF}"
|
||||
exit 1
|
||||
fi
|
||||
{
|
||||
echo "multi<<EOF"
|
||||
printf "%s\n" "${multi_refs[@]}" "${slim_multi_refs[@]}"
|
||||
echo "EOF"
|
||||
echo "amd64<<EOF"
|
||||
printf "%s\n" "${amd64_refs[@]}"
|
||||
echo "EOF"
|
||||
echo "arm64<<EOF"
|
||||
printf "%s\n" "${arm64_refs[@]}"
|
||||
echo "EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Verify Docker attestations
|
||||
shell: bash
|
||||
env:
|
||||
MULTI_REFS: ${{ steps.refs.outputs.multi }}
|
||||
AMD64_REFS: ${{ steps.refs.outputs.amd64 }}
|
||||
ARM64_REFS: ${{ steps.refs.outputs.arm64 }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mapfile -t multi_refs <<< "${MULTI_REFS}"
|
||||
mapfile -t amd64_refs <<< "${AMD64_REFS}"
|
||||
mapfile -t arm64_refs <<< "${ARM64_REFS}"
|
||||
|
||||
node scripts/verify-docker-attestations.mjs \
|
||||
--platform linux/amd64 \
|
||||
--platform linux/arm64 \
|
||||
"${multi_refs[@]}"
|
||||
node scripts/verify-docker-attestations.mjs \
|
||||
--platform linux/amd64 \
|
||||
"${amd64_refs[@]}"
|
||||
node scripts/verify-docker-attestations.mjs \
|
||||
--platform linux/arm64 \
|
||||
"${arm64_refs[@]}"
|
||||
|
||||
391
.github/workflows/full-release-validation.yml
vendored
Normal file
391
.github/workflows/full-release-validation.yml
vendored
Normal file
@@ -0,0 +1,391 @@
|
||||
name: Full Release Validation
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
ref:
|
||||
description: Branch, tag, or full commit SHA to validate
|
||||
required: true
|
||||
default: main
|
||||
type: string
|
||||
workflow_ref:
|
||||
description: Trusted workflow ref used to run child workflows
|
||||
required: false
|
||||
default: main
|
||||
type: string
|
||||
provider:
|
||||
description: Provider lane for cross-OS onboarding and the end-to-end agent turn
|
||||
required: false
|
||||
default: openai
|
||||
type: choice
|
||||
options:
|
||||
- openai
|
||||
- anthropic
|
||||
- minimax
|
||||
mode:
|
||||
description: Which cross-OS release lanes to run
|
||||
required: false
|
||||
default: both
|
||||
type: choice
|
||||
options:
|
||||
- fresh
|
||||
- upgrade
|
||||
- both
|
||||
npm_telegram_package_spec:
|
||||
description: Optional published package spec for the post-publish Telegram E2E lane
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
npm_telegram_provider_mode:
|
||||
description: Provider mode for the optional post-publish Telegram E2E lane
|
||||
required: false
|
||||
default: mock-openai
|
||||
type: choice
|
||||
options:
|
||||
- mock-openai
|
||||
- live-frontier
|
||||
npm_telegram_scenario:
|
||||
description: Optional comma-separated Telegram scenario ids for the post-publish lane
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
actions: write
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: full-release-validation-${{ inputs.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
GH_REPO: ${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
resolve_target:
|
||||
name: Resolve target ref
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
outputs:
|
||||
sha: ${{ steps.resolve.outputs.sha }}
|
||||
steps:
|
||||
- name: Checkout target ref
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ inputs.ref }}
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
submodules: false
|
||||
|
||||
- name: Resolve target SHA
|
||||
id: resolve
|
||||
run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Summarize target
|
||||
env:
|
||||
TARGET_REF: ${{ inputs.ref }}
|
||||
TARGET_SHA: ${{ steps.resolve.outputs.sha }}
|
||||
WORKFLOW_REF: ${{ inputs.workflow_ref }}
|
||||
NPM_TELEGRAM_PACKAGE_SPEC: ${{ inputs.npm_telegram_package_spec }}
|
||||
run: |
|
||||
{
|
||||
echo "## Full release validation"
|
||||
echo
|
||||
echo "- Target ref: \`${TARGET_REF}\`"
|
||||
echo "- Target SHA: \`${TARGET_SHA}\`"
|
||||
echo "- Child workflow ref: \`${WORKFLOW_REF}\`"
|
||||
echo "- Normal CI: \`CI\` with \`target_ref=${TARGET_REF}\`"
|
||||
echo "- Release/live/Docker/package/QA: \`OpenClaw Release Checks\`"
|
||||
if [[ -n "${NPM_TELEGRAM_PACKAGE_SPEC// }" ]]; then
|
||||
echo "- Post-publish Telegram E2E: \`${NPM_TELEGRAM_PACKAGE_SPEC}\`"
|
||||
else
|
||||
echo "- Post-publish Telegram E2E: skipped because no published package spec was provided"
|
||||
fi
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
normal_ci:
|
||||
name: Run normal full CI
|
||||
needs: [resolve_target]
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 240
|
||||
steps:
|
||||
- name: Dispatch and monitor CI
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
TARGET_REF: ${{ inputs.ref }}
|
||||
TARGET_SHA: ${{ needs.resolve_target.outputs.sha }}
|
||||
WORKFLOW_REF: ${{ inputs.workflow_ref }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
dispatch_and_wait() {
|
||||
local workflow="$1"
|
||||
local workflow_ref="$2"
|
||||
shift 2
|
||||
|
||||
local before_json run_id status conclusion url
|
||||
before_json="$(gh run list --workflow "$workflow" --event workflow_dispatch --limit 100 --json databaseId --jq '[.[].databaseId]')"
|
||||
|
||||
gh workflow run "$workflow" --ref "$workflow_ref" "$@"
|
||||
|
||||
for _ in $(seq 1 60); do
|
||||
run_id="$(
|
||||
BEFORE_IDS="$before_json" gh run list --workflow "$workflow" --event workflow_dispatch --limit 50 --json databaseId,createdAt \
|
||||
--jq 'map(select(.databaseId as $id | (env.BEFORE_IDS | fromjson | index($id) | not))) | sort_by(.createdAt) | reverse | .[0].databaseId // empty'
|
||||
)"
|
||||
if [[ -n "$run_id" ]]; then
|
||||
break
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
|
||||
if [[ -z "${run_id:-}" ]]; then
|
||||
echo "Could not find dispatched run for ${workflow}." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Dispatched ${workflow}: https://github.com/${GITHUB_REPOSITORY}/actions/runs/${run_id}"
|
||||
|
||||
while true; do
|
||||
status="$(gh run view "$run_id" --json status --jq '.status')"
|
||||
if [[ "$status" == "completed" ]]; then
|
||||
break
|
||||
fi
|
||||
sleep 30
|
||||
done
|
||||
|
||||
conclusion="$(gh run view "$run_id" --json conclusion --jq '.conclusion')"
|
||||
url="$(gh run view "$run_id" --json url --jq '.url')"
|
||||
echo "${workflow} finished with ${conclusion}: ${url}"
|
||||
if [[ "$conclusion" != "success" ]]; then
|
||||
gh run view "$run_id" --json jobs --jq '.jobs[] | select(.conclusion != "success" and .conclusion != "skipped") | {name, conclusion, url}'
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
{
|
||||
echo "### Normal CI"
|
||||
echo
|
||||
echo "- Target ref: \`${TARGET_REF}\`"
|
||||
echo "- Target SHA: \`${TARGET_SHA}\`"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
dispatch_and_wait ci.yml "$WORKFLOW_REF" -f target_ref="$TARGET_REF"
|
||||
|
||||
release_checks:
|
||||
name: Run release/live/Docker/QA validation
|
||||
needs: [resolve_target]
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 720
|
||||
steps:
|
||||
- name: Dispatch and monitor release checks
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
TARGET_REF: ${{ inputs.ref }}
|
||||
TARGET_SHA: ${{ needs.resolve_target.outputs.sha }}
|
||||
WORKFLOW_REF: ${{ inputs.workflow_ref }}
|
||||
PROVIDER: ${{ inputs.provider }}
|
||||
MODE: ${{ inputs.mode }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
dispatch_and_wait() {
|
||||
local workflow="$1"
|
||||
local workflow_ref="$2"
|
||||
shift 2
|
||||
|
||||
local before_json run_id status conclusion url
|
||||
before_json="$(gh run list --workflow "$workflow" --event workflow_dispatch --limit 100 --json databaseId --jq '[.[].databaseId]')"
|
||||
|
||||
gh workflow run "$workflow" --ref "$workflow_ref" "$@"
|
||||
|
||||
for _ in $(seq 1 60); do
|
||||
run_id="$(
|
||||
BEFORE_IDS="$before_json" gh run list --workflow "$workflow" --event workflow_dispatch --limit 50 --json databaseId,createdAt \
|
||||
--jq 'map(select(.databaseId as $id | (env.BEFORE_IDS | fromjson | index($id) | not))) | sort_by(.createdAt) | reverse | .[0].databaseId // empty'
|
||||
)"
|
||||
if [[ -n "$run_id" ]]; then
|
||||
break
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
|
||||
if [[ -z "${run_id:-}" ]]; then
|
||||
echo "Could not find dispatched run for ${workflow}." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Dispatched ${workflow}: https://github.com/${GITHUB_REPOSITORY}/actions/runs/${run_id}"
|
||||
|
||||
while true; do
|
||||
status="$(gh run view "$run_id" --json status --jq '.status')"
|
||||
if [[ "$status" == "completed" ]]; then
|
||||
break
|
||||
fi
|
||||
sleep 60
|
||||
done
|
||||
|
||||
conclusion="$(gh run view "$run_id" --json conclusion --jq '.conclusion')"
|
||||
url="$(gh run view "$run_id" --json url --jq '.url')"
|
||||
echo "${workflow} finished with ${conclusion}: ${url}"
|
||||
if [[ "$conclusion" != "success" ]]; then
|
||||
gh run view "$run_id" --json jobs --jq '.jobs[] | select(.conclusion != "success" and .conclusion != "skipped") | {name, conclusion, url}'
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
{
|
||||
echo "### Release/live/Docker/QA validation"
|
||||
echo
|
||||
echo "- Target ref: \`${TARGET_REF}\`"
|
||||
echo "- Target SHA: \`${TARGET_SHA}\`"
|
||||
echo "- Provider: \`${PROVIDER}\`"
|
||||
echo "- Cross-OS mode: \`${MODE}\`"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
dispatch_and_wait openclaw-release-checks.yml "$WORKFLOW_REF" \
|
||||
-f ref="$TARGET_REF" \
|
||||
-f provider="$PROVIDER" \
|
||||
-f mode="$MODE"
|
||||
|
||||
npm_telegram:
|
||||
name: Run post-publish Telegram E2E
|
||||
needs: [resolve_target]
|
||||
if: inputs.npm_telegram_package_spec != ''
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 120
|
||||
steps:
|
||||
- name: Dispatch and monitor npm Telegram E2E
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
WORKFLOW_REF: ${{ inputs.workflow_ref }}
|
||||
PACKAGE_SPEC: ${{ inputs.npm_telegram_package_spec }}
|
||||
PROVIDER_MODE: ${{ inputs.npm_telegram_provider_mode }}
|
||||
SCENARIO: ${{ inputs.npm_telegram_scenario }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
before_json="$(gh run list --workflow npm-telegram-beta-e2e.yml --event workflow_dispatch --limit 100 --json databaseId --jq '[.[].databaseId]')"
|
||||
|
||||
args=(-f package_spec="$PACKAGE_SPEC" -f provider_mode="$PROVIDER_MODE")
|
||||
if [[ -n "${SCENARIO// }" ]]; then
|
||||
args+=(-f scenario="$SCENARIO")
|
||||
fi
|
||||
|
||||
gh workflow run npm-telegram-beta-e2e.yml --ref "$WORKFLOW_REF" "${args[@]}"
|
||||
|
||||
run_id=""
|
||||
for _ in $(seq 1 60); do
|
||||
run_id="$(
|
||||
BEFORE_IDS="$before_json" gh run list --workflow npm-telegram-beta-e2e.yml --event workflow_dispatch --limit 50 --json databaseId,createdAt \
|
||||
--jq 'map(select(.databaseId as $id | (env.BEFORE_IDS | fromjson | index($id) | not))) | sort_by(.createdAt) | reverse | .[0].databaseId // empty'
|
||||
)"
|
||||
if [[ -n "$run_id" ]]; then
|
||||
break
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
|
||||
if [[ -z "$run_id" ]]; then
|
||||
echo "Could not find dispatched run for npm-telegram-beta-e2e.yml." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Dispatched npm-telegram-beta-e2e.yml: https://github.com/${GITHUB_REPOSITORY}/actions/runs/${run_id}"
|
||||
|
||||
while true; do
|
||||
status="$(gh run view "$run_id" --json status --jq '.status')"
|
||||
if [[ "$status" == "completed" ]]; then
|
||||
break
|
||||
fi
|
||||
sleep 60
|
||||
done
|
||||
|
||||
conclusion="$(gh run view "$run_id" --json conclusion --jq '.conclusion')"
|
||||
url="$(gh run view "$run_id" --json url --jq '.url')"
|
||||
echo "npm-telegram-beta-e2e.yml finished with ${conclusion}: ${url}"
|
||||
if [[ "$conclusion" != "success" ]]; then
|
||||
gh run view "$run_id" --json jobs --jq '.jobs[] | select(.conclusion != "success" and .conclusion != "skipped") | {name, conclusion, url}'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
summary:
|
||||
name: Verify full validation
|
||||
needs: [normal_ci, release_checks, npm_telegram]
|
||||
if: always()
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Request private evidence update
|
||||
env:
|
||||
RELEASE_PRIVATE_DISPATCH_TOKEN: ${{ secrets.OPENCLAW_RELEASES_PRIVATE_DISPATCH_TOKEN }}
|
||||
TARGET_REF: ${{ inputs.ref }}
|
||||
PACKAGE_SPEC: ${{ inputs.npm_telegram_package_spec }}
|
||||
GITHUB_RUN_ID_VALUE: ${{ github.run_id }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ -z "${RELEASE_PRIVATE_DISPATCH_TOKEN// }" ]]; then
|
||||
echo "OPENCLAW_RELEASES_PRIVATE_DISPATCH_TOKEN is not configured; skipping automatic private evidence update."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
release_id="${TARGET_REF#refs/tags/}"
|
||||
release_id="${release_id#v}"
|
||||
if [[ "$PACKAGE_SPEC" =~ ^openclaw@(.+)$ ]]; then
|
||||
release_id="${BASH_REMATCH[1]}"
|
||||
fi
|
||||
release_id="$(printf '%s' "$release_id" | tr '/:@ ' '----' | tr -cd 'A-Za-z0-9._-')"
|
||||
if [[ -z "$release_id" ]]; then
|
||||
echo "::error::Could not derive release evidence id from target ref '${TARGET_REF}'."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
payload="$(
|
||||
jq -cn \
|
||||
--arg full_validation_run_id "$GITHUB_RUN_ID_VALUE" \
|
||||
--arg release_id "$release_id" \
|
||||
--arg release_ref "$TARGET_REF" \
|
||||
--arg package_spec "$PACKAGE_SPEC" \
|
||||
--arg notes "Automatically requested by Full Release Validation ${GITHUB_RUN_ID_VALUE} after child workflows completed." \
|
||||
'{
|
||||
event_type: "openclaw_full_release_validation_completed",
|
||||
client_payload: {
|
||||
full_validation_run_id: $full_validation_run_id,
|
||||
release_id: $release_id,
|
||||
release_ref: $release_ref,
|
||||
package_spec: $package_spec,
|
||||
notes: $notes
|
||||
}
|
||||
}'
|
||||
)"
|
||||
|
||||
curl --fail-with-body \
|
||||
-X POST \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "Authorization: Bearer ${RELEASE_PRIVATE_DISPATCH_TOKEN}" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
https://api.github.com/repos/openclaw/releases-private/dispatches \
|
||||
-d "$payload"
|
||||
|
||||
- name: Verify child workflow results
|
||||
env:
|
||||
NORMAL_CI_RESULT: ${{ needs.normal_ci.result }}
|
||||
RELEASE_CHECKS_RESULT: ${{ needs.release_checks.result }}
|
||||
NPM_TELEGRAM_RESULT: ${{ needs.npm_telegram.result }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
failed=0
|
||||
for item in \
|
||||
"normal_ci=${NORMAL_CI_RESULT}" \
|
||||
"release_checks=${RELEASE_CHECKS_RESULT}" \
|
||||
"npm_telegram=${NPM_TELEGRAM_RESULT}"
|
||||
do
|
||||
name="${item%%=*}"
|
||||
result="${item#*=}"
|
||||
if [[ "$result" != "success" && "$result" != "skipped" ]]; then
|
||||
echo "::error::${name} ended with ${result}"
|
||||
failed=1
|
||||
fi
|
||||
done
|
||||
exit "$failed"
|
||||
14
.github/workflows/install-smoke.yml
vendored
14
.github/workflows/install-smoke.yml
vendored
@@ -10,6 +10,11 @@ on:
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
update_baseline_version:
|
||||
description: Baseline openclaw version or dist-tag for installer update smoke
|
||||
required: false
|
||||
default: latest
|
||||
type: string
|
||||
workflow_call:
|
||||
inputs:
|
||||
ref:
|
||||
@@ -21,6 +26,11 @@ on:
|
||||
required: false
|
||||
default: true
|
||||
type: boolean
|
||||
update_baseline_version:
|
||||
description: Baseline openclaw version or dist-tag for installer update smoke
|
||||
required: false
|
||||
default: latest
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -103,7 +113,6 @@ jobs:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
build-args: |
|
||||
OPENCLAW_DOCKER_APT_UPGRADE=0
|
||||
OPENCLAW_EXTENSIONS=matrix
|
||||
tags: |
|
||||
openclaw-dockerfile-smoke:local
|
||||
@@ -218,7 +227,6 @@ jobs:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
build-args: |
|
||||
OPENCLAW_DOCKER_APT_UPGRADE=0
|
||||
OPENCLAW_EXTENSIONS=matrix
|
||||
tags: |
|
||||
openclaw-dockerfile-smoke:local
|
||||
@@ -332,7 +340,7 @@ jobs:
|
||||
OPENCLAW_INSTALL_SMOKE_SKIP_NONROOT: "0"
|
||||
OPENCLAW_INSTALL_SMOKE_SKIP_NPM_GLOBAL: "1"
|
||||
OPENCLAW_INSTALL_SMOKE_SKIP_PREVIOUS: "1"
|
||||
OPENCLAW_INSTALL_SMOKE_UPDATE_BASELINE: latest
|
||||
OPENCLAW_INSTALL_SMOKE_UPDATE_BASELINE: ${{ inputs.update_baseline_version || 'latest' }}
|
||||
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
|
||||
|
||||
176
.github/workflows/npm-telegram-beta-e2e.yml
vendored
176
.github/workflows/npm-telegram-beta-e2e.yml
vendored
@@ -4,10 +4,20 @@ on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
package_spec:
|
||||
description: Published OpenClaw package spec to test
|
||||
description: Published OpenClaw package spec to test when no artifact is supplied
|
||||
required: true
|
||||
default: openclaw@beta
|
||||
type: string
|
||||
package_label:
|
||||
description: Optional display label for an artifact-backed package candidate
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
package_artifact_name:
|
||||
description: Advanced package-under-test artifact name; leave blank for registry install
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
provider_mode:
|
||||
description: QA provider mode
|
||||
required: true
|
||||
@@ -20,6 +30,39 @@ on:
|
||||
description: Optional comma-separated Telegram scenario ids
|
||||
required: false
|
||||
type: string
|
||||
workflow_call:
|
||||
inputs:
|
||||
package_spec:
|
||||
description: Published OpenClaw package spec to test when no artifact is supplied
|
||||
required: true
|
||||
type: string
|
||||
package_artifact_name:
|
||||
description: Optional package-under-test artifact from the current workflow run
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
package_label:
|
||||
description: Optional display label for an artifact-backed package candidate
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
provider_mode:
|
||||
description: QA provider mode
|
||||
required: false
|
||||
default: mock-openai
|
||||
type: string
|
||||
scenario:
|
||||
description: Optional comma-separated Telegram scenario ids
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
secrets:
|
||||
OPENAI_API_KEY:
|
||||
required: false
|
||||
OPENCLAW_QA_CONVEX_SITE_URL:
|
||||
required: false
|
||||
OPENCLAW_QA_CONVEX_SECRET_CI:
|
||||
required: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -34,106 +77,39 @@ env:
|
||||
PNPM_VERSION: "10.33.0"
|
||||
|
||||
jobs:
|
||||
validate_dispatch_ref:
|
||||
name: Validate dispatch ref
|
||||
runs-on: blacksmith-8vcpu-ubuntu-2404
|
||||
steps:
|
||||
- name: Require main workflow ref
|
||||
env:
|
||||
WORKFLOW_REF: ${{ github.ref }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ "${WORKFLOW_REF}" != "refs/heads/main" ]]; then
|
||||
echo "NPM Telegram beta E2E must be dispatched from main so workflow logic stays controlled." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
approve_release_manager:
|
||||
name: Approve npm Telegram beta E2E
|
||||
needs: validate_dispatch_ref
|
||||
runs-on: ubuntu-latest
|
||||
environment: npm-release
|
||||
steps:
|
||||
- name: Record approval
|
||||
env:
|
||||
PACKAGE_SPEC: ${{ inputs.package_spec }}
|
||||
run: echo "Approved npm Telegram beta E2E for ${PACKAGE_SPEC}"
|
||||
|
||||
prepare_docker_e2e_image:
|
||||
name: Prepare Docker E2E image
|
||||
needs: validate_dispatch_ref
|
||||
run_package_telegram_e2e:
|
||||
name: Run package Telegram E2E
|
||||
runs-on: blacksmith-32vcpu-ubuntu-2404
|
||||
timeout-minutes: 90
|
||||
timeout-minutes: 60
|
||||
environment: qa-live-shared
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
outputs:
|
||||
image: ${{ steps.image.outputs.image }}
|
||||
env:
|
||||
DOCKER_BUILD_SUMMARY: "false"
|
||||
DOCKER_BUILD_RECORD_UPLOAD: "false"
|
||||
steps:
|
||||
- name: Checkout main
|
||||
- name: Checkout dispatch ref
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ github.sha }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Resolve Docker E2E image tag
|
||||
id: image
|
||||
shell: bash
|
||||
env:
|
||||
SELECTED_SHA: ${{ github.sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
repository="${GITHUB_REPOSITORY,,}"
|
||||
image="ghcr.io/${repository}-docker-e2e:${SELECTED_SHA}"
|
||||
echo "image=$image" >> "$GITHUB_OUTPUT"
|
||||
echo "Docker E2E image: \`$image\`" >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Set up Blacksmith Docker Builder
|
||||
uses: useblacksmith/setup-docker-builder@ac083cc84672d01c60d5e8561d0a939b697de542 # v1
|
||||
|
||||
- name: Log in to GHCR
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ github.token }}
|
||||
max-cache-size-mb: 800000
|
||||
|
||||
- name: Build and push Docker E2E image
|
||||
- name: Build Docker E2E image
|
||||
uses: useblacksmith/build-push-action@cbd1f60d194a98cb3be5523b15134501eaf0fbf3 # v2
|
||||
with:
|
||||
context: .
|
||||
file: ./scripts/e2e/Dockerfile
|
||||
target: build
|
||||
platforms: linux/amd64
|
||||
tags: ${{ steps.image.outputs.image }}
|
||||
tags: openclaw-docker-e2e:local
|
||||
load: true
|
||||
push: false
|
||||
provenance: false
|
||||
push: true
|
||||
|
||||
run_npm_telegram_beta_e2e:
|
||||
name: Run published npm Telegram E2E
|
||||
needs: [approve_release_manager, prepare_docker_e2e_image]
|
||||
runs-on: blacksmith-32vcpu-ubuntu-2404
|
||||
timeout-minutes: 60
|
||||
environment: qa-live-shared
|
||||
permissions:
|
||||
contents: read
|
||||
packages: read
|
||||
steps:
|
||||
- name: Checkout main
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ github.sha }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Log in to GHCR
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ github.token }}
|
||||
|
||||
- name: Setup Node environment
|
||||
uses: ./.github/actions/setup-node-env
|
||||
@@ -145,6 +121,7 @@ jobs:
|
||||
- name: Validate inputs and secrets
|
||||
env:
|
||||
PACKAGE_SPEC: ${{ inputs.package_spec }}
|
||||
PACKAGE_ARTIFACT_NAME: ${{ inputs.package_artifact_name || '' }}
|
||||
PROVIDER_MODE: ${{ inputs.provider_mode }}
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
OPENCLAW_QA_CONVEX_SITE_URL: ${{ secrets.OPENCLAW_QA_CONVEX_SITE_URL }}
|
||||
@@ -153,10 +130,19 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if [[ ! "${PACKAGE_SPEC}" =~ ^openclaw@(beta|latest|[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*(-[1-9][0-9]*|-beta\.[1-9][0-9]*)?)$ ]]; then
|
||||
echo "package_spec must be openclaw@beta, openclaw@latest, or an exact OpenClaw release version; got: ${PACKAGE_SPEC}" >&2
|
||||
exit 1
|
||||
if [[ -z "${PACKAGE_ARTIFACT_NAME// }" ]]; then
|
||||
if [[ ! "${PACKAGE_SPEC}" =~ ^openclaw@(beta|latest|[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*(-[1-9][0-9]*|-beta\.[1-9][0-9]*)?)$ ]]; then
|
||||
echo "package_spec must be openclaw@beta, openclaw@latest, or an exact OpenClaw release version; got: ${PACKAGE_SPEC}" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
case "${PROVIDER_MODE}" in
|
||||
mock-openai | live-frontier) ;;
|
||||
*)
|
||||
echo "provider_mode must be mock-openai or live-frontier; got: ${PROVIDER_MODE}" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
require_var() {
|
||||
local key="$1"
|
||||
@@ -172,21 +158,31 @@ jobs:
|
||||
require_var OPENAI_API_KEY
|
||||
fi
|
||||
|
||||
- name: Run npm Telegram beta E2E
|
||||
- name: Download package-under-test artifact
|
||||
if: inputs.package_artifact_name != ''
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: ${{ inputs.package_artifact_name }}
|
||||
path: .artifacts/telegram-package-under-test
|
||||
|
||||
- name: Run package Telegram E2E
|
||||
id: run_lane
|
||||
shell: bash
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
OPENCLAW_SKIP_DOCKER_BUILD: "1"
|
||||
OPENCLAW_DOCKER_E2E_IMAGE: ${{ needs.prepare_docker_e2e_image.outputs.image }}
|
||||
OPENCLAW_DOCKER_E2E_IMAGE: openclaw-docker-e2e:local
|
||||
OPENCLAW_NPM_TELEGRAM_PACKAGE_SPEC: ${{ inputs.package_spec }}
|
||||
OPENCLAW_NPM_TELEGRAM_PACKAGE_LABEL: ${{ inputs.package_label }}
|
||||
OPENCLAW_NPM_TELEGRAM_PROVIDER_MODE: ${{ inputs.provider_mode }}
|
||||
OPENCLAW_NPM_TELEGRAM_CREDENTIAL_SOURCE: convex
|
||||
OPENCLAW_NPM_TELEGRAM_CREDENTIAL_ROLE: ci
|
||||
OPENCLAW_QA_CONVEX_SITE_URL: ${{ secrets.OPENCLAW_QA_CONVEX_SITE_URL }}
|
||||
OPENCLAW_QA_CONVEX_SECRET_CI: ${{ secrets.OPENCLAW_QA_CONVEX_SECRET_CI }}
|
||||
OPENCLAW_QA_REDACT_PUBLIC_METADATA: "1"
|
||||
OPENCLAW_QA_TELEGRAM_CAPTURE_CONTENT: "1"
|
||||
INPUT_SCENARIO: ${{ inputs.scenario }}
|
||||
PACKAGE_ARTIFACT_NAME: ${{ inputs.package_artifact_name || '' }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
@@ -194,6 +190,20 @@ jobs:
|
||||
echo "output_dir=${output_dir}" >> "$GITHUB_OUTPUT"
|
||||
export OPENCLAW_NPM_TELEGRAM_OUTPUT_DIR="${output_dir}"
|
||||
|
||||
if [[ -n "${PACKAGE_ARTIFACT_NAME// }" ]]; then
|
||||
mapfile -t package_tgzs < <(find .artifacts/telegram-package-under-test -type f -name "*.tgz" | sort)
|
||||
if [[ "${#package_tgzs[@]}" -ne 1 ]]; then
|
||||
echo "package artifact ${PACKAGE_ARTIFACT_NAME} must contain exactly one .tgz; found ${#package_tgzs[@]}" >&2
|
||||
exit 1
|
||||
fi
|
||||
export OPENCLAW_NPM_TELEGRAM_PACKAGE_TGZ="${package_tgzs[0]}"
|
||||
if [[ -z "${OPENCLAW_NPM_TELEGRAM_PACKAGE_LABEL// }" ]]; then
|
||||
export OPENCLAW_NPM_TELEGRAM_PACKAGE_LABEL="$(basename "${package_tgzs[0]}")"
|
||||
fi
|
||||
elif [[ -z "${OPENCLAW_NPM_TELEGRAM_PACKAGE_LABEL// }" ]]; then
|
||||
export OPENCLAW_NPM_TELEGRAM_PACKAGE_LABEL="${OPENCLAW_NPM_TELEGRAM_PACKAGE_SPEC}"
|
||||
fi
|
||||
|
||||
if [[ -n "${INPUT_SCENARIO// }" ]]; then
|
||||
export OPENCLAW_NPM_TELEGRAM_SCENARIOS="${INPUT_SCENARIO}"
|
||||
fi
|
||||
|
||||
@@ -23,6 +23,31 @@ on:
|
||||
required: false
|
||||
default: true
|
||||
type: boolean
|
||||
docker_lanes:
|
||||
description: Comma/space separated Docker scheduler lane names to run against the prepared image
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
package_artifact_name:
|
||||
description: Existing workflow artifact containing openclaw-current.tgz; blank packs the selected ref
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
package_artifact_run_id:
|
||||
description: Prior run id containing package_artifact_name; blank uses this run or packs the selected ref
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
docker_e2e_bare_image:
|
||||
description: Existing bare Docker E2E image to reuse; blank derives from package SHA/ref
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
docker_e2e_functional_image:
|
||||
description: Existing functional Docker E2E image to reuse; blank derives from package SHA/ref
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
include_live_suites:
|
||||
description: Whether to run live-provider coverage
|
||||
required: false
|
||||
@@ -33,6 +58,11 @@ on:
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
live_model_providers:
|
||||
description: Comma/space separated provider ids for the Docker live model matrix; blank runs all providers
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
workflow_call:
|
||||
inputs:
|
||||
ref:
|
||||
@@ -54,6 +84,31 @@ on:
|
||||
required: false
|
||||
default: true
|
||||
type: boolean
|
||||
docker_lanes:
|
||||
description: Comma/space separated Docker scheduler lane names to run against the prepared image
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
package_artifact_name:
|
||||
description: Existing workflow artifact containing openclaw-current.tgz; blank packs the selected ref
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
package_artifact_run_id:
|
||||
description: Prior run id containing package_artifact_name; blank uses this run or packs the selected ref
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
docker_e2e_bare_image:
|
||||
description: Existing bare Docker E2E image to reuse; blank derives from package SHA/ref
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
docker_e2e_functional_image:
|
||||
description: Existing functional Docker E2E image to reuse; blank derives from package SHA/ref
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
include_live_suites:
|
||||
description: Whether to run live-provider coverage
|
||||
required: false
|
||||
@@ -64,6 +119,11 @@ on:
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
live_model_providers:
|
||||
description: Comma/space separated provider ids for the Docker live model matrix; blank runs all providers
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
secrets:
|
||||
OPENAI_API_KEY:
|
||||
required: false
|
||||
@@ -180,7 +240,6 @@ jobs:
|
||||
- name: Validate selected ref
|
||||
id: validate
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
INPUT_REF: ${{ inputs.ref }}
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -188,27 +247,22 @@ jobs:
|
||||
selected_sha="$(git rev-parse HEAD)"
|
||||
trusted_reason=""
|
||||
|
||||
git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main
|
||||
git fetch --no-tags origin '+refs/heads/*:refs/remotes/origin/*'
|
||||
git fetch --tags origin '+refs/tags/*:refs/tags/*'
|
||||
|
||||
if git merge-base --is-ancestor "$selected_sha" refs/remotes/origin/main; then
|
||||
trusted_reason="main-ancestor"
|
||||
elif git tag --points-at "$selected_sha" | grep -Eq '^v'; then
|
||||
trusted_reason="release-tag"
|
||||
elif git for-each-ref --format='%(refname:short)' --contains "$selected_sha" refs/remotes/origin | grep -Eq '^origin/'; then
|
||||
trusted_reason="repository-branch-history"
|
||||
else
|
||||
pr_head_count="$(
|
||||
gh api \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
"repos/${GITHUB_REPOSITORY}/commits/${selected_sha}/pulls" \
|
||||
--jq '[.[] | select(.state == "open" and .head.repo.full_name == "'"${GITHUB_REPOSITORY}"'" and .head.sha == "'"${selected_sha}"'")] | length'
|
||||
)"
|
||||
if [[ "$pr_head_count" != "0" ]]; then
|
||||
trusted_reason="open-pr-head"
|
||||
fi
|
||||
trusted_reason=""
|
||||
fi
|
||||
|
||||
if [[ -z "$trusted_reason" ]]; then
|
||||
echo "Ref '${INPUT_REF}' resolved to $selected_sha, which is not trusted for secret-bearing live/E2E checks." >&2
|
||||
echo "Allowed refs must be on main, point to a release tag, or match an open PR head in ${GITHUB_REPOSITORY}." >&2
|
||||
echo "Allowed refs must be reachable from an OpenClaw branch or release tag." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -303,7 +357,7 @@ jobs:
|
||||
requires_live_suites: false
|
||||
- suite_id: openai-ws-stream-live-e2e
|
||||
label: OpenAI WebSocket live E2E
|
||||
command: pnpm test:e2e -- src/agents/openai-ws-stream.e2e.test.ts
|
||||
command: pnpm test:e2e src/agents/openai-ws-stream.e2e.test.ts
|
||||
timeout_minutes: 90
|
||||
requires_repo_e2e: false
|
||||
requires_live_suites: true
|
||||
@@ -363,88 +417,23 @@ jobs:
|
||||
|
||||
validate_docker_e2e:
|
||||
needs: [validate_selected_ref, prepare_docker_e2e_image]
|
||||
if: inputs.include_release_path_suites
|
||||
if: inputs.include_release_path_suites && inputs.docker_lanes == ''
|
||||
name: Docker E2E (${{ matrix.label }})
|
||||
runs-on: blacksmith-32vcpu-ubuntu-2404
|
||||
timeout-minutes: ${{ matrix.timeout_minutes }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- suite_id: docker-onboard
|
||||
label: Onboarding Docker E2E
|
||||
command: pnpm test:docker:onboard
|
||||
timeout_minutes: 60
|
||||
release_path: true
|
||||
- suite_id: docker-npm-onboard-channel-agent
|
||||
label: Npm Onboard Channel Agent Docker E2E
|
||||
command: pnpm test:docker:npm-onboard-channel-agent
|
||||
timeout_minutes: 90
|
||||
release_path: true
|
||||
- suite_id: docker-gateway-network
|
||||
label: Gateway Network Docker E2E
|
||||
command: pnpm test:docker:gateway-network
|
||||
timeout_minutes: 60
|
||||
release_path: true
|
||||
- suite_id: docker-openai-web-search-minimal
|
||||
label: OpenAI Web Search Minimal Docker E2E
|
||||
command: pnpm test:docker:openai-web-search-minimal
|
||||
timeout_minutes: 60
|
||||
release_path: true
|
||||
- suite_id: docker-mcp-channels
|
||||
label: MCP Channels Docker E2E
|
||||
command: pnpm test:docker:mcp-channels
|
||||
timeout_minutes: 60
|
||||
release_path: true
|
||||
- suite_id: docker-pi-bundle-mcp-tools
|
||||
label: Pi Bundle MCP Tools Docker E2E
|
||||
command: pnpm test:docker:pi-bundle-mcp-tools
|
||||
timeout_minutes: 60
|
||||
release_path: true
|
||||
- suite_id: docker-cron-mcp-cleanup
|
||||
label: Cron MCP Cleanup Docker E2E
|
||||
command: pnpm test:docker:cron-mcp-cleanup
|
||||
timeout_minutes: 60
|
||||
release_path: true
|
||||
- suite_id: docker-plugins
|
||||
label: Plugins Docker E2E
|
||||
command: pnpm test:docker:plugins
|
||||
timeout_minutes: 75
|
||||
release_path: true
|
||||
- suite_id: docker-plugin-update
|
||||
label: Plugin Update Docker E2E
|
||||
command: pnpm test:docker:plugin-update
|
||||
timeout_minutes: 60
|
||||
release_path: true
|
||||
- suite_id: docker-config-reload
|
||||
label: Config Reload Docker E2E
|
||||
command: pnpm test:docker:config-reload
|
||||
timeout_minutes: 60
|
||||
release_path: true
|
||||
- suite_id: docker-bundled-channel-deps
|
||||
label: Bundled Channel Runtime Deps Docker E2E
|
||||
command: pnpm test:docker:bundled-channel-deps
|
||||
timeout_minutes: 75
|
||||
release_path: true
|
||||
- suite_id: docker-doctor-switch
|
||||
label: Doctor Install Switch Docker E2E
|
||||
command: pnpm test:docker:doctor-switch
|
||||
timeout_minutes: 60
|
||||
release_path: true
|
||||
- suite_id: docker-session-runtime-context
|
||||
label: Session Runtime Context Docker E2E
|
||||
command: pnpm test:docker:session-runtime-context
|
||||
timeout_minutes: 60
|
||||
release_path: true
|
||||
- suite_id: docker-qr
|
||||
label: QR Import Docker E2E
|
||||
command: pnpm test:docker:qr
|
||||
timeout_minutes: 60
|
||||
release_path: true
|
||||
- suite_id: docker-install-e2e
|
||||
label: Installer Docker E2E
|
||||
command: pnpm test:install:e2e
|
||||
- chunk_id: core
|
||||
label: core
|
||||
timeout_minutes: 120
|
||||
release_path: true
|
||||
- chunk_id: package-update
|
||||
label: package/update
|
||||
timeout_minutes: 180
|
||||
- chunk_id: plugins-integrations
|
||||
label: plugins/integrations
|
||||
timeout_minutes: 180
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}
|
||||
@@ -491,7 +480,13 @@ jobs:
|
||||
OPENCLAW_GEMINI_SETTINGS_JSON: ${{ secrets.OPENCLAW_GEMINI_SETTINGS_JSON }}
|
||||
FIREWORKS_API_KEY: ${{ secrets.FIREWORKS_API_KEY }}
|
||||
OPENCLAW_DOCKER_E2E_IMAGE: ${{ needs.prepare_docker_e2e_image.outputs.image }}
|
||||
OPENCLAW_DOCKER_E2E_BARE_IMAGE: ${{ needs.prepare_docker_e2e_image.outputs.bare_image }}
|
||||
OPENCLAW_DOCKER_E2E_FUNCTIONAL_IMAGE: ${{ needs.prepare_docker_e2e_image.outputs.functional_image }}
|
||||
OPENCLAW_DOCKER_E2E_PACKAGE_ARTIFACT_NAME: ${{ inputs.package_artifact_name || 'docker-e2e-package' }}
|
||||
OPENCLAW_CURRENT_PACKAGE_TGZ: .artifacts/docker-e2e-package/openclaw-current.tgz
|
||||
OPENCLAW_SKIP_DOCKER_BUILD: "1"
|
||||
INCLUDE_OPENWEBUI: ${{ inputs.include_openwebui }}
|
||||
DOCKER_E2E_CHUNK: ${{ matrix.chunk_id }}
|
||||
steps:
|
||||
- name: Checkout selected ref
|
||||
uses: actions/checkout@v6
|
||||
@@ -516,45 +511,194 @@ jobs:
|
||||
- name: Hydrate live auth/profile inputs
|
||||
run: bash scripts/ci-hydrate-live-auth.sh
|
||||
|
||||
- name: Configure suite-specific env
|
||||
- name: Plan and hydrate Docker E2E chunk
|
||||
id: plan
|
||||
uses: ./.github/actions/docker-e2e-plan
|
||||
with:
|
||||
mode: chunk
|
||||
chunk: ${{ matrix.chunk_id }}
|
||||
include-openwebui: ${{ inputs.include_openwebui }}
|
||||
package-artifact-name: ${{ inputs.package_artifact_name || 'docker-e2e-package' }}
|
||||
|
||||
- name: Run Docker E2E chunk
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
case "${{ matrix.suite_id }}" in
|
||||
docker-install-e2e)
|
||||
echo "OPENCLAW_E2E_MODELS=both" >> "$GITHUB_ENV"
|
||||
;;
|
||||
esac
|
||||
export OPENCLAW_DOCKER_ALL_PROFILE=release-path
|
||||
export OPENCLAW_DOCKER_ALL_CHUNK="${DOCKER_E2E_CHUNK}"
|
||||
export OPENCLAW_DOCKER_ALL_BUILD=0
|
||||
export OPENCLAW_DOCKER_ALL_PREFLIGHT=0
|
||||
export OPENCLAW_DOCKER_ALL_FAIL_FAST=0
|
||||
export OPENCLAW_DOCKER_ALL_INCLUDE_OPENWEBUI="${INCLUDE_OPENWEBUI}"
|
||||
export OPENCLAW_DOCKER_ALL_LOG_DIR=".artifacts/docker-tests/release-${DOCKER_E2E_CHUNK}"
|
||||
export OPENCLAW_DOCKER_ALL_TIMINGS_FILE=".artifacts/docker-tests/release-${DOCKER_E2E_CHUNK}-timings.json"
|
||||
export OPENCLAW_DOCKER_ALL_PNPM_COMMAND="$(command -v pnpm)"
|
||||
|
||||
- name: Validate suite credentials
|
||||
pnpm test:docker:all
|
||||
|
||||
- name: Summarize Docker E2E chunk
|
||||
if: always()
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
case "${{ matrix.suite_id }}" in
|
||||
docker-install-e2e)
|
||||
[[ -n "${OPENAI_API_KEY:-}" ]] || {
|
||||
echo "OPENAI_API_KEY is required for installer Docker E2E." >&2
|
||||
exit 1
|
||||
}
|
||||
if [[ -z "${ANTHROPIC_API_TOKEN:-}" && -z "${ANTHROPIC_API_KEY:-}" ]]; then
|
||||
echo "ANTHROPIC_API_TOKEN or ANTHROPIC_API_KEY is required for installer Docker E2E." >&2
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
summary=".artifacts/docker-tests/release-${DOCKER_E2E_CHUNK}/summary.json"
|
||||
if [[ ! -f "$summary" ]]; then
|
||||
echo "Docker chunk summary missing: \`$summary\`" >> "$GITHUB_STEP_SUMMARY"
|
||||
exit 0
|
||||
fi
|
||||
node scripts/docker-e2e.mjs summary "$summary" "Docker E2E chunk: ${DOCKER_E2E_CHUNK:-unknown}" >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Run ${{ matrix.label }}
|
||||
run: ${{ matrix.command }}
|
||||
- name: Upload Docker E2E chunk artifacts
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: docker-e2e-${{ matrix.chunk_id }}
|
||||
path: .artifacts/docker-tests/
|
||||
if-no-files-found: ignore
|
||||
|
||||
validate_docker_lanes:
|
||||
needs: [validate_selected_ref, prepare_docker_e2e_image]
|
||||
if: inputs.docker_lanes != ''
|
||||
name: Docker E2E targeted lanes
|
||||
runs-on: blacksmith-32vcpu-ubuntu-2404
|
||||
timeout-minutes: 180
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}
|
||||
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }}
|
||||
ANTHROPIC_API_KEY_OLD: ${{ secrets.ANTHROPIC_API_KEY_OLD }}
|
||||
BYTEPLUS_API_KEY: ${{ secrets.BYTEPLUS_API_KEY }}
|
||||
CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}
|
||||
DASHSCOPE_API_KEY: ${{ secrets.DASHSCOPE_API_KEY }}
|
||||
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
|
||||
KIMI_API_KEY: ${{ secrets.KIMI_API_KEY }}
|
||||
MODELSTUDIO_API_KEY: ${{ secrets.MODELSTUDIO_API_KEY }}
|
||||
MOONSHOT_API_KEY: ${{ secrets.MOONSHOT_API_KEY }}
|
||||
MISTRAL_API_KEY: ${{ secrets.MISTRAL_API_KEY }}
|
||||
MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }}
|
||||
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
||||
OPENCODE_ZEN_API_KEY: ${{ secrets.OPENCODE_ZEN_API_KEY }}
|
||||
OPENCLAW_LIVE_BROWSER_CDP_URL: ${{ secrets.OPENCLAW_LIVE_BROWSER_CDP_URL }}
|
||||
OPENCLAW_LIVE_SETUP_TOKEN: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN }}
|
||||
OPENCLAW_LIVE_SETUP_TOKEN_MODEL: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN_MODEL }}
|
||||
OPENCLAW_LIVE_SETUP_TOKEN_PROFILE: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN_PROFILE }}
|
||||
OPENCLAW_LIVE_SETUP_TOKEN_VALUE: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN_VALUE }}
|
||||
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
|
||||
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
|
||||
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
|
||||
QWEN_API_KEY: ${{ secrets.QWEN_API_KEY }}
|
||||
FAL_KEY: ${{ secrets.FAL_KEY }}
|
||||
RUNWAY_API_KEY: ${{ secrets.RUNWAY_API_KEY }}
|
||||
DEEPGRAM_API_KEY: ${{ secrets.DEEPGRAM_API_KEY }}
|
||||
TOGETHER_API_KEY: ${{ secrets.TOGETHER_API_KEY }}
|
||||
VYDRA_API_KEY: ${{ secrets.VYDRA_API_KEY }}
|
||||
XAI_API_KEY: ${{ secrets.XAI_API_KEY }}
|
||||
ZAI_API_KEY: ${{ secrets.ZAI_API_KEY }}
|
||||
Z_AI_API_KEY: ${{ secrets.Z_AI_API_KEY }}
|
||||
BYTEPLUS_ACCESS_KEY_ID: ${{ secrets.BYTEPLUS_ACCESS_KEY_ID }}
|
||||
BYTEPLUS_SECRET_ACCESS_KEY: ${{ secrets.BYTEPLUS_SECRET_ACCESS_KEY }}
|
||||
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
OPENCLAW_CODEX_AUTH_JSON: ${{ secrets.OPENCLAW_CODEX_AUTH_JSON }}
|
||||
OPENCLAW_CODEX_CONFIG_TOML: ${{ secrets.OPENCLAW_CODEX_CONFIG_TOML }}
|
||||
OPENCLAW_CLAUDE_JSON: ${{ secrets.OPENCLAW_CLAUDE_JSON }}
|
||||
OPENCLAW_CLAUDE_CREDENTIALS_JSON: ${{ secrets.OPENCLAW_CLAUDE_CREDENTIALS_JSON }}
|
||||
OPENCLAW_CLAUDE_SETTINGS_JSON: ${{ secrets.OPENCLAW_CLAUDE_SETTINGS_JSON }}
|
||||
OPENCLAW_CLAUDE_SETTINGS_LOCAL_JSON: ${{ secrets.OPENCLAW_CLAUDE_SETTINGS_LOCAL_JSON }}
|
||||
OPENCLAW_GEMINI_SETTINGS_JSON: ${{ secrets.OPENCLAW_GEMINI_SETTINGS_JSON }}
|
||||
FIREWORKS_API_KEY: ${{ secrets.FIREWORKS_API_KEY }}
|
||||
OPENCLAW_DOCKER_E2E_IMAGE: ${{ needs.prepare_docker_e2e_image.outputs.image }}
|
||||
OPENCLAW_DOCKER_E2E_BARE_IMAGE: ${{ needs.prepare_docker_e2e_image.outputs.bare_image }}
|
||||
OPENCLAW_DOCKER_E2E_FUNCTIONAL_IMAGE: ${{ needs.prepare_docker_e2e_image.outputs.functional_image }}
|
||||
OPENCLAW_DOCKER_E2E_PACKAGE_ARTIFACT_NAME: ${{ inputs.package_artifact_name || 'docker-e2e-package' }}
|
||||
OPENCLAW_CURRENT_PACKAGE_TGZ: .artifacts/docker-e2e-package/openclaw-current.tgz
|
||||
OPENCLAW_SKIP_DOCKER_BUILD: "1"
|
||||
INCLUDE_OPENWEBUI: ${{ inputs.include_openwebui }}
|
||||
DOCKER_E2E_LANES: ${{ inputs.docker_lanes }}
|
||||
steps:
|
||||
- name: Checkout selected ref
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ needs.validate_selected_ref.outputs.selected_sha }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Log in to GHCR for shared Docker E2E image
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ github.token }}
|
||||
|
||||
- name: Setup Node environment
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
pnpm-version: ${{ env.PNPM_VERSION }}
|
||||
install-bun: "true"
|
||||
|
||||
- name: Hydrate live auth/profile inputs
|
||||
run: bash scripts/ci-hydrate-live-auth.sh
|
||||
|
||||
- name: Plan and hydrate targeted Docker E2E lanes
|
||||
id: plan
|
||||
uses: ./.github/actions/docker-e2e-plan
|
||||
with:
|
||||
mode: targeted
|
||||
lanes: ${{ inputs.docker_lanes }}
|
||||
include-openwebui: ${{ inputs.include_openwebui }}
|
||||
package-artifact-name: ${{ inputs.package_artifact_name || 'docker-e2e-package' }}
|
||||
|
||||
- name: Run targeted Docker E2E lanes
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
export OPENCLAW_DOCKER_ALL_LANES="${DOCKER_E2E_LANES}"
|
||||
export OPENCLAW_DOCKER_ALL_PREFLIGHT=0
|
||||
export OPENCLAW_DOCKER_ALL_FAIL_FAST=0
|
||||
export OPENCLAW_DOCKER_ALL_INCLUDE_OPENWEBUI="${INCLUDE_OPENWEBUI}"
|
||||
export OPENCLAW_DOCKER_ALL_LOG_DIR=".artifacts/docker-tests/targeted"
|
||||
export OPENCLAW_DOCKER_ALL_TIMINGS_FILE=".artifacts/docker-tests/targeted-timings.json"
|
||||
export OPENCLAW_DOCKER_ALL_PNPM_COMMAND="$(command -v pnpm)"
|
||||
if [[ "${{ steps.plan.outputs.needs_live_image }}" == "1" ]]; then
|
||||
pnpm test:docker:live-build
|
||||
fi
|
||||
export OPENCLAW_DOCKER_ALL_BUILD=0
|
||||
|
||||
pnpm test:docker:all
|
||||
|
||||
- name: Summarize targeted Docker E2E lanes
|
||||
if: always()
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
summary=".artifacts/docker-tests/targeted/summary.json"
|
||||
if [[ ! -f "$summary" ]]; then
|
||||
echo "Docker targeted summary missing: \`$summary\`" >> "$GITHUB_STEP_SUMMARY"
|
||||
exit 0
|
||||
fi
|
||||
node scripts/docker-e2e.mjs summary "$summary" "Docker E2E targeted lanes" >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Upload targeted Docker E2E artifacts
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: docker-e2e-targeted
|
||||
path: .artifacts/docker-tests/
|
||||
if-no-files-found: ignore
|
||||
|
||||
validate_docker_openwebui:
|
||||
needs: [validate_selected_ref, prepare_docker_e2e_image]
|
||||
if: inputs.include_openwebui
|
||||
if: inputs.include_openwebui && !inputs.include_release_path_suites && inputs.docker_lanes == ''
|
||||
name: Docker E2E (openwebui)
|
||||
runs-on: blacksmith-32vcpu-ubuntu-2404
|
||||
timeout-minutes: 75
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}
|
||||
OPENCLAW_DOCKER_E2E_IMAGE: ${{ needs.prepare_docker_e2e_image.outputs.image }}
|
||||
OPENCLAW_DOCKER_E2E_FUNCTIONAL_IMAGE: ${{ needs.prepare_docker_e2e_image.outputs.functional_image }}
|
||||
OPENCLAW_DOCKER_E2E_PACKAGE_ARTIFACT_NAME: ${{ inputs.package_artifact_name || 'docker-e2e-package' }}
|
||||
OPENCLAW_CURRENT_PACKAGE_TGZ: .artifacts/docker-e2e-package/openclaw-current.tgz
|
||||
OPENCLAW_SKIP_DOCKER_BUILD: "1"
|
||||
steps:
|
||||
- name: Checkout selected ref
|
||||
@@ -586,19 +730,69 @@ jobs:
|
||||
exit 1
|
||||
}
|
||||
|
||||
- name: Run Open WebUI Docker E2E
|
||||
run: pnpm test:docker:openwebui
|
||||
- name: Plan and hydrate Open WebUI Docker E2E chunk
|
||||
id: plan
|
||||
uses: ./.github/actions/docker-e2e-plan
|
||||
with:
|
||||
mode: chunk
|
||||
chunk: openwebui
|
||||
include-openwebui: "true"
|
||||
package-artifact-name: ${{ inputs.package_artifact_name || 'docker-e2e-package' }}
|
||||
|
||||
- name: Run Open WebUI Docker E2E chunk
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
export OPENCLAW_DOCKER_ALL_PROFILE=release-path
|
||||
export OPENCLAW_DOCKER_ALL_CHUNK=openwebui
|
||||
export OPENCLAW_DOCKER_ALL_BUILD=0
|
||||
export OPENCLAW_DOCKER_ALL_PREFLIGHT=0
|
||||
export OPENCLAW_DOCKER_ALL_FAIL_FAST=0
|
||||
export OPENCLAW_DOCKER_ALL_INCLUDE_OPENWEBUI=1
|
||||
export OPENCLAW_DOCKER_ALL_LOG_DIR=".artifacts/docker-tests/release-openwebui"
|
||||
export OPENCLAW_DOCKER_ALL_TIMINGS_FILE=".artifacts/docker-tests/release-openwebui-timings.json"
|
||||
export OPENCLAW_DOCKER_ALL_PNPM_COMMAND="$(command -v pnpm)"
|
||||
|
||||
pnpm test:docker:all
|
||||
|
||||
- name: Summarize Open WebUI Docker E2E chunk
|
||||
if: always()
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
summary=".artifacts/docker-tests/release-openwebui/summary.json"
|
||||
if [[ ! -f "$summary" ]]; then
|
||||
echo "Docker Open WebUI summary missing: \`$summary\`" >> "$GITHUB_STEP_SUMMARY"
|
||||
exit 0
|
||||
fi
|
||||
node scripts/docker-e2e.mjs summary "$summary" "Docker E2E chunk: openwebui" >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Upload Open WebUI Docker E2E artifacts
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: docker-e2e-openwebui
|
||||
path: .artifacts/docker-tests/
|
||||
if-no-files-found: ignore
|
||||
|
||||
prepare_docker_e2e_image:
|
||||
needs: validate_selected_ref
|
||||
if: inputs.include_release_path_suites || inputs.include_openwebui
|
||||
if: inputs.include_release_path_suites || inputs.include_openwebui || inputs.docker_lanes != ''
|
||||
runs-on: blacksmith-32vcpu-ubuntu-2404
|
||||
timeout-minutes: 90
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
packages: write
|
||||
outputs:
|
||||
image: ${{ steps.image.outputs.image }}
|
||||
bare_image: ${{ steps.image.outputs.bare_image }}
|
||||
functional_image: ${{ steps.image.outputs.functional_image }}
|
||||
needs_bare_image: ${{ steps.plan.outputs.needs_bare_image }}
|
||||
needs_e2e_image: ${{ steps.plan.outputs.needs_e2e_image }}
|
||||
needs_functional_image: ${{ steps.plan.outputs.needs_functional_image }}
|
||||
needs_live_image: ${{ steps.plan.outputs.needs_live_image }}
|
||||
needs_package: ${{ steps.plan.outputs.needs_package }}
|
||||
env:
|
||||
DOCKER_BUILD_SUMMARY: "false"
|
||||
DOCKER_BUILD_RECORD_UPLOAD: "false"
|
||||
@@ -609,45 +803,191 @@ jobs:
|
||||
ref: ${{ needs.validate_selected_ref.outputs.selected_sha }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Resolve shared Docker E2E image tag
|
||||
- name: Plan Docker E2E images
|
||||
id: plan
|
||||
uses: ./.github/actions/docker-e2e-plan
|
||||
with:
|
||||
mode: prepare
|
||||
lanes: ${{ inputs.docker_lanes }}
|
||||
include-release-path-suites: ${{ inputs.include_release_path_suites }}
|
||||
include-openwebui: ${{ inputs.include_openwebui }}
|
||||
hydrate-artifacts: "false"
|
||||
|
||||
- name: Setup Node environment
|
||||
if: steps.plan.outputs.needs_package == '1' && inputs.package_artifact_name == '' && inputs.package_artifact_run_id == ''
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
pnpm-version: ${{ env.PNPM_VERSION }}
|
||||
install-bun: "true"
|
||||
|
||||
- name: Download current-run OpenClaw Docker E2E package
|
||||
if: steps.plan.outputs.needs_package == '1' && inputs.package_artifact_name != '' && inputs.package_artifact_run_id == ''
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: ${{ inputs.package_artifact_name }}
|
||||
path: .artifacts/docker-e2e-package
|
||||
|
||||
- name: Download previous-run OpenClaw Docker E2E package
|
||||
if: steps.plan.outputs.needs_package == '1' && inputs.package_artifact_run_id != ''
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: ${{ inputs.package_artifact_name || 'docker-e2e-package' }}
|
||||
path: .artifacts/docker-e2e-package
|
||||
run-id: ${{ inputs.package_artifact_run_id }}
|
||||
github-token: ${{ github.token }}
|
||||
|
||||
- name: Pack OpenClaw package for Docker E2E
|
||||
if: steps.plan.outputs.needs_package == '1' && inputs.package_artifact_name == '' && inputs.package_artifact_run_id == ''
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p .artifacts/docker-e2e-package
|
||||
node scripts/package-openclaw-for-docker.mjs \
|
||||
--output-dir .artifacts/docker-e2e-package \
|
||||
--output-name openclaw-current.tgz
|
||||
|
||||
- name: Validate OpenClaw Docker E2E package
|
||||
id: package
|
||||
if: steps.plan.outputs.needs_package == '1'
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p .artifacts/docker-e2e-package
|
||||
target=".artifacts/docker-e2e-package/openclaw-current.tgz"
|
||||
if [[ ! -f "$target" ]]; then
|
||||
mapfile -t tgzs < <(find .artifacts/docker-e2e-package -type f -name '*.tgz' | sort)
|
||||
if [[ "${#tgzs[@]}" -ne 1 ]]; then
|
||||
echo "Expected exactly one package tarball in .artifacts/docker-e2e-package; found ${#tgzs[@]}." >&2
|
||||
printf '%s\n' "${tgzs[@]}" >&2
|
||||
exit 1
|
||||
fi
|
||||
cp "${tgzs[0]}" "$target"
|
||||
fi
|
||||
node scripts/check-openclaw-package-tarball.mjs "$target"
|
||||
digest="$(sha256sum "$target" | awk '{print $1}')"
|
||||
tag="pkg-${digest:0:32}"
|
||||
echo "sha256=$digest" >> "$GITHUB_OUTPUT"
|
||||
echo "tag=$tag" >> "$GITHUB_OUTPUT"
|
||||
{
|
||||
echo "Docker E2E package: \`$target\`"
|
||||
echo "Docker E2E package SHA-256: \`$digest\`"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Upload OpenClaw Docker E2E package
|
||||
if: steps.plan.outputs.needs_package == '1' && (inputs.package_artifact_name == '' || inputs.package_artifact_run_id != '')
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ inputs.package_artifact_name || 'docker-e2e-package' }}
|
||||
path: .artifacts/docker-e2e-package/openclaw-current.tgz
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Resolve shared Docker E2E image tags
|
||||
id: image
|
||||
shell: bash
|
||||
env:
|
||||
PACKAGE_TAG: ${{ steps.package.outputs.tag }}
|
||||
SELECTED_SHA: ${{ needs.validate_selected_ref.outputs.selected_sha }}
|
||||
PROVIDED_BARE_IMAGE: ${{ inputs.docker_e2e_bare_image }}
|
||||
PROVIDED_FUNCTIONAL_IMAGE: ${{ inputs.docker_e2e_functional_image }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
repository="${GITHUB_REPOSITORY,,}"
|
||||
image="ghcr.io/${repository}-docker-e2e:${SELECTED_SHA}"
|
||||
image_tag="${PACKAGE_TAG:-$SELECTED_SHA}"
|
||||
bare_image="${PROVIDED_BARE_IMAGE:-ghcr.io/${repository}-docker-e2e-bare:${image_tag}}"
|
||||
functional_image="${PROVIDED_FUNCTIONAL_IMAGE:-ghcr.io/${repository}-docker-e2e-functional:${image_tag}}"
|
||||
image="$functional_image"
|
||||
echo "image=$image" >> "$GITHUB_OUTPUT"
|
||||
echo "Shared Docker E2E image: \`$image\`" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "bare_image=$bare_image" >> "$GITHUB_OUTPUT"
|
||||
echo "functional_image=$functional_image" >> "$GITHUB_OUTPUT"
|
||||
echo "Shared Docker E2E bare image: \`$bare_image\`" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "Shared Docker E2E functional image: \`$functional_image\`" >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Log in to GHCR
|
||||
if: steps.plan.outputs.needs_e2e_image == '1'
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ github.token }}
|
||||
|
||||
- name: Check existing shared Docker E2E images
|
||||
id: image_exists
|
||||
if: steps.plan.outputs.needs_e2e_image == '1'
|
||||
shell: bash
|
||||
env:
|
||||
PROVIDED_BARE_IMAGE: ${{ inputs.docker_e2e_bare_image }}
|
||||
PROVIDED_FUNCTIONAL_IMAGE: ${{ inputs.docker_e2e_functional_image }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
bare_exists=0
|
||||
functional_exists=0
|
||||
needs_build=0
|
||||
|
||||
if [[ "${{ steps.plan.outputs.needs_bare_image }}" == "1" ]]; then
|
||||
if docker manifest inspect "${{ steps.image.outputs.bare_image }}" >/dev/null 2>&1; then
|
||||
bare_exists=1
|
||||
echo "Shared Docker E2E bare image already exists: ${{ steps.image.outputs.bare_image }}"
|
||||
elif [[ -n "$PROVIDED_BARE_IMAGE" ]]; then
|
||||
echo "Provided bare Docker E2E image does not exist: $PROVIDED_BARE_IMAGE" >&2
|
||||
exit 1
|
||||
else
|
||||
needs_build=1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "${{ steps.plan.outputs.needs_functional_image }}" == "1" ]]; then
|
||||
if docker manifest inspect "${{ steps.image.outputs.functional_image }}" >/dev/null 2>&1; then
|
||||
functional_exists=1
|
||||
echo "Shared Docker E2E functional image already exists: ${{ steps.image.outputs.functional_image }}"
|
||||
elif [[ -n "$PROVIDED_FUNCTIONAL_IMAGE" ]]; then
|
||||
echo "Provided functional Docker E2E image does not exist: $PROVIDED_FUNCTIONAL_IMAGE" >&2
|
||||
exit 1
|
||||
else
|
||||
needs_build=1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "bare_exists=$bare_exists" >> "$GITHUB_OUTPUT"
|
||||
echo "functional_exists=$functional_exists" >> "$GITHUB_OUTPUT"
|
||||
echo "needs_build=$needs_build" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Setup Docker builder
|
||||
if: steps.image_exists.outputs.needs_build == '1'
|
||||
uses: useblacksmith/setup-docker-builder@ac083cc84672d01c60d5e8561d0a939b697de542 # v1
|
||||
|
||||
- name: Build and push shared Docker E2E image
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||
- name: Build and push bare Docker E2E image
|
||||
if: steps.plan.outputs.needs_bare_image == '1' && steps.image_exists.outputs.bare_exists != '1'
|
||||
uses: useblacksmith/build-push-action@cbd1f60d194a98cb3be5523b15134501eaf0fbf3 # v2
|
||||
with:
|
||||
context: .
|
||||
file: ./scripts/e2e/Dockerfile
|
||||
target: build
|
||||
target: bare
|
||||
platforms: linux/amd64
|
||||
cache-from: type=gha,scope=docker-e2e
|
||||
cache-to: type=gha,mode=max,scope=docker-e2e
|
||||
tags: ${{ steps.image.outputs.image }}
|
||||
provenance: false
|
||||
tags: ${{ steps.image.outputs.bare_image }}
|
||||
sbom: true
|
||||
provenance: mode=max
|
||||
push: true
|
||||
|
||||
- name: Build and push functional Docker E2E image
|
||||
if: steps.plan.outputs.needs_functional_image == '1' && steps.image_exists.outputs.functional_exists != '1'
|
||||
uses: useblacksmith/build-push-action@cbd1f60d194a98cb3be5523b15134501eaf0fbf3 # v2
|
||||
with:
|
||||
context: .
|
||||
file: ./scripts/e2e/Dockerfile
|
||||
target: functional
|
||||
build-contexts: |
|
||||
openclaw_package=.artifacts/docker-e2e-package
|
||||
platforms: linux/amd64
|
||||
tags: ${{ steps.image.outputs.functional_image }}
|
||||
sbom: true
|
||||
provenance: mode=max
|
||||
push: true
|
||||
|
||||
validate_live_models_docker:
|
||||
name: Docker live models (${{ matrix.provider_label }})
|
||||
needs: validate_selected_ref
|
||||
if: inputs.include_live_suites
|
||||
if: inputs.include_live_suites && inputs.live_model_providers == ''
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 75
|
||||
strategy:
|
||||
@@ -761,6 +1101,163 @@ jobs:
|
||||
- name: Run Docker live model sweep
|
||||
run: pnpm test:docker:live-models
|
||||
|
||||
validate_live_models_docker_targeted:
|
||||
name: Docker live models (selected providers)
|
||||
needs: validate_selected_ref
|
||||
if: inputs.include_live_suites && inputs.live_model_providers != ''
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 75
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}
|
||||
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }}
|
||||
ANTHROPIC_API_KEY_OLD: ${{ secrets.ANTHROPIC_API_KEY_OLD }}
|
||||
BYTEPLUS_API_KEY: ${{ secrets.BYTEPLUS_API_KEY }}
|
||||
CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}
|
||||
DASHSCOPE_API_KEY: ${{ secrets.DASHSCOPE_API_KEY }}
|
||||
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
|
||||
KIMI_API_KEY: ${{ secrets.KIMI_API_KEY }}
|
||||
MODELSTUDIO_API_KEY: ${{ secrets.MODELSTUDIO_API_KEY }}
|
||||
MOONSHOT_API_KEY: ${{ secrets.MOONSHOT_API_KEY }}
|
||||
MISTRAL_API_KEY: ${{ secrets.MISTRAL_API_KEY }}
|
||||
MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }}
|
||||
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
||||
OPENCODE_ZEN_API_KEY: ${{ secrets.OPENCODE_ZEN_API_KEY }}
|
||||
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
|
||||
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
|
||||
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
|
||||
QWEN_API_KEY: ${{ secrets.QWEN_API_KEY }}
|
||||
XAI_API_KEY: ${{ secrets.XAI_API_KEY }}
|
||||
ZAI_API_KEY: ${{ secrets.ZAI_API_KEY }}
|
||||
Z_AI_API_KEY: ${{ secrets.Z_AI_API_KEY }}
|
||||
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
OPENCLAW_CODEX_AUTH_JSON: ${{ secrets.OPENCLAW_CODEX_AUTH_JSON }}
|
||||
OPENCLAW_CODEX_CONFIG_TOML: ${{ secrets.OPENCLAW_CODEX_CONFIG_TOML }}
|
||||
OPENCLAW_CLAUDE_JSON: ${{ secrets.OPENCLAW_CLAUDE_JSON }}
|
||||
OPENCLAW_CLAUDE_CREDENTIALS_JSON: ${{ secrets.OPENCLAW_CLAUDE_CREDENTIALS_JSON }}
|
||||
OPENCLAW_CLAUDE_SETTINGS_JSON: ${{ secrets.OPENCLAW_CLAUDE_SETTINGS_JSON }}
|
||||
OPENCLAW_CLAUDE_SETTINGS_LOCAL_JSON: ${{ secrets.OPENCLAW_CLAUDE_SETTINGS_LOCAL_JSON }}
|
||||
OPENCLAW_GEMINI_SETTINGS_JSON: ${{ secrets.OPENCLAW_GEMINI_SETTINGS_JSON }}
|
||||
FIREWORKS_API_KEY: ${{ secrets.FIREWORKS_API_KEY }}
|
||||
REQUESTED_LIVE_MODEL_PROVIDERS: ${{ inputs.live_model_providers }}
|
||||
OPENCLAW_VITEST_MAX_WORKERS: "2"
|
||||
steps:
|
||||
- name: Checkout selected ref
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ needs.validate_selected_ref.outputs.selected_sha }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup Node environment
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
pnpm-version: ${{ env.PNPM_VERSION }}
|
||||
install-bun: "true"
|
||||
|
||||
- name: Normalize provider allowlist
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
all_providers=(anthropic google minimax openai opencode-go openrouter xai zai fireworks)
|
||||
|
||||
normalize_provider() {
|
||||
local value="${1,,}"
|
||||
case "$value" in
|
||||
z.ai|z-ai) echo "zai" ;;
|
||||
opencode|opencode-go) echo "opencode-go" ;;
|
||||
open-router|openrouter) echo "openrouter" ;;
|
||||
*) echo "$value" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
is_known_provider() {
|
||||
local value="$1"
|
||||
local provider
|
||||
for provider in "${all_providers[@]}"; do
|
||||
[[ "$provider" == "$value" ]] && return 0
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
selected=()
|
||||
declare -A seen=()
|
||||
raw="${REQUESTED_LIVE_MODEL_PROVIDERS:-}"
|
||||
normalized_all="${raw,,}"
|
||||
normalized_all="${normalized_all//[[:space:],]/}"
|
||||
if [[ -z "$normalized_all" || "$normalized_all" == "all" ]]; then
|
||||
selected=("${all_providers[@]}")
|
||||
else
|
||||
while IFS= read -r entry; do
|
||||
[[ -z "$entry" ]] && continue
|
||||
provider="$(normalize_provider "$entry")"
|
||||
if ! is_known_provider "$provider"; then
|
||||
echo "Unknown live model provider '${entry}'. Expected one of: ${all_providers[*]}" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ -z "${seen[$provider]:-}" ]]; then
|
||||
selected+=("$provider")
|
||||
seen[$provider]=1
|
||||
fi
|
||||
done < <(printf '%s\n' "$raw" | tr ',' '\n' | tr '[:space:]' '\n')
|
||||
fi
|
||||
|
||||
if [[ "${#selected[@]}" -eq 0 ]]; then
|
||||
echo "No live model providers selected." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
providers_csv="$(IFS=,; echo "${selected[*]}")"
|
||||
echo "OPENCLAW_LIVE_PROVIDERS=$providers_csv" >> "$GITHUB_ENV"
|
||||
{
|
||||
echo "Live model providers: \`$providers_csv\`"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Hydrate live auth/profile inputs
|
||||
run: bash scripts/ci-hydrate-live-auth.sh
|
||||
|
||||
- name: Validate provider credentials
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
require_any() {
|
||||
local label="$1"
|
||||
shift
|
||||
local key
|
||||
for key in "$@"; do
|
||||
if [[ -n "${!key:-}" ]]; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
echo "Missing credential for ${label}: expected one of $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
IFS=',' read -r -a providers <<<"${OPENCLAW_LIVE_PROVIDERS}"
|
||||
for provider in "${providers[@]}"; do
|
||||
case "$provider" in
|
||||
anthropic) require_any Anthropic ANTHROPIC_API_KEY ANTHROPIC_API_KEY_OLD ANTHROPIC_API_TOKEN ;;
|
||||
google) require_any Google GEMINI_API_KEY GOOGLE_API_KEY ;;
|
||||
minimax) require_any MiniMax MINIMAX_API_KEY ;;
|
||||
openai) require_any OpenAI OPENAI_API_KEY ;;
|
||||
opencode-go) require_any OpenCode OPENCODE_API_KEY OPENCODE_ZEN_API_KEY ;;
|
||||
openrouter) require_any OpenRouter OPENROUTER_API_KEY ;;
|
||||
xai) require_any xAI XAI_API_KEY ;;
|
||||
zai) require_any Z.ai ZAI_API_KEY Z_AI_API_KEY ;;
|
||||
fireworks) require_any Fireworks FIREWORKS_API_KEY ;;
|
||||
*)
|
||||
echo "Unhandled live model provider shard: ${provider}" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
- name: Run Docker live model sweep
|
||||
run: pnpm test:docker:live-models
|
||||
|
||||
validate_live_provider_suites:
|
||||
needs: validate_selected_ref
|
||||
if: inputs.include_live_suites && !inputs.live_models_only
|
||||
|
||||
95
.github/workflows/openclaw-release-checks.yml
vendored
95
.github/workflows/openclaw-release-checks.yml
vendored
@@ -4,7 +4,7 @@ on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
ref:
|
||||
description: Existing release tag or current full 40-character workflow-branch commit SHA to validate (for example v2026.4.12 or 0123456789abcdef0123456789abcdef01234567)
|
||||
description: Branch, tag, or full commit SHA to validate
|
||||
required: true
|
||||
type: string
|
||||
provider:
|
||||
@@ -63,8 +63,8 @@ jobs:
|
||||
RELEASE_REF: ${{ inputs.ref }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ ! "${RELEASE_REF}" =~ ^v[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*((-beta\.[1-9][0-9]*)|(-[1-9][0-9]*))?$ ]] && [[ ! "${RELEASE_REF}" =~ ^[0-9a-fA-F]{40}$ ]]; then
|
||||
echo "Expected an existing release tag or current full 40-character workflow-branch commit SHA, got: ${RELEASE_REF}" >&2
|
||||
if [[ -z "${RELEASE_REF// }" ]] || [[ "${RELEASE_REF}" == -* ]]; then
|
||||
echo "Expected a branch, tag, or full commit SHA; got: ${RELEASE_REF}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -78,24 +78,27 @@ jobs:
|
||||
id: ref
|
||||
run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Validate selected ref is on workflow branch
|
||||
- name: Validate selected ref belongs to this repository
|
||||
env:
|
||||
RELEASE_REF: ${{ inputs.ref }}
|
||||
WORKFLOW_REF_NAME: ${{ github.ref_name }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
RELEASE_BRANCH_REF="refs/remotes/origin/${WORKFLOW_REF_NAME}"
|
||||
git fetch --no-tags origin "+refs/heads/${WORKFLOW_REF_NAME}:refs/remotes/origin/${WORKFLOW_REF_NAME}"
|
||||
if [[ "${RELEASE_REF}" =~ ^[0-9a-fA-F]{40}$ ]]; then
|
||||
BRANCH_SHA="$(git rev-parse "${RELEASE_BRANCH_REF}")"
|
||||
if [[ "$(git rev-parse HEAD)" != "${BRANCH_SHA}" ]]; then
|
||||
echo "Commit SHA mode only supports the current ${WORKFLOW_REF_NAME} HEAD. Use a release tag for older commits." >&2
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
git merge-base --is-ancestor HEAD "${RELEASE_BRANCH_REF}"
|
||||
SELECTED_SHA="$(git rev-parse HEAD)"
|
||||
git fetch --no-tags origin '+refs/heads/*:refs/remotes/origin/*'
|
||||
git fetch --tags origin '+refs/tags/*:refs/tags/*'
|
||||
|
||||
if git tag --points-at "${SELECTED_SHA}" | grep -Eq '^v'; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if git for-each-ref --format='%(refname:short)' --contains "${SELECTED_SHA}" refs/remotes/origin | grep -Eq '^origin/'; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Ref '${RELEASE_REF}' resolved to ${SELECTED_SHA}, but that commit is not reachable from an OpenClaw branch or release tag." >&2
|
||||
echo "Secret-bearing release checks only run repository-owned branch/tag history, not arbitrary unreferenced commits." >&2
|
||||
exit 1
|
||||
|
||||
- name: Capture selected inputs
|
||||
id: inputs
|
||||
env:
|
||||
@@ -211,6 +214,26 @@ jobs:
|
||||
OPENCLAW_GEMINI_SETTINGS_JSON: ${{ secrets.OPENCLAW_GEMINI_SETTINGS_JSON }}
|
||||
FIREWORKS_API_KEY: ${{ secrets.FIREWORKS_API_KEY }}
|
||||
|
||||
package_acceptance_release_checks:
|
||||
name: Run package acceptance
|
||||
needs: [resolve_target]
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
packages: write
|
||||
pull-requests: read
|
||||
uses: ./.github/workflows/package-acceptance.yml
|
||||
with:
|
||||
workflow_ref: ${{ github.ref_name }}
|
||||
source: ref
|
||||
package_ref: ${{ needs.resolve_target.outputs.ref }}
|
||||
suite_profile: package
|
||||
telegram_mode: mock-openai
|
||||
secrets:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
OPENCLAW_QA_CONVEX_SITE_URL: ${{ secrets.OPENCLAW_QA_CONVEX_SITE_URL }}
|
||||
OPENCLAW_QA_CONVEX_SECRET_CI: ${{ secrets.OPENCLAW_QA_CONVEX_SECRET_CI }}
|
||||
|
||||
qa_lab_parity_release_checks:
|
||||
name: Run QA Lab parity gate
|
||||
needs: [resolve_target]
|
||||
@@ -332,6 +355,7 @@ jobs:
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
OPENCLAW_QA_REDACT_PUBLIC_METADATA: "1"
|
||||
OPENCLAW_QA_MATRIX_NO_REPLY_WINDOW_MS: "3000"
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
@@ -344,7 +368,9 @@ jobs:
|
||||
--provider-mode live-frontier \
|
||||
--model "${OPENCLAW_CI_OPENAI_MODEL}" \
|
||||
--alt-model "${OPENCLAW_CI_OPENAI_MODEL}" \
|
||||
--fast
|
||||
--profile fast \
|
||||
--fast \
|
||||
--fail-fast
|
||||
|
||||
- name: Upload Matrix QA artifacts
|
||||
if: always()
|
||||
@@ -438,3 +464,40 @@ jobs:
|
||||
path: ${{ steps.run_lane.outputs.output_dir }}
|
||||
retention-days: 14
|
||||
if-no-files-found: warn
|
||||
|
||||
summary:
|
||||
name: Verify release checks
|
||||
needs:
|
||||
- install_smoke_release_checks
|
||||
- cross_os_release_checks
|
||||
- live_and_e2e_release_checks
|
||||
- package_acceptance_release_checks
|
||||
- qa_lab_parity_release_checks
|
||||
- qa_live_matrix_release_checks
|
||||
- qa_live_telegram_release_checks
|
||||
if: always()
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Verify release check results
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
failed=0
|
||||
for item in \
|
||||
"install_smoke_release_checks=${{ needs.install_smoke_release_checks.result }}" \
|
||||
"cross_os_release_checks=${{ needs.cross_os_release_checks.result }}" \
|
||||
"live_and_e2e_release_checks=${{ needs.live_and_e2e_release_checks.result }}" \
|
||||
"package_acceptance_release_checks=${{ needs.package_acceptance_release_checks.result }}" \
|
||||
"qa_lab_parity_release_checks=${{ needs.qa_lab_parity_release_checks.result }}" \
|
||||
"qa_live_matrix_release_checks=${{ needs.qa_live_matrix_release_checks.result }}" \
|
||||
"qa_live_telegram_release_checks=${{ needs.qa_live_telegram_release_checks.result }}"
|
||||
do
|
||||
name="${item%%=*}"
|
||||
result="${item#*=}"
|
||||
if [[ "$result" != "success" && "$result" != "skipped" ]]; then
|
||||
echo "::error::${name} ended with ${result}"
|
||||
failed=1
|
||||
fi
|
||||
done
|
||||
exit "$failed"
|
||||
|
||||
517
.github/workflows/package-acceptance.yml
vendored
Normal file
517
.github/workflows/package-acceptance.yml
vendored
Normal file
@@ -0,0 +1,517 @@
|
||||
name: Package Acceptance
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
workflow_ref:
|
||||
description: Trusted repo ref for workflow scripts and Docker E2E harness
|
||||
required: true
|
||||
default: main
|
||||
type: string
|
||||
source:
|
||||
description: Package candidate source
|
||||
required: true
|
||||
default: npm
|
||||
type: choice
|
||||
options:
|
||||
- npm
|
||||
- ref
|
||||
- url
|
||||
- artifact
|
||||
package_ref:
|
||||
description: Trusted package source ref when source=ref
|
||||
required: true
|
||||
default: main
|
||||
type: string
|
||||
package_spec:
|
||||
description: Published package spec when source=npm
|
||||
required: false
|
||||
default: openclaw@beta
|
||||
type: string
|
||||
package_url:
|
||||
description: HTTPS .tgz URL when source=url
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
package_sha256:
|
||||
description: Expected package SHA-256; required for source=url
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
artifact_run_id:
|
||||
description: GitHub Actions run id when source=artifact
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
artifact_name:
|
||||
description: Artifact name containing one .tgz when source=artifact
|
||||
required: false
|
||||
default: package-under-test
|
||||
type: string
|
||||
suite_profile:
|
||||
description: Acceptance profile
|
||||
required: true
|
||||
default: package
|
||||
type: choice
|
||||
options:
|
||||
- smoke
|
||||
- package
|
||||
- product
|
||||
- full
|
||||
- custom
|
||||
docker_lanes:
|
||||
description: Comma/space separated Docker lanes when suite_profile=custom
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
telegram_mode:
|
||||
description: Optional Telegram QA lane for the resolved package candidate
|
||||
required: true
|
||||
default: none
|
||||
type: choice
|
||||
options:
|
||||
- none
|
||||
- mock-openai
|
||||
- live-frontier
|
||||
workflow_call:
|
||||
inputs:
|
||||
workflow_ref:
|
||||
description: Trusted repo ref for workflow scripts and Docker E2E harness
|
||||
required: false
|
||||
default: main
|
||||
type: string
|
||||
source:
|
||||
description: "Package candidate source: npm, ref, url, or artifact"
|
||||
required: true
|
||||
type: string
|
||||
package_ref:
|
||||
description: Trusted package source ref when source=ref
|
||||
required: false
|
||||
default: main
|
||||
type: string
|
||||
package_spec:
|
||||
description: Published package spec when source=npm
|
||||
required: false
|
||||
default: openclaw@beta
|
||||
type: string
|
||||
package_url:
|
||||
description: HTTPS .tgz URL when source=url
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
package_sha256:
|
||||
description: Expected package SHA-256; required for source=url
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
artifact_run_id:
|
||||
description: GitHub Actions run id when source=artifact
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
artifact_name:
|
||||
description: Artifact name containing one .tgz when source=artifact
|
||||
required: false
|
||||
default: package-under-test
|
||||
type: string
|
||||
suite_profile:
|
||||
description: "Acceptance profile: smoke, package, product, full, or custom"
|
||||
required: false
|
||||
default: package
|
||||
type: string
|
||||
docker_lanes:
|
||||
description: Comma/space separated Docker lanes when suite_profile=custom
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
telegram_mode:
|
||||
description: Optional Telegram QA lane for the resolved package candidate
|
||||
required: false
|
||||
default: none
|
||||
type: string
|
||||
secrets:
|
||||
OPENAI_API_KEY:
|
||||
required: false
|
||||
OPENAI_BASE_URL:
|
||||
required: false
|
||||
ANTHROPIC_API_KEY:
|
||||
required: false
|
||||
ANTHROPIC_API_KEY_OLD:
|
||||
required: false
|
||||
ANTHROPIC_API_TOKEN:
|
||||
required: false
|
||||
BYTEPLUS_API_KEY:
|
||||
required: false
|
||||
CEREBRAS_API_KEY:
|
||||
required: false
|
||||
DASHSCOPE_API_KEY:
|
||||
required: false
|
||||
GROQ_API_KEY:
|
||||
required: false
|
||||
KIMI_API_KEY:
|
||||
required: false
|
||||
MODELSTUDIO_API_KEY:
|
||||
required: false
|
||||
MOONSHOT_API_KEY:
|
||||
required: false
|
||||
MISTRAL_API_KEY:
|
||||
required: false
|
||||
MINIMAX_API_KEY:
|
||||
required: false
|
||||
OPENCODE_API_KEY:
|
||||
required: false
|
||||
OPENCODE_ZEN_API_KEY:
|
||||
required: false
|
||||
OPENCLAW_LIVE_BROWSER_CDP_URL:
|
||||
required: false
|
||||
OPENCLAW_LIVE_SETUP_TOKEN:
|
||||
required: false
|
||||
OPENCLAW_LIVE_SETUP_TOKEN_MODEL:
|
||||
required: false
|
||||
OPENCLAW_LIVE_SETUP_TOKEN_PROFILE:
|
||||
required: false
|
||||
OPENCLAW_LIVE_SETUP_TOKEN_VALUE:
|
||||
required: false
|
||||
GEMINI_API_KEY:
|
||||
required: false
|
||||
GOOGLE_API_KEY:
|
||||
required: false
|
||||
OPENROUTER_API_KEY:
|
||||
required: false
|
||||
QWEN_API_KEY:
|
||||
required: false
|
||||
FAL_KEY:
|
||||
required: false
|
||||
RUNWAY_API_KEY:
|
||||
required: false
|
||||
DEEPGRAM_API_KEY:
|
||||
required: false
|
||||
TOGETHER_API_KEY:
|
||||
required: false
|
||||
VYDRA_API_KEY:
|
||||
required: false
|
||||
XAI_API_KEY:
|
||||
required: false
|
||||
ZAI_API_KEY:
|
||||
required: false
|
||||
Z_AI_API_KEY:
|
||||
required: false
|
||||
BYTEPLUS_ACCESS_KEY_ID:
|
||||
required: false
|
||||
BYTEPLUS_SECRET_ACCESS_KEY:
|
||||
required: false
|
||||
CLAUDE_CODE_OAUTH_TOKEN:
|
||||
required: false
|
||||
OPENCLAW_CODEX_AUTH_JSON:
|
||||
required: false
|
||||
OPENCLAW_CODEX_CONFIG_TOML:
|
||||
required: false
|
||||
OPENCLAW_CLAUDE_JSON:
|
||||
required: false
|
||||
OPENCLAW_CLAUDE_CREDENTIALS_JSON:
|
||||
required: false
|
||||
OPENCLAW_CLAUDE_SETTINGS_JSON:
|
||||
required: false
|
||||
OPENCLAW_CLAUDE_SETTINGS_LOCAL_JSON:
|
||||
required: false
|
||||
OPENCLAW_GEMINI_SETTINGS_JSON:
|
||||
required: false
|
||||
FIREWORKS_API_KEY:
|
||||
required: false
|
||||
OPENCLAW_QA_CONVEX_SITE_URL:
|
||||
required: false
|
||||
OPENCLAW_QA_CONVEX_SECRET_CI:
|
||||
required: false
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
packages: write
|
||||
pull-requests: read
|
||||
|
||||
concurrency:
|
||||
group: package-acceptance-${{ github.run_id }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
NODE_VERSION: "24.x"
|
||||
PNPM_VERSION: "10.33.0"
|
||||
PACKAGE_ARTIFACT_NAME: package-under-test
|
||||
|
||||
jobs:
|
||||
resolve_package:
|
||||
name: Resolve package candidate
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 60
|
||||
outputs:
|
||||
docker_lanes: ${{ steps.profile.outputs.docker_lanes }}
|
||||
include_live_suites: ${{ steps.profile.outputs.include_live_suites }}
|
||||
include_openwebui: ${{ steps.profile.outputs.include_openwebui }}
|
||||
include_release_path_suites: ${{ steps.profile.outputs.include_release_path_suites }}
|
||||
package_artifact_name: ${{ steps.profile.outputs.package_artifact_name }}
|
||||
package_sha256: ${{ steps.resolve.outputs.sha256 }}
|
||||
package_version: ${{ steps.resolve.outputs.package_version }}
|
||||
telegram_enabled: ${{ steps.profile.outputs.telegram_enabled }}
|
||||
telegram_mode: ${{ steps.profile.outputs.telegram_mode }}
|
||||
steps:
|
||||
- name: Checkout package workflow ref
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ inputs.workflow_ref }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node environment
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
pnpm-version: ${{ env.PNPM_VERSION }}
|
||||
install-bun: ${{ inputs.source == 'ref' && 'true' || 'false' }}
|
||||
install-deps: "false"
|
||||
|
||||
- name: Download package artifact input
|
||||
if: inputs.source == 'artifact'
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
ARTIFACT_RUN_ID: ${{ inputs.artifact_run_id }}
|
||||
ARTIFACT_NAME: ${{ inputs.artifact_name }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ -z "${ARTIFACT_RUN_ID// }" ]]; then
|
||||
echo "artifact_run_id is required when source=artifact." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ -z "${ARTIFACT_NAME// }" ]]; then
|
||||
echo "artifact_name is required when source=artifact." >&2
|
||||
exit 1
|
||||
fi
|
||||
mkdir -p .artifacts/package-candidate-input
|
||||
gh run download "$ARTIFACT_RUN_ID" -n "$ARTIFACT_NAME" -D .artifacts/package-candidate-input
|
||||
|
||||
- name: Resolve package candidate
|
||||
id: resolve
|
||||
env:
|
||||
SOURCE: ${{ inputs.source }}
|
||||
PACKAGE_REF: ${{ inputs.package_ref }}
|
||||
PACKAGE_SPEC: ${{ inputs.package_spec }}
|
||||
PACKAGE_URL: ${{ inputs.package_url }}
|
||||
PACKAGE_SHA256: ${{ inputs.package_sha256 }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
artifact_dir=""
|
||||
if [[ "$SOURCE" == "artifact" ]]; then
|
||||
artifact_dir=".artifacts/package-candidate-input"
|
||||
fi
|
||||
|
||||
node scripts/resolve-openclaw-package-candidate.mjs \
|
||||
--source "$SOURCE" \
|
||||
--package-ref "$PACKAGE_REF" \
|
||||
--package-spec "$PACKAGE_SPEC" \
|
||||
--package-url "$PACKAGE_URL" \
|
||||
--package-sha256 "$PACKAGE_SHA256" \
|
||||
--artifact-dir "${artifact_dir:-.}" \
|
||||
--output-dir .artifacts/docker-e2e-package \
|
||||
--output-name openclaw-current.tgz \
|
||||
--metadata .artifacts/docker-e2e-package/package-candidate.json \
|
||||
--github-output "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Select acceptance profile
|
||||
id: profile
|
||||
env:
|
||||
SOURCE: ${{ inputs.source }}
|
||||
SUITE_PROFILE: ${{ inputs.suite_profile }}
|
||||
CUSTOM_DOCKER_LANES: ${{ inputs.docker_lanes }}
|
||||
TELEGRAM_MODE: ${{ inputs.telegram_mode }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
include_release_path_suites=false
|
||||
include_openwebui=false
|
||||
include_live_suites=false
|
||||
docker_lanes=""
|
||||
|
||||
case "$SUITE_PROFILE" in
|
||||
smoke)
|
||||
docker_lanes="npm-onboard-channel-agent gateway-network config-reload"
|
||||
;;
|
||||
package)
|
||||
docker_lanes="npm-onboard-channel-agent doctor-switch update-channel-switch bundled-channel-deps-compat plugins-offline plugin-update"
|
||||
;;
|
||||
product)
|
||||
docker_lanes="npm-onboard-channel-agent doctor-switch update-channel-switch bundled-channel-deps-compat plugins plugin-update mcp-channels cron-mcp-cleanup openai-web-search-minimal openwebui"
|
||||
include_openwebui=true
|
||||
;;
|
||||
full)
|
||||
include_release_path_suites=true
|
||||
include_openwebui=true
|
||||
;;
|
||||
custom)
|
||||
docker_lanes="$CUSTOM_DOCKER_LANES"
|
||||
if [[ -z "${docker_lanes// }" ]]; then
|
||||
echo "docker_lanes is required when suite_profile=custom." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$docker_lanes" == *"openwebui"* ]]; then
|
||||
include_openwebui=true
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo "Unknown suite_profile: $SUITE_PROFILE" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
telegram_enabled=false
|
||||
if [[ "$TELEGRAM_MODE" != "none" ]]; then
|
||||
telegram_enabled=true
|
||||
fi
|
||||
|
||||
{
|
||||
echo "docker_lanes=$docker_lanes"
|
||||
echo "include_release_path_suites=$include_release_path_suites"
|
||||
echo "include_openwebui=$include_openwebui"
|
||||
echo "include_live_suites=$include_live_suites"
|
||||
echo "telegram_enabled=$telegram_enabled"
|
||||
echo "telegram_mode=$TELEGRAM_MODE"
|
||||
echo "package_artifact_name=${PACKAGE_ARTIFACT_NAME}"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Upload package-under-test artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ env.PACKAGE_ARTIFACT_NAME }}
|
||||
path: |
|
||||
.artifacts/docker-e2e-package/openclaw-current.tgz
|
||||
.artifacts/docker-e2e-package/package-candidate.json
|
||||
retention-days: 14
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Summarize package candidate
|
||||
env:
|
||||
PACKAGE_SHA256: ${{ steps.resolve.outputs.sha256 }}
|
||||
PACKAGE_VERSION: ${{ steps.resolve.outputs.package_version }}
|
||||
PACKAGE_REF: ${{ inputs.package_ref }}
|
||||
SOURCE: ${{ inputs.source }}
|
||||
SUITE_PROFILE: ${{ inputs.suite_profile }}
|
||||
WORKFLOW_REF: ${{ inputs.workflow_ref }}
|
||||
shell: bash
|
||||
run: |
|
||||
{
|
||||
echo "## Package acceptance"
|
||||
echo
|
||||
echo "- Source: \`${SOURCE}\`"
|
||||
echo "- Workflow ref: \`${WORKFLOW_REF}\`"
|
||||
if [[ "${SOURCE}" == "ref" ]]; then
|
||||
echo "- Package ref: \`${PACKAGE_REF}\`"
|
||||
fi
|
||||
echo "- Version: \`${PACKAGE_VERSION}\`"
|
||||
echo "- SHA-256: \`${PACKAGE_SHA256}\`"
|
||||
echo "- Profile: \`${SUITE_PROFILE}\`"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
docker_acceptance:
|
||||
name: Docker product acceptance
|
||||
needs: resolve_package
|
||||
uses: ./.github/workflows/openclaw-live-and-e2e-checks-reusable.yml
|
||||
with:
|
||||
ref: ${{ inputs.workflow_ref }}
|
||||
include_repo_e2e: false
|
||||
include_release_path_suites: ${{ needs.resolve_package.outputs.include_release_path_suites == 'true' }}
|
||||
include_openwebui: ${{ needs.resolve_package.outputs.include_openwebui == 'true' }}
|
||||
docker_lanes: ${{ needs.resolve_package.outputs.docker_lanes }}
|
||||
package_artifact_name: ${{ needs.resolve_package.outputs.package_artifact_name }}
|
||||
include_live_suites: ${{ needs.resolve_package.outputs.include_live_suites == 'true' }}
|
||||
live_models_only: false
|
||||
secrets:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}
|
||||
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
ANTHROPIC_API_KEY_OLD: ${{ secrets.ANTHROPIC_API_KEY_OLD }}
|
||||
ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }}
|
||||
BYTEPLUS_API_KEY: ${{ secrets.BYTEPLUS_API_KEY }}
|
||||
CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}
|
||||
DASHSCOPE_API_KEY: ${{ secrets.DASHSCOPE_API_KEY }}
|
||||
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
|
||||
KIMI_API_KEY: ${{ secrets.KIMI_API_KEY }}
|
||||
MODELSTUDIO_API_KEY: ${{ secrets.MODELSTUDIO_API_KEY }}
|
||||
MOONSHOT_API_KEY: ${{ secrets.MOONSHOT_API_KEY }}
|
||||
MISTRAL_API_KEY: ${{ secrets.MISTRAL_API_KEY }}
|
||||
MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }}
|
||||
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
||||
OPENCODE_ZEN_API_KEY: ${{ secrets.OPENCODE_ZEN_API_KEY }}
|
||||
OPENCLAW_LIVE_BROWSER_CDP_URL: ${{ secrets.OPENCLAW_LIVE_BROWSER_CDP_URL }}
|
||||
OPENCLAW_LIVE_SETUP_TOKEN: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN }}
|
||||
OPENCLAW_LIVE_SETUP_TOKEN_MODEL: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN_MODEL }}
|
||||
OPENCLAW_LIVE_SETUP_TOKEN_PROFILE: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN_PROFILE }}
|
||||
OPENCLAW_LIVE_SETUP_TOKEN_VALUE: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN_VALUE }}
|
||||
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
|
||||
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
|
||||
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
|
||||
QWEN_API_KEY: ${{ secrets.QWEN_API_KEY }}
|
||||
FAL_KEY: ${{ secrets.FAL_KEY }}
|
||||
RUNWAY_API_KEY: ${{ secrets.RUNWAY_API_KEY }}
|
||||
DEEPGRAM_API_KEY: ${{ secrets.DEEPGRAM_API_KEY }}
|
||||
TOGETHER_API_KEY: ${{ secrets.TOGETHER_API_KEY }}
|
||||
VYDRA_API_KEY: ${{ secrets.VYDRA_API_KEY }}
|
||||
XAI_API_KEY: ${{ secrets.XAI_API_KEY }}
|
||||
ZAI_API_KEY: ${{ secrets.ZAI_API_KEY }}
|
||||
Z_AI_API_KEY: ${{ secrets.Z_AI_API_KEY }}
|
||||
BYTEPLUS_ACCESS_KEY_ID: ${{ secrets.BYTEPLUS_ACCESS_KEY_ID }}
|
||||
BYTEPLUS_SECRET_ACCESS_KEY: ${{ secrets.BYTEPLUS_SECRET_ACCESS_KEY }}
|
||||
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
OPENCLAW_CODEX_AUTH_JSON: ${{ secrets.OPENCLAW_CODEX_AUTH_JSON }}
|
||||
OPENCLAW_CODEX_CONFIG_TOML: ${{ secrets.OPENCLAW_CODEX_CONFIG_TOML }}
|
||||
OPENCLAW_CLAUDE_JSON: ${{ secrets.OPENCLAW_CLAUDE_JSON }}
|
||||
OPENCLAW_CLAUDE_CREDENTIALS_JSON: ${{ secrets.OPENCLAW_CLAUDE_CREDENTIALS_JSON }}
|
||||
OPENCLAW_CLAUDE_SETTINGS_JSON: ${{ secrets.OPENCLAW_CLAUDE_SETTINGS_JSON }}
|
||||
OPENCLAW_CLAUDE_SETTINGS_LOCAL_JSON: ${{ secrets.OPENCLAW_CLAUDE_SETTINGS_LOCAL_JSON }}
|
||||
OPENCLAW_GEMINI_SETTINGS_JSON: ${{ secrets.OPENCLAW_GEMINI_SETTINGS_JSON }}
|
||||
FIREWORKS_API_KEY: ${{ secrets.FIREWORKS_API_KEY }}
|
||||
|
||||
package_telegram:
|
||||
name: Telegram package acceptance
|
||||
needs: resolve_package
|
||||
if: needs.resolve_package.outputs.telegram_enabled == 'true'
|
||||
uses: ./.github/workflows/npm-telegram-beta-e2e.yml
|
||||
with:
|
||||
package_spec: ${{ inputs.package_spec }}
|
||||
package_artifact_name: ${{ needs.resolve_package.outputs.package_artifact_name }}
|
||||
package_label: openclaw@${{ needs.resolve_package.outputs.package_version }}
|
||||
provider_mode: ${{ needs.resolve_package.outputs.telegram_mode }}
|
||||
secrets:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
OPENCLAW_QA_CONVEX_SITE_URL: ${{ secrets.OPENCLAW_QA_CONVEX_SITE_URL }}
|
||||
OPENCLAW_QA_CONVEX_SECRET_CI: ${{ secrets.OPENCLAW_QA_CONVEX_SECRET_CI }}
|
||||
|
||||
summary:
|
||||
name: Verify package acceptance
|
||||
needs: [resolve_package, docker_acceptance, package_telegram]
|
||||
if: always()
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Verify package acceptance results
|
||||
env:
|
||||
DOCKER_RESULT: ${{ needs.docker_acceptance.result }}
|
||||
PACKAGE_TELEGRAM_RESULT: ${{ needs.package_telegram.result }}
|
||||
RESOLVE_RESULT: ${{ needs.resolve_package.result }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
failed=0
|
||||
for item in \
|
||||
"resolve_package=${RESOLVE_RESULT}" \
|
||||
"docker_acceptance=${DOCKER_RESULT}" \
|
||||
"package_telegram=${PACKAGE_TELEGRAM_RESULT}"
|
||||
do
|
||||
name="${item%%=*}"
|
||||
result="${item#*=}"
|
||||
if [[ "$result" != "success" && "$result" != "skipped" ]]; then
|
||||
echo "::error::${name} ended with ${result}"
|
||||
failed=1
|
||||
fi
|
||||
done
|
||||
exit "$failed"
|
||||
97
.github/workflows/qa-live-transports-convex.yml
vendored
97
.github/workflows/qa-live-transports-convex.yml
vendored
@@ -18,6 +18,19 @@ on:
|
||||
description: Optional comma-separated Discord scenario ids
|
||||
required: false
|
||||
type: string
|
||||
matrix_profile:
|
||||
description: Matrix QA profile for the live Matrix lane
|
||||
required: false
|
||||
default: all
|
||||
type: choice
|
||||
options:
|
||||
- fast
|
||||
- all
|
||||
- transport
|
||||
- media
|
||||
- e2ee-smoke
|
||||
- e2ee-deep
|
||||
- e2ee-cli
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -199,6 +212,7 @@ jobs:
|
||||
run_live_matrix:
|
||||
name: Run Matrix live QA lane
|
||||
needs: [authorize_actor, validate_selected_ref]
|
||||
if: ${{ !(github.event_name == 'workflow_dispatch' && inputs.matrix_profile == 'all') }}
|
||||
runs-on: blacksmith-32vcpu-ubuntu-2404
|
||||
timeout-minutes: 60
|
||||
environment: qa-live-shared
|
||||
@@ -236,7 +250,9 @@ jobs:
|
||||
shell: bash
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
INPUT_MATRIX_PROFILE: ${{ github.event_name == 'workflow_dispatch' && inputs.matrix_profile || 'fast' }}
|
||||
OPENCLAW_QA_REDACT_PUBLIC_METADATA: "1"
|
||||
OPENCLAW_QA_MATRIX_NO_REPLY_WINDOW_MS: "3000"
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
@@ -249,7 +265,9 @@ jobs:
|
||||
--provider-mode live-frontier \
|
||||
--model "${OPENCLAW_CI_OPENAI_MODEL}" \
|
||||
--alt-model "${OPENCLAW_CI_OPENAI_MODEL}" \
|
||||
--fast
|
||||
--profile "${INPUT_MATRIX_PROFILE}" \
|
||||
--fast \
|
||||
--fail-fast
|
||||
|
||||
- name: Upload Matrix QA artifacts
|
||||
if: always()
|
||||
@@ -260,6 +278,83 @@ jobs:
|
||||
retention-days: 14
|
||||
if-no-files-found: warn
|
||||
|
||||
run_live_matrix_sharded:
|
||||
name: Run Matrix live QA lane (${{ matrix.profile }})
|
||||
needs: [authorize_actor, validate_selected_ref]
|
||||
if: ${{ github.event_name == 'workflow_dispatch' && inputs.matrix_profile == 'all' }}
|
||||
runs-on: blacksmith-32vcpu-ubuntu-2404
|
||||
timeout-minutes: 60
|
||||
environment: qa-live-shared
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
profile:
|
||||
- transport
|
||||
- media
|
||||
- e2ee-smoke
|
||||
- e2ee-deep
|
||||
- e2ee-cli
|
||||
steps:
|
||||
- name: Checkout selected ref
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ needs.validate_selected_ref.outputs.selected_sha }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup Node environment
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
pnpm-version: ${{ env.PNPM_VERSION }}
|
||||
install-bun: "true"
|
||||
|
||||
- name: Validate required QA credential env
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if [[ -z "${OPENAI_API_KEY:-}" ]]; then
|
||||
echo "Missing required OPENAI_API_KEY." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Build private QA runtime
|
||||
run: pnpm build
|
||||
|
||||
- name: Run Matrix live lane shard
|
||||
id: run_lane
|
||||
shell: bash
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
OPENCLAW_QA_REDACT_PUBLIC_METADATA: "1"
|
||||
OPENCLAW_QA_MATRIX_NO_REPLY_WINDOW_MS: "3000"
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
output_dir=".artifacts/qa-e2e/matrix-live-${{ matrix.profile }}-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
|
||||
echo "output_dir=${output_dir}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
pnpm openclaw qa matrix \
|
||||
--repo-root . \
|
||||
--output-dir "${output_dir}" \
|
||||
--provider-mode live-frontier \
|
||||
--model "${OPENCLAW_CI_OPENAI_MODEL}" \
|
||||
--alt-model "${OPENCLAW_CI_OPENAI_MODEL}" \
|
||||
--profile "${{ matrix.profile }}" \
|
||||
--fast \
|
||||
--fail-fast
|
||||
|
||||
- name: Upload Matrix QA shard artifacts
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: qa-live-matrix-${{ matrix.profile }}-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
path: ${{ steps.run_lane.outputs.output_dir }}
|
||||
retention-days: 14
|
||||
if-no-files-found: warn
|
||||
|
||||
run_live_telegram:
|
||||
name: Run Telegram live QA lane with Convex leases
|
||||
needs: [authorize_actor, validate_selected_ref]
|
||||
|
||||
110
.github/workflows/stale.yml
vendored
110
.github/workflows/stale.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
with:
|
||||
app-id: "2971289"
|
||||
private-key: ${{ secrets.GH_APP_PRIVATE_KEY_FALLBACK }}
|
||||
- name: Mark stale issues and pull requests (primary)
|
||||
- name: Mark stale unassigned issues and pull requests (primary)
|
||||
id: stale-primary
|
||||
continue-on-error: true
|
||||
uses: actions/stale@v10
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
days-before-pr-close: 3
|
||||
stale-issue-label: stale
|
||||
stale-pr-label: stale
|
||||
exempt-issue-labels: enhancement,maintainer,pinned,security,no-stale
|
||||
exempt-issue-labels: enhancement,maintainer,pinned,security,no-stale,bad-barnacle
|
||||
exempt-pr-labels: maintainer,no-stale,bad-barnacle
|
||||
operations-per-run: 2000
|
||||
ascending: true
|
||||
@@ -56,11 +56,59 @@ jobs:
|
||||
close-issue-message: |
|
||||
Closing due to inactivity.
|
||||
If this is still an issue, please retry on the latest OpenClaw release and share updated details.
|
||||
If you are absolutely sure it still happens on the latest release, open a new issue with fresh repro steps.
|
||||
If you are absolutely sure it still happens on the latest release, open a new issue with fresh steps to reproduce.
|
||||
close-issue-reason: not_planned
|
||||
close-pr-message: |
|
||||
Closing due to inactivity.
|
||||
If you believe this PR should be revived, post in #pr-thunderdome-dangerzone on Discord to talk to a maintainer.
|
||||
If you believe this PR should be revived, post in #clawtributors on Discord to talk to a maintainer.
|
||||
That channel is the escape hatch for high-quality PRs that get auto-closed.
|
||||
- name: Mark stale assigned issues (primary)
|
||||
id: assigned-issue-stale-primary
|
||||
continue-on-error: true
|
||||
uses: actions/stale@v10
|
||||
with:
|
||||
repo-token: ${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token }}
|
||||
days-before-issue-stale: 30
|
||||
days-before-issue-close: 10
|
||||
days-before-pr-stale: -1
|
||||
days-before-pr-close: -1
|
||||
stale-issue-label: stale
|
||||
exempt-issue-labels: enhancement,maintainer,pinned,security,no-stale,bad-barnacle
|
||||
operations-per-run: 2000
|
||||
ascending: true
|
||||
include-only-assigned: true
|
||||
remove-stale-when-updated: true
|
||||
stale-issue-message: |
|
||||
This assigned issue has been automatically marked as stale after 30 days of inactivity.
|
||||
Please add updates or it will be closed.
|
||||
close-issue-message: |
|
||||
Closing due to inactivity.
|
||||
If this is still an issue, please retry on the latest OpenClaw release and share updated details.
|
||||
If you are absolutely sure it still happens on the latest release, open a new issue with fresh steps to reproduce.
|
||||
close-issue-reason: not_planned
|
||||
- name: Mark stale assigned pull requests (primary)
|
||||
id: assigned-stale-primary
|
||||
continue-on-error: true
|
||||
uses: actions/stale@v10
|
||||
with:
|
||||
repo-token: ${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token }}
|
||||
days-before-issue-stale: -1
|
||||
days-before-issue-close: -1
|
||||
days-before-pr-stale: 27
|
||||
days-before-pr-close: 3
|
||||
stale-pr-label: stale
|
||||
exempt-pr-labels: maintainer,no-stale,bad-barnacle
|
||||
operations-per-run: 2000
|
||||
ascending: true
|
||||
include-only-assigned: true
|
||||
ignore-pr-updates: true
|
||||
remove-stale-when-updated: true
|
||||
stale-pr-message: |
|
||||
This assigned pull request has been automatically marked as stale after being open for 27 days.
|
||||
Please add updates or it will be closed.
|
||||
close-pr-message: |
|
||||
Closing due to inactivity.
|
||||
If you believe this PR should be revived, post in #clawtributors on Discord to talk to a maintainer.
|
||||
That channel is the escape hatch for high-quality PRs that get auto-closed.
|
||||
- name: Check stale state cache
|
||||
id: stale-state
|
||||
@@ -86,7 +134,7 @@ jobs:
|
||||
core.warning(`Failed to check stale state cache: ${message}`);
|
||||
core.setOutput("has_state", "false");
|
||||
}
|
||||
- name: Mark stale issues and pull requests (fallback)
|
||||
- name: Mark stale unassigned issues and pull requests (fallback)
|
||||
if: (steps.stale-primary.outcome == 'failure' || steps.stale-state.outputs.has_state == 'true') && steps.app-token-fallback.outputs.token != ''
|
||||
uses: actions/stale@v10
|
||||
with:
|
||||
@@ -97,7 +145,7 @@ jobs:
|
||||
days-before-pr-close: 3
|
||||
stale-issue-label: stale
|
||||
stale-pr-label: stale
|
||||
exempt-issue-labels: enhancement,maintainer,pinned,security,no-stale
|
||||
exempt-issue-labels: enhancement,maintainer,pinned,security,no-stale,bad-barnacle
|
||||
exempt-pr-labels: maintainer,no-stale,bad-barnacle
|
||||
operations-per-run: 2000
|
||||
ascending: true
|
||||
@@ -112,11 +160,57 @@ jobs:
|
||||
close-issue-message: |
|
||||
Closing due to inactivity.
|
||||
If this is still an issue, please retry on the latest OpenClaw release and share updated details.
|
||||
If you are absolutely sure it still happens on the latest release, open a new issue with fresh repro steps.
|
||||
If you are absolutely sure it still happens on the latest release, open a new issue with fresh steps to reproduce.
|
||||
close-issue-reason: not_planned
|
||||
close-pr-message: |
|
||||
Closing due to inactivity.
|
||||
If you believe this PR should be revived, post in #pr-thunderdome-dangerzone on Discord to talk to a maintainer.
|
||||
If you believe this PR should be revived, post in #clawtributors on Discord to talk to a maintainer.
|
||||
That channel is the escape hatch for high-quality PRs that get auto-closed.
|
||||
- name: Mark stale assigned issues (fallback)
|
||||
if: (steps.assigned-issue-stale-primary.outcome == 'failure' || steps.stale-state.outputs.has_state == 'true') && steps.app-token-fallback.outputs.token != ''
|
||||
uses: actions/stale@v10
|
||||
with:
|
||||
repo-token: ${{ steps.app-token-fallback.outputs.token }}
|
||||
days-before-issue-stale: 30
|
||||
days-before-issue-close: 10
|
||||
days-before-pr-stale: -1
|
||||
days-before-pr-close: -1
|
||||
stale-issue-label: stale
|
||||
exempt-issue-labels: enhancement,maintainer,pinned,security,no-stale,bad-barnacle
|
||||
operations-per-run: 2000
|
||||
ascending: true
|
||||
include-only-assigned: true
|
||||
remove-stale-when-updated: true
|
||||
stale-issue-message: |
|
||||
This assigned issue has been automatically marked as stale after 30 days of inactivity.
|
||||
Please add updates or it will be closed.
|
||||
close-issue-message: |
|
||||
Closing due to inactivity.
|
||||
If this is still an issue, please retry on the latest OpenClaw release and share updated details.
|
||||
If you are absolutely sure it still happens on the latest release, open a new issue with fresh steps to reproduce.
|
||||
close-issue-reason: not_planned
|
||||
- name: Mark stale assigned pull requests (fallback)
|
||||
if: (steps.assigned-stale-primary.outcome == 'failure' || steps.stale-state.outputs.has_state == 'true') && steps.app-token-fallback.outputs.token != ''
|
||||
uses: actions/stale@v10
|
||||
with:
|
||||
repo-token: ${{ steps.app-token-fallback.outputs.token }}
|
||||
days-before-issue-stale: -1
|
||||
days-before-issue-close: -1
|
||||
days-before-pr-stale: 27
|
||||
days-before-pr-close: 3
|
||||
stale-pr-label: stale
|
||||
exempt-pr-labels: maintainer,no-stale,bad-barnacle
|
||||
operations-per-run: 2000
|
||||
ascending: true
|
||||
include-only-assigned: true
|
||||
ignore-pr-updates: true
|
||||
remove-stale-when-updated: true
|
||||
stale-pr-message: |
|
||||
This assigned pull request has been automatically marked as stale after being open for 27 days.
|
||||
Please add updates or it will be closed.
|
||||
close-pr-message: |
|
||||
Closing due to inactivity.
|
||||
If you believe this PR should be revived, post in #clawtributors on Discord to talk to a maintainer.
|
||||
That channel is the escape hatch for high-quality PRs that get auto-closed.
|
||||
|
||||
lock-closed-issues:
|
||||
|
||||
32
.gitignore
vendored
32
.gitignore
vendored
@@ -97,6 +97,38 @@ USER.md
|
||||
# local tooling
|
||||
.serena/
|
||||
|
||||
# Local project-agent skill installs. Only repo-owned skills are visible by
|
||||
# default; promoting a new repo skill should require an intentional `git add -f`.
|
||||
.agents/skills/*
|
||||
!.agents/skills/blacksmith-testbox/
|
||||
!.agents/skills/blacksmith-testbox/**
|
||||
!.agents/skills/openclaw-ghsa-maintainer/
|
||||
!.agents/skills/openclaw-ghsa-maintainer/**
|
||||
!.agents/skills/openclaw-parallels-smoke/
|
||||
!.agents/skills/openclaw-parallels-smoke/**
|
||||
!.agents/skills/openclaw-pr-maintainer/
|
||||
!.agents/skills/openclaw-pr-maintainer/**
|
||||
!.agents/skills/openclaw-qa-testing/
|
||||
!.agents/skills/openclaw-qa-testing/**
|
||||
!.agents/skills/openclaw-release-maintainer/
|
||||
!.agents/skills/openclaw-release-maintainer/**
|
||||
!.agents/skills/openclaw-secret-scanning-maintainer/
|
||||
!.agents/skills/openclaw-secret-scanning-maintainer/**
|
||||
!.agents/skills/openclaw-test-heap-leaks/
|
||||
!.agents/skills/openclaw-test-heap-leaks/**
|
||||
!.agents/skills/openclaw-test-performance/
|
||||
!.agents/skills/openclaw-test-performance/**
|
||||
!.agents/skills/openclaw-testing/
|
||||
!.agents/skills/openclaw-testing/**
|
||||
!.agents/skills/optimizetests/
|
||||
!.agents/skills/optimizetests/**
|
||||
!.agents/skills/parallels-discord-roundtrip/
|
||||
!.agents/skills/parallels-discord-roundtrip/**
|
||||
!.agents/skills/security-triage/
|
||||
!.agents/skills/security-triage/**
|
||||
!.agents/skills/tag-duplicate-prs-issues/
|
||||
!.agents/skills/tag-duplicate-prs-issues/**
|
||||
|
||||
# Agent credentials and memory (NEVER COMMIT)
|
||||
/memory/
|
||||
.agent/*.json
|
||||
|
||||
12
AGENTS.md
12
AGENTS.md
@@ -29,6 +29,7 @@ Telegraph style. Root rules only. Read scoped `AGENTS.md` before subtree work.
|
||||
- Extension prod code: no core `src/**`, `src/plugin-sdk-internal/**`, other extension `src/**`, or relative outside package.
|
||||
- Core/tests: no deep plugin internals (`extensions/*/src/**`, `onboard.js`). Use `api.ts`, SDK facade, generic contracts.
|
||||
- Extension-owned behavior stays extension-owned: repair, detection, onboarding, auth/provider defaults, provider tools/settings.
|
||||
- Owner boundary: fix owner-specific behavior in the owner module. Shared/core gets generic seams only; no owner ids, dependency strings, defaults, migrations, or recovery policy. If a bug names an extension or its dependency, start in that extension and add a generic core seam only when multiple owners need it.
|
||||
- Legacy config repair: doctor/fix paths, not startup/load-time core migrations.
|
||||
- Core test asserting extension-specific behavior: move to owner extension or generic contract test.
|
||||
- New seams: backwards-compatible, documented, versioned. Third-party plugins exist.
|
||||
@@ -50,7 +51,8 @@ Telegraph style. Root rules only. Read scoped `AGENTS.md` before subtree work.
|
||||
- Extension tests: `pnpm test:extensions`, `pnpm test extensions`, `pnpm test extensions/<id>`.
|
||||
- Targeted tests: `pnpm test <path-or-filter> [vitest args...]`; never raw `vitest`.
|
||||
- Typecheck: `tsgo` lanes only (`pnpm tsgo*`, `pnpm check:test-types`); do not add `tsc --noEmit`, `typecheck`, `check:types`.
|
||||
- Format/lint: `pnpm format:check`/`pnpm format`; `pnpm lint*` lanes.
|
||||
- Formatting: use `oxfmt`, not Prettier. Prefer `pnpm format:check` / `pnpm format`; for targeted files use `pnpm exec oxfmt --check --threads=1 <files...>` or `pnpm exec oxfmt --write --threads=1 <files...>`.
|
||||
- Linting: use repo wrappers (`pnpm lint:*`, `scripts/run-oxlint.mjs`); do not invoke generic JS formatters/lints unless a repo script uses them.
|
||||
- Heavy checks: `OPENCLAW_LOCAL_CHECK=1`, mode `OPENCLAW_LOCAL_CHECK_MODE=throttled|full`; CI/shared use `OPENCLAW_LOCAL_CHECK=0`.
|
||||
- Local first. Use repo `pnpm` lanes before Blacksmith/Testbox. Remote only for parity-only failures, secrets/services, or explicit ask.
|
||||
|
||||
@@ -58,6 +60,7 @@ Telegraph style. Root rules only. Read scoped `AGENTS.md` before subtree work.
|
||||
|
||||
- Triage: list first, hydrate few. Use bounded `gh --json --jq`; avoid repeated full comment scans.
|
||||
- Automatic PR/issue discovery: skip maintainer-owned items unless directly relevant. Do not comment, close, label, retitle, rebase, fix up, or land them without Peter asking.
|
||||
- PR scan/triage: no unsolicited PR comments/reviews. Report in chat only unless explicitly asked, or a close/duplicate action needs a reason comment.
|
||||
- Search/dedupe: prefer `gh search issues 'repo:openclaw/openclaw is:open <terms>' --json number,title,state,updatedAt --limit 20`.
|
||||
- GitHub search boolean text is fussy. If `OR` queries return empty, split exact terms and search title/body/comments separately before concluding no hits.
|
||||
- PR shortlist: `gh pr list ...`; then `gh pr view <n> --json number,title,body,closingIssuesReferences,files,statusCheckRollup,reviewDecision`.
|
||||
@@ -85,7 +88,8 @@ Telegraph style. Root rules only. Read scoped `AGENTS.md` before subtree work.
|
||||
- extension tests: extension test typecheck/tests
|
||||
- public SDK/plugin contract: extension prod/test too
|
||||
- unknown root/config: all lanes
|
||||
- Before handoff/push: `pnpm check:changed`. Tests-only: `pnpm test:changed`. Full prod sweep: `pnpm check`.
|
||||
- Before handoff/push for code/test/runtime/config changes: `pnpm check:changed`. Tests-only: `pnpm test:changed`. Full prod sweep: `pnpm check`.
|
||||
- Docs/changelog-only and CI/workflow metadata-only changes are not changed-gate work by default. Use `git diff --check` plus the relevant formatter/docs/workflow sanity check; escalate to `pnpm check:changed` only when scripts, test config, generated docs/API, package metadata, or runtime/build behavior changed.
|
||||
- Rebase sanity: after a green `pnpm check:changed`, a clean rebase onto current
|
||||
`origin/main` does not require rerunning the full changed gate when the rebase
|
||||
has no conflicts and the branch diff is materially unchanged. Do a quick
|
||||
@@ -116,6 +120,7 @@ Telegraph style. Root rules only. Read scoped `AGENTS.md` before subtree work.
|
||||
## Tests
|
||||
|
||||
- Vitest. Colocated `*.test.ts`; e2e `*.e2e.test.ts`; example models `sonnet-4.6`, `gpt-5.4`.
|
||||
- Avoid brittle tests that grep workflow/docs strings for operator policy. Prefer executable behavior, parsed config/schema checks, or live run proof; put release/CI policy reminders in AGENTS/docs instead.
|
||||
- Clean timers/env/globals/mocks/sockets/temp dirs/module state; `--isolate=false` safe.
|
||||
- Hot tests: avoid per-test `vi.resetModules()` + heavy imports. Measure with `pnpm test:perf:imports <file>` / `pnpm test:perf:hotspots --limit N`.
|
||||
- Seam depth: pure helper/contract unit tests; one integration smoke per boundary.
|
||||
@@ -123,6 +128,7 @@ Telegraph style. Root rules only. Read scoped `AGENTS.md` before subtree work.
|
||||
- Prefer injection; if module mocking, mock narrow local `*.runtime.ts`, not broad barrels or `openclaw/plugin-sdk/*`.
|
||||
- Share fixtures/builders; delete duplicate assertions; assert behavior that can regress here.
|
||||
- Do not edit baseline/inventory/ignore/snapshot/expected-failure files to silence checks without explicit approval.
|
||||
- Do not run multiple independent `pnpm test`/Vitest commands concurrently in the same worktree. They can race on `node_modules/.experimental-vitest-cache` and fail with `ENOTEMPTY`. Use one grouped `pnpm test ...` invocation, run targeted lanes sequentially, or set distinct `OPENCLAW_VITEST_FS_MODULE_CACHE_PATH` values when true parallel Vitest processes are needed.
|
||||
- Test workers max 16. Memory pressure: `OPENCLAW_VITEST_MAX_WORKERS=1 pnpm test`.
|
||||
- Live: `OPENCLAW_LIVE_TEST=1 pnpm test:live`; verbose `OPENCLAW_LIVE_TEST_QUIET=0`.
|
||||
- Guide: `docs/help/testing.md`.
|
||||
@@ -131,7 +137,7 @@ Telegraph style. Root rules only. Read scoped `AGENTS.md` before subtree work.
|
||||
|
||||
- Docs change with behavior/API. Use docs list/read_when hints; docs links per `docs/AGENTS.md`.
|
||||
- Changelog user-facing only; pure test/internal usually no entry.
|
||||
- Changelog placement: active version `### Changes`/`### Fixes`; every added entry must include at least one `Thanks @author` attribution, using credited GitHub username(s). Never add `Thanks @steipete`.
|
||||
- Changelog placement: active version `### Changes`/`### Fixes`; every added entry must include at least one `Thanks @author` attribution, using credited GitHub username(s). Never add `Thanks @steipete` or `Thanks @codex`.
|
||||
- Changelog bullets are always single-line. No wrapping/continuation across multiple lines. Long entries stay on one long line so dedupe, PR-ref, and credit-audit tooling work and so the visual style stays uniform.
|
||||
|
||||
## Git
|
||||
|
||||
302
CHANGELOG.md
302
CHANGELOG.md
@@ -4,29 +4,150 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
## Unreleased
|
||||
|
||||
## 2026.4.25 (Unreleased)
|
||||
### Changes
|
||||
|
||||
- Control UI: polish the quick settings dashboard grid so common cards align across desktop, tablet, and mobile layouts without wasting horizontal space. Thanks @BunsDev.
|
||||
- Matrix/E2EE: add `openclaw matrix encryption setup` to enable Matrix encryption, bootstrap recovery, and print verification status from one setup flow. Thanks @gumadeiras.
|
||||
- Agents/compaction: add an opt-in `agents.defaults.compaction.maxActiveTranscriptBytes` preflight trigger that runs normal local compaction when the active JSONL grows too large, requiring transcript rotation so successful compaction moves future turns onto a smaller successor file instead of raw byte-splitting history. Thanks @vincentkoc.
|
||||
|
||||
### Fixes
|
||||
|
||||
- macOS Gateway: detect installed-but-unloaded LaunchAgent split-brain states during status, doctor, and restart, and re-bootstrap launchd supervision before falling back to unmanaged listener restarts. Fixes #67335, #53475, and #71060; refs #58890, #60885, and #70801. Thanks @ze1tgeist88, @dafacto, and @vishutdhar.
|
||||
- Plugins/install: stage bundled plugin runtime dependencies before Gateway startup and drain update restarts while preserving per-plugin isolation when pre-stage scan or install fails. Thanks @codex.
|
||||
- CLI/startup: read generated startup metadata from the bundled `dist` layout before falling back to live help rendering, so root/browser help and channel-option bootstrap stay on the fast path. Thanks @vincentkoc.
|
||||
- CLI/help: treat positional `help` invocations like `openclaw channels help` as help paths for startup gating, avoiding model/auth warmup while preserving positional arguments such as `openclaw docs help`. Thanks @gumadeiras.
|
||||
- Web search: route plugin-scoped web_search SecretRefs through the active runtime config snapshot so provider execution receives resolved credentials across app/runtime paths, including `plugins.entries.brave.config.webSearch.apiKey`. Fixes #68690. Thanks @VACInc.
|
||||
- Matrix/E2EE: stabilize recovery and broken-device QA flows while avoiding Matrix device-cleanup sync races that could leave shutdown-time crypto work running. Thanks @gumadeiras.
|
||||
- Cron: treat isolated run-level agent failures as job errors even when no reply payload is produced, synthesizing a safe error payload so model/provider failures increment error counters and trigger failure notifications instead of clearing as successful. Fixes #43604; carries forward #43631. Thanks @SPFAdvisors.
|
||||
- Cron: preserve exact `NO_REPLY` tool results from isolated jobs with empty final assistant turns as quiet successes instead of surfacing incomplete-turn errors. Fixes #68452; carries forward #68453. Thanks @anyech.
|
||||
- Cron: resolve failure alerts and failure-destination announcements against `session:<id>` targets before falling back to the creator session, so jobs created from group chats can notify the targeted direct session without cross-account routing errors. Refs #62777; carries forward #68535. Thanks @slideshow-dingo and @likewen-tech.
|
||||
- Cron: classify isolated runs as errors from structured embedded-run execution-denial metadata, with final-output marker fallback for `SYSTEM_RUN_DENIED`, `INVALID_REQUEST`, and approval-binding refusals, so blocked commands no longer appear green in cron history. Fixes #67172; carries forward #67186. Thanks @oc-gh-dr, @hclsys, and @1yihui.
|
||||
- Onboarding/GitHub Copilot: add manifest-owned `--github-copilot-token` support for non-interactive setup, including env fallback, tokenRef storage in ref mode, saved-profile reuse, and current Copilot default-model wiring. Refs #50002 and supersedes #50003. Thanks @scottgl9.
|
||||
- Gateway/install: add a validated `--wrapper`/`OPENCLAW_WRAPPER` service install path that persists executable LaunchAgent/systemd wrappers across forced reinstalls, updates, and doctor repairs instead of falling back to raw node/bun `ProgramArguments`. Fixes #69400. (#72445) Thanks @willtmc.
|
||||
- Plugins: fail plugin registration when loader-owned acceptance gates reject missing hook names or memory-only capability registration from non-memory plugins, surfacing the issue through plugin status and doctor instead of silently dropping the registration. Fixes #72459. Thanks @1fanwang and @amknight.
|
||||
- macOS Gateway: write launchd services with a state-dir `WorkingDirectory`, use a durable state-dir temp path instead of freezing macOS session `TMPDIR`, create that temp directory before bootstrap, and label abort-shaped launchd exits as `SIGABRT/abort` in status output. Fixes #53679 and #70223; refs #71848. Thanks @dlturock, @stammi922, and @palladius.
|
||||
- Exec approvals: accept runtime-owned `source: "allow-always"` and `commandText` allowlist metadata in gateway and node approval-set payloads so Control UI round-trips no longer fail with `unexpected property 'source'`. Fixes #60000; carries forward #60064. Thanks @sd1471123, @sharkqwy, and @luoyanglang.
|
||||
- Exec/node: skip approval-plan preparation for full-trust `host=node` runs so interpreter and script commands no longer fail with `SYSTEM_RUN_DENIED: approval cannot safely bind` when effective policy is `security=full` and `ask=off`. Fixes #48457 and duplicate #69251. Thanks @ajtran303, @jaserNo1, @Blakeshannon, @lesliefag, and @AvIsBeastMC.
|
||||
- Exec/node: synthesize a local approval plan when a paired node advertises `system.run` without `system.run.prepare`, unblocking approval-required `host=node` exec on current macOS companion nodes while preserving remote prepare for node hosts that support it. Fixes #37591 and duplicate #66839; carries forward #69725. Thanks @soloclz.
|
||||
- Memory/QMD: prefer QMD's `--mask` collection pattern flag so root memory indexing stays scoped to `MEMORY.md` instead of widening to every markdown file in the workspace. Thanks @codex.
|
||||
- Memory/doctor: treat the specific `gateway timeout after ...` gateway memory probe result as inconclusive instead of reporting embeddings not ready, while preserving warnings for explicit failures. Fixes #44426; carries forward #46576 with the Greptile review feedback applied. Thanks Cengiz (@ghost).
|
||||
- Gateway/memory: defer QMD startup for implicit non-default agents and scope memory runtime loading to the selected memory slot so Gateway boot and first memory recall avoid broad plugin runtime fanout. Thanks @vincentkoc.
|
||||
- Gateway/startup: keep core request handlers, setup wizard, and channel runtime helpers off the boot path until the first matching request, wizard run, or channel start, reducing no-plugin Gateway ready RSS and avoidable startup imports. Thanks @vincentkoc.
|
||||
- Gateway/startup: keep CLI outbound channel send dependencies as lazy request-time senders so Gateway boot no longer imports channel plugin registration just to construct default deps. Thanks @vincentkoc.
|
||||
- Gateway/startup: split lightweight HTTP auth helpers away from model-override helpers so Gateway bind no longer imports model catalog selection while wiring base HTTP routes. Thanks @vincentkoc.
|
||||
- Gateway/startup: lazy-load plugin HTTP route dispatch when active plugin routes exist so no-plugin Gateway boot skips plugin route runtime scope setup. Thanks @vincentkoc.
|
||||
- CLI/Gateway: use a parse-only config snapshot for plain `gateway status` reads and reuse same-path service config context so status no longer spends tens of seconds in full config validation before printing. Thanks @vincentkoc.
|
||||
- Lobster/Gateway: memoize repeated Ajv schema compilation before loading the embedded Lobster runtime so scheduled workflows and `llm.invoke` loops stop growing gateway heap on content-identical schemas. Fixes #71148. Thanks @cmi525, @vsolaz, and @vincentkoc.
|
||||
- Codex harness: normalize cached input tokens before session/context accounting so prompt cache reads are not double-counted in `/status`, `session_status`, or persisted `sessionEntry.totalTokens`. Fixes #69298. Thanks @richardmqq.
|
||||
- Hooks/session-memory: use the host local timezone for memory filenames, fallback timestamp slugs, and markdown headers instead of UTC dates. Fixes #46703. (#46721) Thanks @Astro-Han.
|
||||
- Feishu: extract quoted/replied interactive-card text across schema 1.0, schema 2.0, i18n, template-variable, and post-format fallback shapes without carrying broad generated/config churn from related parser experiments. (#38776, #60383, #42218, #45936) Thanks @lishuaigit, @lskun, @just2gooo, and @Br1an67.
|
||||
- Exec approvals: accept a symlinked `OPENCLAW_HOME` as the trusted approvals root while still rejecting symlinked `.openclaw` path components below it. (#64663) Thanks @FunJim.
|
||||
- Logging: add top-level `hostname`, flattened `message`, and available `agent_id`, `session_id`, and `channel` fields to file-log JSONL records for multi-agent filtering without removing existing structured log arguments. Fixes #51075. Thanks @stevengonsalvez.
|
||||
- ACP: route server logs to stderr before Gateway config/bootstrap work so ACP stdout remains JSON-RPC only for IDE integrations. Fixes #49060. Thanks @Hollychou924.
|
||||
- Logging: propagate internal request trace scopes through Gateway HTTP requests and WebSocket frames so file logs, diagnostic events, agent run traces, model-call traces, OTEL spans, and trusted provider `traceparent` headers share a correlatable `traceId` without logging raw request or model content. Fixes #40353. Thanks @liangruochong44-ui.
|
||||
- Diagnostics/OTEL: capture privacy-safe model-call request payload bytes, streamed response bytes, first-response latency, and total duration in diagnostic events, plugin hooks, stability snapshots, and OTEL model-call spans/metrics without logging raw model content. Fixes #33832. Thanks @wwh830.
|
||||
- Logging: write validated diagnostic trace context as top-level `traceId`, `spanId`, `parentSpanId`, and `traceFlags` fields in file-log JSONL records so traced requests and model calls are easier to correlate in log processors. Refs #40353. Thanks @liangruochong44-ui.
|
||||
- Logging/sessions: apply configured redaction patterns to persisted session transcript text and accept escaped character classes in safe custom redaction regexes, so transcript JSONL no longer keeps matching sensitive text in the clear. Fixes #42982. Thanks @panpan0000.
|
||||
- Providers/Ollama: honor `/api/show` capabilities when registering local models so non-tool Ollama models no longer receive the agent tool surface, and keep native Ollama thinking opt-in instead of enabling it by default. Fixes #64710 and duplicate #65343. Thanks @yuan-b, @netherby, @xilopaint, and @Diyforfun2026.
|
||||
- Providers/Ollama: read larger custom Modelfile `PARAMETER num_ctx` values from `/api/show` so auto-discovered Ollama models with expanded context no longer stay pinned to the base model context. Fixes #68344. Thanks @neeravmakwana.
|
||||
- Providers/Ollama: honor configured model `params.num_ctx` in native and OpenAI-compatible Ollama requests so local models can cap runtime context without rebuilding Modelfiles. Fixes #44550 and #52206; supersedes #69464. Thanks @taitruong, @armi0024, and @LokiCode404.
|
||||
- Providers/Ollama: forward whitelisted native Ollama model params such as `temperature`, `top_p`, and top-level `think` so users can disable API-level thinking or tune local models from config without proxy shims. Fixes #48010. Thanks @tangzhi, @pandego, @maweibin, @Adam-Researchh, and @EmpireCreator.
|
||||
- Providers/Ollama: expose native Ollama thinking effort levels so `/think max` is accepted for reasoning-capable Ollama models and maps to Ollama's highest supported `think` effort. Fixes #71584. Thanks @g0st1n.
|
||||
- Providers/Ollama: strip the active custom Ollama provider prefix before native chat and embedding requests, so custom provider ids like `ollama-spark/qwen3:32b` reach Ollama as the real model name. Fixes #72353. Thanks @maximus-dss and @hclsys.
|
||||
- Providers/Ollama: parse stringified native tool-call arguments before dispatch, preserving unsafe integer values so Ollama tool use receives structured parameters. Fixes #69735; supersedes #69910. Thanks @rongshuzhao and @yfge.
|
||||
- Providers/Ollama: skip ambient localhost discovery unless Ollama auth or meaningful config opts in, preventing unexpected probes to `127.0.0.1:11434` for users who are not using Ollama. Fixes #56939; supersedes #57116. Thanks @IanxDev and @tsukhani.
|
||||
- Providers/Ollama: skip implicit localhost discovery when a custom remote `api: "ollama"` provider is configured, while still treating `127/8` loopback hosts as local. Carries forward #43224. Thanks @issacthekaylon.
|
||||
- Providers/models: honor provider-level `contextWindow`, `contextTokens`, and `maxTokens` as defaults when resolving discovered models, so local Ollama and other self-hosted providers can cap all models without repeating per-model entries. Fixes #44786; carries forward #44955. Thanks @voltwake and @maweibin.
|
||||
- Providers/Ollama: move memory embeddings to Ollama's current `/api/embed` endpoint with batched `input` requests while preserving vector normalization and custom provider auth/header overrides. Fixes #39983. Thanks @sskkcc and @LiudengZhang.
|
||||
- Providers/Ollama: route local web search through Ollama's signed `/api/experimental/web_search` daemon proxy, use hosted `/api/web_search` directly for `ollama.com`, and keep `OLLAMA_API_KEY` scoped to cloud fallback auth. Fixes #69132. Thanks @yoon1012 and @hyspacex.
|
||||
- Providers/Ollama: accept OpenAI SDK-style `baseURL` as an alias for `baseUrl` across discovery, streaming, setup pulls, embeddings, and web search so remote Ollama hosts are not silently ignored. Fixes #62533; supersedes #62549. Thanks @Julien-BKK and @Linux2010.
|
||||
- Providers/Ollama: scope synthetic local auth and embedding bearer headers to declared Ollama host boundaries so cloud keys are not sent to local/self-hosted embedding endpoints and remote/cloud Ollama endpoints no longer receive the `ollama-local` marker as if it were a real token. Supersedes #69261 and #69857; refs #43945. Thanks @hyspacex, @maxramsay, and @Meli73.
|
||||
- Providers/Ollama: resolve custom-named local Ollama providers such as `ollama-remote` through the Ollama synthetic-auth hook so subagents no longer miss `ollama-local` auth and silently fall back to cloud models. Fixes #43945. Thanks @Meli73 and @maxramsay.
|
||||
- Providers/Ollama: add provider-scoped model request timeouts, thread them through guarded fetch connect/header/body/abort handling, and document `params.keep_alive` for cold local models so first-turn Ollama loads no longer require global agent timeout changes. Fixes #64541 and #68796; supersedes #65143 and #66511. Thanks @LittleJakub, @Juankcba, @uninhibite-scholar, and @yfge.
|
||||
- Providers/Ollama: preserve explicit configured model input modalities when merging discovered provider metadata so custom vision models keep image support instead of silently dropping attachments. Fixes #39690; carries forward #39785. Thanks @Skrblik and @Mriris.
|
||||
- Providers/Ollama: estimate native Ollama transcript usage when `/api/chat` omits prompt/eval counters while preserving exact zero counters, keeping local model runs visible in usage surfaces. Carries forward #39112. Thanks @TylonHH.
|
||||
- Agents/Ollama: retry native Ollama turns that finish without user-visible text, including unsigned thinking-only responses, so constrained reasoning turns can continue instead of surfacing an empty reply. Carries forward #66552 and #61223. Thanks @yfge and @L3G.
|
||||
- Docs/Ollama: expand setup recipes for local, LAN, cloud, multi-host, web search, embeddings, thinking control, and large-context troubleshooting. Thanks @codex.
|
||||
- Providers/PDF/Ollama: add bounded network timeouts for Ollama model pulls and native Anthropic/Gemini PDF analysis requests so unresponsive provider endpoints no longer hang sessions indefinitely. Fixes #54142; supersedes #54144 and #54145. Thanks @jinduwang1001-max and @arkyu2077.
|
||||
- LLM Task/Ollama: accept model overrides that already include the selected provider prefix, avoiding doubled ids such as `ollama/ollama/llama3.2:latest`, and live-verify local Ollama JSON tasks return parsed output. Fixes #50052. Thanks @ralphy-maplebots and @Hollychou924.
|
||||
- Memory/doctor: treat Ollama memory embeddings as key-optional so `openclaw doctor` no longer warns about a missing API key when the gateway reports embeddings are ready. Fixes #46584. Thanks @fengly78.
|
||||
- Agents/Ollama: apply provider-owned replay turn normalization to native Ollama chat so Cloud models no longer reject non-alternating replay history in agent/Gateway runs. Fixes #71697. Thanks @ismael-81.
|
||||
- Control UI/Ollama: show the resolved configured thinking default in chat and session thinking dropdowns so inherited `adaptive`/per-model thinking config no longer appears as `Default (off)` or a generic inherit value. Fixes #72407. Thanks @NotecAG.
|
||||
- Agents/Ollama: validate explicit `--thinking max` against catalog-discovered Ollama reasoning metadata so local agent runs accept the same native thinking levels shown in the model catalog. Fixes #71584. Thanks @g0st1n.
|
||||
- CLI/models: include explicitly configured provider models in `openclaw models list --provider <id>` without requiring the full catalog path, so configured Ollama models are visible. Fixes #65207. Thanks @drzeast-png.
|
||||
- Docker/QA: add observability coverage to the normal Docker aggregate so QA-lab OTEL and Prometheus diagnostics run inside Docker. Thanks @vincentkoc.
|
||||
- Auto-reply: poison inbound message dedupe after replay-unsafe provider/runtime failures so retries stay safe before visible progress but cannot duplicate messages after block output, tool side effects, or session progress. Fixes #69303; keeps #58549 and #64606 as duplicate validation. Thanks @martingarramon, @NikolaFC, and @zeroth-blip.
|
||||
- Agents/model fallback: jump directly to a known later live-session model redirect instead of walking unrelated fallback candidates, while preserving the already-landed live-session/fallback loop guard. Fixes #57471; related loop family already closed via #58496. Thanks @yuxiaoyang2007-prog.
|
||||
- Gateway/Bonjour: keep @homebridge/ciao cancellation handlers registered across advertiser restarts so late probing cancellations cannot crash Linux and other mDNS-churned gateways. Thanks @codex.
|
||||
- Plugins/startup: load the default `memory-core` slot during Gateway startup when permitted so active-memory recall can call `memory_search` and `memory_get` without requiring an explicit `plugins.slots.memory` entry, while preserving `plugins.slots.memory: "none"`. Thanks @codex.
|
||||
- Plugins/CLI: prefer native require for compiled bundled plugin JavaScript before jiti so read-only config, status, device, and node commands avoid unnecessary transform overhead on slow hosts. Fixes #62842. Thanks @Effet.
|
||||
- Plugins/compat: inventory doctor-side deprecation migrations separately from runtime plugin compatibility so release sweeps preserve needed repairs while enforcing dated removal windows. Thanks @vincentkoc.
|
||||
- Plugins/compat: add missing dated compatibility records for legacy extension-api, memory registration, provider hook/type aliases, runtime aliases, channel SDK helpers, and approval/test utility shims. Thanks @vincentkoc.
|
||||
- Plugins/CLI: refresh the persisted registry after managed plugin files are removed so ClawHub uninstall cannot leave stale `plugins list` entries. Thanks @codex.
|
||||
- Plugins/CLI: make plugin install and uninstall config writes conflict-aware, clear stale denylist entries on explicit reinstall/removal, and delete managed plugin files only after config/index commit succeeds. Thanks @codex.
|
||||
- Plugins: fail `plugins update` when tracked plugin or hook updates error, keep bundled runtime-dependency repair behind restrictive allowlists, and reject package installs with unloadable extension entries. Thanks @codex.
|
||||
- WebChat/Control UI: support non-video file attachments in chat uploads while preserving the existing image attachment path and MIME-sniff fallback for generic image uploads. (#70947) Thanks @IAMSamuelRodda.
|
||||
- Skills/memory: restore Chokidar v5 hot reloads by watching concrete skill and memory roots with filters, including SKILL.md removals and deleted skill folders without broad workspace recursion. Fixes #27404, #33585, and #41606. Thanks @shelvenzhou, @08820048, and @rocke2020.
|
||||
- Gateway/chat: keep duplicate attachment-backed `chat.send` retries with the same idempotency key on the documented in-flight path so aborts still target the real active run. Fixes #70139. Thanks @Feelw00.
|
||||
- Gateway/session rows: report the same config-resolved thinking default that runtime sessions use, including global and per-agent defaults, so Control UI and TUI default labels stay aligned. (#71779, #70981, #71033, #70302) Thanks @chen-zhang-cs-code, @SymbolStar, and @cholaolu-boop.
|
||||
- Plugins: share package entrypoint resolution between install and discovery, reject mismatched `runtimeExtensions`, and cache bundled runtime-dependency manifest reads during scans. Thanks @codex.
|
||||
- WhatsApp/Web: keep quiet but healthy linked-device sessions connected by basing the watchdog on WhatsApp Web transport activity, while retaining a longer app-silence cap so frame activity cannot mask a stuck session forever. Fixes #70678; carries forward the focused #71466 approach and keeps #63939 as related configurable-timeout follow-up. Thanks @vincentkoc and @oromeis.
|
||||
- Discord/gateway: count failed health-monitor restart attempts toward cooldown and hourly caps, and evict stale account lifecycle state during channel reloads so repeated Discord gateway recovery cannot loop on old status. Fixes #38596. (#40413) Thanks @jellyAI-dev and @vashquez.
|
||||
- Cron/context engine: run isolated cron jobs under run-scoped context-engine session keys so prior runs of the same job are not inherited unless the job is explicitly session-bound. (#72292) Thanks @jalehman.
|
||||
- Control UI: localize command palette labels, categories, skill shortcuts, footer hints, and connect-command copy labels while preserving localized command palette search matching. (#61130, #61119) Thanks @rubensfox20.
|
||||
- Plugins/memory-lancedb: request float embedding responses from OpenAI-compatible servers so local providers that default SDK requests to base64 no longer return dimension-mismatched LanceDB vectors while preserving configured dimensions. Fixes #45982. (#59048, #46069, #45986) Thanks @deep-introspection, @xiaokhkh, @caicongyang, and @thiswind.
|
||||
- Plugins/memory-core: respect configured memory-search embedding concurrency during non-batch indexing so local Ollama embedding backends can serialize indexing instead of flooding the server. Fixes #66822. (#66931) Thanks @oliviareid-svg and @LyraInTheFlesh.
|
||||
- Docker/update smoke: keep the package-derived update-channel fixture on package-shipped files and make its UI build stub create the asset the updater verifies. Thanks @vincentkoc.
|
||||
- Gateway/models: repair legacy `models.providers.*.api = "openai"` config values to `openai-completions`, and skip providers with future stale API enum values during startup instead of bricking the gateway. Fixes #72477. (#72542) Thanks @JooyoungChoi14 and @obviyus.
|
||||
|
||||
## 2026.4.26
|
||||
|
||||
### Fixes
|
||||
|
||||
- Plugins/CLI: let flag-driven `openclaw channels add` install the selected channel plugin from its default source without opening an interactive prompt, fixing published npm Telegram setup in stdin-closed automation. Thanks @codex.
|
||||
- Onboarding/setup: keep first-run config reads, plugin compatibility notices, and post-model sanity checks on cold metadata paths unless the user chooses to browse all models, avoiding full plugin/runtime catalog work between prompts. Thanks @shakkernerd.
|
||||
- Onboarding/auth: run manifest-owned provider auth choices through scoped setup providers so selecting OpenAI Codex browser/device auth no longer loads every provider runtime before OAuth starts. Thanks @shakkernerd.
|
||||
- Onboarding/auth: keep the post-auth default-model policy lookup on manifest/setup metadata so the next prompt appears without loading broad provider runtime. Thanks @shakkernerd.
|
||||
- Onboarding/models: keep skip-auth and provider-scoped model picker prompts off the full global model catalog path, and cache provider catalog hook resolution so setup no longer stalls after auth on large plugin registries. Thanks @shakkernerd.
|
||||
- Gateway/Bonjour: suppress known @homebridge/ciao cancellation and network assertion failures through scoped process handlers so malformed mDNS packets or restricted VPS networking disable/restart Bonjour instead of crashing the gateway. Fixes #67578. Thanks @zenassist26-create.
|
||||
- Discord: keep late clicks on already-resolved exec approval buttons quiet when elevated mode auto-resolved the request, while still surfacing real approval submission failures. Fixes #66906. Thanks @rlerikse.
|
||||
- Telegram: send a fresh final message for long-lived preview-streamed replies so the visible Telegram timestamp reflects completion time instead of the preview creation time. Thanks @rubencu.
|
||||
|
||||
## 2026.4.25
|
||||
|
||||
### Highlights
|
||||
|
||||
- Voice replies get a full TTS upgrade: `/tts latest`, chat-scoped auto-TTS controls, personas, per-agent/per-account overrides, and new Azure Speech, Xiaomi, Local CLI, Inworld, Volcengine, and ElevenLabs v3 provider coverage. Thanks @leonchui, @zoujiejun, @solar2ain, @cshape, @xuruiray, @itsuzef, and @barronlroth.
|
||||
- Plugin startup and install paths move to the cold persisted registry, cutting broad manifest scans while making plugin update, repair, provider discovery, and install metadata more deterministic. Thanks @vincentkoc and @shakkernerd.
|
||||
- OpenTelemetry coverage expands across model calls, token usage, tool loops, harness runs, exec processes, outbound delivery, context assembly, and memory pressure with bounded low-cardinality attributes. Thanks @vincentkoc, @jlapenna, @Lidang-Jiang, and @oc-factus.
|
||||
- Browser automation gets safer tab URLs, iframe-aware role snapshots, CDP readiness tuning, headless one-shot launch, and deeper browser doctor probes for slow hosts. Thanks @beat843796 and @BenediktSchackenberg.
|
||||
- Control UI and setup flows add PWA/Web Push support, Crestodian first-run repair, TUI setup, context mode selection, and a shorter startup greeting. Thanks @eduardocruz, @SebTardif, and @kevinlin-openai.
|
||||
- Install/update hardening covers Windows, macOS, Linux, Docker, bundled plugin runtime deps, Node service restarts, LaunchAgent token rotation, and mixed-version gateway verification. Thanks @Kobevictor, @igormf, @abhinas90, @jsompis, @Solvely-Colin, and @gucasbrg.
|
||||
|
||||
### Changes
|
||||
|
||||
- Codex/agent: translate `--thinking minimal` to `low` for modern Codex models (gpt-5.5, gpt-5.4, gpt-5.4-mini, gpt-5.2) at request build time so the first turn is accepted instead of paying a wasted call + retry-with-low fallback. Older Codex models still receive `minimal` directly. Fixes #71946. Thanks @hclsys.
|
||||
- TTS/WhatsApp: add `/tts latest` read-aloud support with duplicate suppression and `/tts chat on|off|default` session-scoped auto-TTS overrides, completing the on-demand voice-note UX for current-chat replies. Fixes #66032.
|
||||
- Plugins/tokenjuice: bump the bundled tokenjuice runtime to 0.6.3. Thanks @vincentkoc.
|
||||
- TTS/agents: allow `agents.list[].tts` to override global `messages.tts` for per-agent voices while keeping shared provider credentials and preferences in the existing TTS config surface.
|
||||
- TTS/agents: make `/tts audio`, `/tts status`, and the `tts` agent tool honor the active `agents.list[].tts` voice/provider override.
|
||||
- TTS/channels: resolve channel and account TTS overrides generically, enabling Feishu and QQBot accounts to deep-merge `channels.<channel>.accounts.<id>.tts` over global and per-agent TTS config. Thanks @sahilsatralkar.
|
||||
- TTS/agents: allow `agents.list[].tts` to override global `messages.tts` for per-agent voices, and make `/tts audio`, `/tts status`, and the `tts` agent tool honor the active voice/provider override while keeping shared provider credentials and preferences in the existing TTS config surface.
|
||||
- Providers/Azure Speech: add Azure Speech as a bundled TTS provider with Speech-resource auth, voice listing, SSML escaping, native Ogg/Opus voice-note output, and telephony output. (#51776) Thanks @leonchui.
|
||||
- Browser automation: add a CDP-native role snapshot fallback with iframe-aware refs, cursor-clickable detection, target attach preparation, and `openclaw browser doctor --deep` live snapshot probing.
|
||||
- CLI/image generation: expose generic `--background` on `openclaw infer image generate` and `openclaw infer image edit`, keep `--openai-background` as an OpenAI alias, and let fal image generation honor `--output-format png|jpeg`. Thanks @steipete.
|
||||
- Google Meet: add calendar-backed attendance export workflows, export manifests, dry-run previews, and tool parity for meeting records.
|
||||
- Control UI: add PWA install support and Web Push notifications for Gateway chat. (#44590) Thanks @eduardocruz.
|
||||
- Browser automation: add safe tab URLs in agent responses plus a CDP-native role snapshot fallback with iframe-aware refs, cursor-clickable detection, target attach preparation, and `openclaw browser doctor --deep` live snapshot probing.
|
||||
- CLI/image generation: expose generic `--background` on `openclaw infer image generate` and `openclaw infer image edit`, keep `--openai-background` as an OpenAI alias, and let fal image generation honor `--output-format png|jpeg`.
|
||||
- Browser/config: allow local managed Chrome launch discovery and post-launch CDP readiness timeouts to be raised for slower hosts such as Raspberry Pi. Fixes #66803. Thanks @beat843796.
|
||||
- Discord: allow `channels.discord.voice.model` to override the LLM used for voice channel responses while keeping STT and TTS on their existing media settings. (#64368) Thanks @mrdavey.
|
||||
- Browser/CLI: add `openclaw browser start --headless` as a one-shot local managed browser launch override without rewriting persisted browser config. Thanks @BenediktSchackenberg.
|
||||
- CLI/Crestodian: open interactive Crestodian in the full OpenClaw TUI shell instead of a basic readline prompt.
|
||||
- CLI/Crestodian: shorten the startup greeting to the active planner/model, config state, Gateway probe result, and next debug action instead of dumping every discovered backend.
|
||||
- CLI/Crestodian/TUI: add the first-run setup helper, local planner fallback, full-TUI interactive Crestodian, startup progress indicators, context mode selector, and a shorter startup greeting. (#71720, #71760) Thanks @SebTardif and @kevinlin-openai.
|
||||
- Plugins: migrate the local plugin registry automatically during package install/update, keeping install metadata in the plugin index while indexing existing plugin manifests for the new cold registry path. Thanks @vincentkoc and @shakkernerd.
|
||||
- Plugins/doctor: make `openclaw doctor --fix` refresh the plugin index and cold registry index when needed without treating plugin install records as authored config. Thanks @vincentkoc and @shakkernerd.
|
||||
- Plugins/hooks: add before-agent-finalize hooks, cron `jobId` hook context, bounded native permission fingerprints, and Codex MCP hook relay support. (#71765, #71758, #71707) Thanks @vincentkoc and @pashpashpash.
|
||||
- Plugins/tokenjuice: bump the bundled tokenjuice runtime to 0.6.3. Thanks @vincentkoc.
|
||||
- Diagnostics/OTEL: align model-call GenAI span attributes with OpenTelemetry stability opt-in semantics, keeping legacy `gen_ai.system` by default while emitting `gen_ai.provider.name` under `OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_latest_experimental`. Thanks @vincentkoc.
|
||||
- Diagnostics/OTEL: support signal-specific OTLP endpoint overrides for traces, metrics, and logs via config or standard OTEL environment variables. Thanks @vincentkoc.
|
||||
- Diagnostics/OTEL: emit bounded telemetry exporter health diagnostics for startup and log-export failures without exporting raw error text. Thanks @vincentkoc.
|
||||
- Diagnostics/OTEL: export agent harness lifecycle telemetry as bounded `openclaw.harness.run` spans and `openclaw.harness.duration_ms` metrics so QA-lab, Codex, and future harnesses share one trace shape. Thanks @vincentkoc.
|
||||
- Diagnostics/trace: propagate W3C `traceparent` headers from trusted model-call trace context to provider transports while replacing caller-supplied traceparent values. Thanks @vincentkoc.
|
||||
- Diagnostics/Prometheus: add a bundled `diagnostics-prometheus` plugin with a protected gateway scrape route for low-cardinality diagnostics metrics. Thanks @vincentkoc.
|
||||
- Plugins/CLI: add `openclaw plugins registry` for explicit persisted-registry inspection and `--refresh` repair without making normal startup rescan plugin locations. Thanks @vincentkoc.
|
||||
- Plugins/CLI: make `openclaw plugins list` read the cold persisted registry snapshot by default, leaving module-aware diagnostics to `plugins doctor` and `plugins inspect`. Thanks @vincentkoc.
|
||||
- Plugins/startup: move gateway startup plugin planning onto the versioned cold registry index, with postinstall repair for older registry files that predate startup metadata. Thanks @vincentkoc.
|
||||
@@ -39,9 +160,13 @@ Docs: https://docs.openclaw.ai
|
||||
- Providers/plugins: keep onboarding and auth-choice setup lists on cold manifest/install metadata and add Provider Index install metadata for not-yet-installed provider plugins. Thanks @vincentkoc.
|
||||
- Providers/plugins: keep provider setup guidance and configure auth imports on cold manifest metadata, with a regression guard against static provider-runtime imports on setup/configure list paths. Thanks @vincentkoc.
|
||||
- CLI/capabilities: keep capability command registration from importing the models auth runtime until `model auth login` actually runs. Thanks @vincentkoc.
|
||||
- CLI/configure: keep web-search configure prompts on cold plugin registry metadata until the user chooses managed search setup. Thanks @vincentkoc.
|
||||
- Plugins/chat commands: refresh the persisted plugin registry after `/plugins enable` and `/plugins disable`, matching the CLI mutation path. Thanks @vincentkoc.
|
||||
- Plugins/compat: mark `OPENCLAW_DISABLE_PERSISTED_PLUGIN_REGISTRY` as a deprecated break-glass switch and point operators at registry repair instead. Thanks @vincentkoc.
|
||||
- Plugins/compat: expand the central compatibility registry with dated owners, replacements, and maximum three-month removal targets for legacy SDK, manifest, setup, registry-migration, and agent-runtime surfaces. Thanks @vincentkoc.
|
||||
- Plugins/registry: ignore stale persisted registry reads when plugin policy no longer matches current config, and stamp generated registry files with a do-not-edit warning. Thanks @vincentkoc.
|
||||
- Config/plugins: keep plugin command-alias validation on cold manifest metadata instead of importing the runtime alias resolver. Thanks @vincentkoc.
|
||||
- Security/plugins: keep web-search credential presence checks on cold config, env, and manifest metadata instead of importing web-search provider runtime. Thanks @vincentkoc.
|
||||
- Diagnostics/OTEL: surface provider request identifiers as bounded hashes on model-call diagnostics and span events, without exporting raw request IDs or metric labels. Thanks @Lidang-Jiang and @vincentkoc.
|
||||
- Plugins/diagnostics: add metadata-only `model_call_started` and `model_call_ended` hooks for provider/model call telemetry without exposing prompts, responses, headers, request bodies, or raw provider request IDs. Thanks @vincentkoc.
|
||||
- Diagnostics/OTEL: emit bounded context assembly diagnostics and export `openclaw.context.assembled` spans with prompt/history sizes but no prompt, history, response, or session-key content. Thanks @vincentkoc.
|
||||
@@ -56,6 +181,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Diagnostics/OTEL: keep model-usage span GenAI provider attributes aligned with the existing semantic-convention opt-in policy, using legacy `gen_ai.system` unless latest experimental GenAI conventions are enabled. Thanks @vincentkoc.
|
||||
- Diagnostics/OTEL: keep `gen_ai.request.model` present on GenAI token usage metrics with a bounded `unknown` fallback when model usage events do not include a model. Thanks @vincentkoc.
|
||||
- Docs/OTEL: document the GenAI token and model-call duration metrics, model-usage span attributes, and `OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_latest_experimental` provider-attribute behavior. Thanks @vincentkoc.
|
||||
- Docs: refresh the MCP, model provider, doctor, troubleshooting, BlueBubbles, media generation, TTS, subagents, skills, cron/tasks, exec approvals, and voice-call guides with structured Steps, Tabs, and Accordion content.
|
||||
- Diagnostics/trace: add an internal traceparent propagation helper that only formats trusted dispatcher metadata, keeping plugin-emitted diagnostic traces out of outbound propagation by default. Thanks @vincentkoc.
|
||||
- Diagnostics/OTEL: add bounded outbound message delivery lifecycle diagnostics and export them as low-cardinality delivery spans/metrics without message body, recipient, room, or media-path data. (#71471) Thanks @vincentkoc and @jlapenna.
|
||||
- Diagnostics/OTEL: emit bounded exec-process diagnostics and export them as `openclaw.exec` spans without exposing command text, working directories, or container identifiers. (#71451) Thanks @vincentkoc and @jlapenna.
|
||||
@@ -76,10 +202,32 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- Agents/subagents: deliver completed yielded-subagent results back to no-thread requester routes via direct fallback when the dormant parent announce turn produces no visible reply, and add QA-lab coverage for the regression. Thanks @vincentkoc.
|
||||
- Gateway/Tailscale: let Tailscale-authenticated Control UI operator sessions with browser device identity skip the device-pairing round trip while still rejecting device-less and node-role connections. Refs #71986. Thanks @jokedul.
|
||||
- Doctor: honor `OPENCLAW_SERVICE_REPAIR_POLICY=external` by reporting gateway service health while skipping service install/start/restart/bootstrap, supervisor rewrites, and legacy service cleanup for externally managed environments. Thanks @shakkernerd.
|
||||
- CLI/update: run package post-update doctor with `--fix` so package updates repair config migrations before restart. Thanks @shakkernerd.
|
||||
- CLI/update: retry failed npm global updates with `--omit=optional` and ignore the superseded first failure when the fallback succeeds. Thanks @shakkernerd.
|
||||
- Plugins/uninstall: migrate and reset `plugins.slots.contextEngine` alongside memory slots when plugin ids change or selected plugins are removed. Thanks @shakkernerd.
|
||||
- Agents/Discord: keep raw `Agent failed before reply` runner failures out of Discord group/channel chats and show detailed runner errors in direct chats only when `/verbose` is enabled. Thanks @codex.
|
||||
- UI/Windows: quote resolved pnpm `.cmd` launcher paths before spawning UI install/build/test commands so Node installs under `C:\Program Files` no longer fail as `C:\Program`. Fixes #45275. Thanks @Kobevictor, @stoppieboy, and @iubns.
|
||||
- Codex/agent: translate `--thinking minimal` to `low` for modern Codex models (gpt-5.5, gpt-5.4, gpt-5.4-mini, gpt-5.2) at request build time so the first turn is accepted instead of paying a wasted call + retry-with-low fallback. Older Codex models still receive `minimal` directly. Fixes #71946. Thanks @hclsys.
|
||||
- Plugins/uninstall: remove tracked plugin files from their recorded managed extensions root even when the current state directory points somewhere else, so `openclaw plugins uninstall --force` does not leave the plugin discoverable. Thanks @shakkernerd.
|
||||
- Agents/runtime: add `agentRuntime.id` as the canonical config key, migrate legacy runtime-policy configs with `openclaw doctor --fix`, route canonical Anthropic models through `claude-cli` without passing CLI backend aliases to embedded harness selection, and load CLI backend owner plugins before channel startup. Fixes #71957. Thanks @WolvenRA.
|
||||
- CLI/update: guard Windows scheduled-task stops by state and timeout so auto-update restart cannot hang indefinitely on `schtasks /End` before stale-listener cleanup. Fixes #69970. Thanks @yangswld and @sherlock-huang.
|
||||
- Windows install/Lobster: execute `pnpm.exe` directly when `npm_execpath` points at the native pnpm binary, add an installed-package fallback for the Lobster embedded runtime, and include the Lobster runner regression test in Windows CI. Fixes #69456. Thanks @igormf.
|
||||
- Gateway/install: refresh loaded gateway service installs when the current service embeds stale gateway auth instead of returning already-installed, avoiding LaunchAgent token-mismatch loops after token rotation. Fixes #70752. Thanks @hyspacex.
|
||||
- Update: ignore bundled plugin `.openclaw-install-stage` directories during global install verification and packaged dist pruning so leftover runtime-dep staging files do not turn successful updates into `unexpected packaged dist file` failures. Fixes #71752. Thanks @waynegault.
|
||||
- CLI/update: fail package updates when post-update plugin sync fails and refresh legacy npm plugin install records before trusting unchanged artifacts, preventing successful updates from restarting with stale or failed plugin state. Thanks @vincentkoc and @shakkernerd.
|
||||
- Release/update: reject pre-populated bundled plugin `.openclaw-install-stage` directories, including mixed-case path variants, before package inventory generation so release tarballs cannot ship poisoned runtime-dependency staging debris. Fixes #71752. Thanks @hclsys.
|
||||
- Node runtime: keep node-host retry timers alive across Gateway restarts and exit on terminal credential pauses so supervised nodes do not become silent zombies. Fixes #69800. Thanks @meroli28.
|
||||
- Gateway/plugins: stop persisted WhatsApp auth state from activating bundled channel runtime-dependency repair during startup when `channels.whatsapp` is absent, avoiding npm/git stalls on packaged Linux installs. Fixes #71994. Thanks @xiao398008.
|
||||
- Gateway/device tokens: enforce caller-scope containment inside token rotation and revocation so pairing-only sessions cannot mutate higher-scope operator tokens. Fixes #71990. Thanks @coygeek.
|
||||
- Plugins/channels: keep security checks, thread-binding placement, provider summaries, health formatting, and message action labels on read-only or already-loaded channel metadata instead of importing full channel runtime. Thanks @shakkernerd.
|
||||
- Plugins/status: keep config-only channel labels and status security summaries from importing plugin runtime modules just to render metadata. Thanks @shakkernerd.
|
||||
- Sessions/channels: stop group-session metadata from loading bundled channel runtime just to classify `#channel` subjects, using only already-loaded channel capabilities on that path. Thanks @shakkernerd.
|
||||
- Plugins/channels: keep native command and native skill `auto` defaults on static channel metadata so config, audit, and command-list checks do not load channel runtime just to read those defaults. Thanks @shakkernerd.
|
||||
- CLI/channels: keep channel remove selection and all-channel capabilities summaries on read-only plugin metadata, loading channel runtime only for the selected mutation path. Thanks @shakkernerd.
|
||||
- CLI/models: keep Provider Index preview rows out of `models list --all --provider <id>` when the owning provider plugin is disabled, preserving config authority for cold catalog fallbacks. Thanks @shakkernerd.
|
||||
- CLI/model runs: keep `openclaw infer model run` on explicit OpenRouter models from loading the full provider catalog or inheriting chat-agent silent-reply policy, restoring non-empty one-shot probe output. Fixes #68791. Thanks @limpredator.
|
||||
- Installer/macOS: rerun Homebrew install steps without the gum spinner when raw-mode ioctl failures occur, and avoid claiming `node@24` was installed when the Homebrew keg binary is missing. Fixes #70411. Thanks @1fanwang and @dad-io.
|
||||
- Installer: load nvm before Node.js detection so `curl | bash` installs respect nvm-managed Node instead of stale system Node. Fixes #49556. Thanks @heavenlxj.
|
||||
@@ -88,13 +236,15 @@ Docs: https://docs.openclaw.ai
|
||||
- Installer: warn when multiple npm global roots contain OpenClaw installs, showing active Node/npm/openclaw plus each install path and version so stale version-manager installs are visible. Fixes #40839. Thanks @zhixianio.
|
||||
- Cron/tasks: recover completed cron task ledger records from durable run logs and job state before marking them `lost`, reducing false `backing session missing` audit errors for isolated cron runs and keeping offline CLI audit from treating its empty local cron active-job set as authoritative. Fixes #71963.
|
||||
- Docker: copy patched dependency files into runtime images so downstream `pnpm install` layers keep working. Fixes #69224. Thanks @gucasbrg.
|
||||
- Package: include patched dependency files in the published npm package so downstream installs can resolve `patchedDependencies`. (#69224) Thanks @gucasbrg and @vincentkoc.
|
||||
- Plugins/channels: treat malformed bundled channel plugin loaders that return `undefined` as unavailable instead of crashing config and help paths. Fixes #69044. Thanks @frankhli843 and @vincentkoc.
|
||||
- Scripts/watch: show corrupted dependency package-config recovery guidance when `gateway:watch` fails during watcher startup, without double-logging unrelated import failures. (#58780) Thanks @roytong9 and @vincentkoc.
|
||||
- Signal: read signal-cli RPC, health checks, and SSE events through Node's HTTP client so Node 24/25 fetch regressions do not break Signal sends or inbound events. Fixes #51716 and #53040. Thanks @Barukimang, @minupla, and @vincentkoc.
|
||||
- Skills/Docker: run npm-backed skill dependency installs with an OpenClaw-managed user prefix so non-root Docker images do not write to `/usr/local`. Fixes #59601. Thanks @chanjarster and @vincentkoc.
|
||||
- Agents/runtime: submit heartbeat, cron, and exec wakeups as transient runtime context instead of visible user prompts, keeping synthetic system work out of chat transcripts. Fixes #66496 and #66814. Thanks @jeades and @mandomaker.
|
||||
- Telegram: include native quote excerpts automatically for threaded replies and reply tags when the original Telegram text is available, without adding another config knob. Fixes #6975. Thanks @rex05ai.
|
||||
- Node/Linux: make `openclaw node install` enable and restart the `openclaw-node` systemd unit instead of the gateway unit on node-only VMs. Fixes #68287. Thanks @dlebee-agent.
|
||||
- Browser/CDP: retry transient raw-CDP WebSocket handshake failures before any
|
||||
browser command is sent, and reconnect stale persistent Playwright CDP
|
||||
sessions for safe tab-list reads without replaying mutating browser actions.
|
||||
Fixes #67728.
|
||||
- Browser/CDP: retry transient raw-CDP WebSocket handshake failures before any browser command is sent, and reconnect stale persistent Playwright CDP sessions for safe tab-list reads without replaying mutating browser actions. Fixes #67728.
|
||||
- Gateway/Linux: retry `systemctl --user enable` after a second daemon reload when the freshly written gateway unit is not visible yet on migrated systemd installs. Fixes #65184. Thanks @liushuaiiu.
|
||||
- Telegram: preserve exact selected quote text when sending native quote replies, and retry with legacy replies if Telegram rejects quote parameters. (#71952) Thanks @rubencu.
|
||||
- Plugins/CLI: preserve manifest name, description, format, and source metadata in cold `openclaw plugins list` output without importing plugin runtime. Thanks @shakkernerd.
|
||||
@@ -102,27 +252,13 @@ Docs: https://docs.openclaw.ai
|
||||
- Plugins/chat: keep `/plugins list`, `/plugins enable`, and `/plugins disable` on the persisted plugin index path so chat plugin management does not load diagnostic/runtime plugin registries before execution. Thanks @shakkernerd.
|
||||
- Plugins/doctor: read workspace plugin status and legacy web-search ownership through installed-index manifest metadata instead of broad manifest registry scans. Thanks @shakkernerd.
|
||||
- CLI/agents: read channel provider status from read-only plugin index metadata for text `agents list` output instead of the loaded channel registry. Thanks @shakkernerd.
|
||||
- Logging: redact configured secret patterns at console and file-log sink exits
|
||||
so credentials that reach the logger are masked before terminal display or
|
||||
JSONL persistence. Fixes #67953. Thanks @Ziy1-Tan.
|
||||
- Gateway/services: refuse process and service mutations from an older OpenClaw
|
||||
binary when the config was last written by a newer version, preventing
|
||||
split-brain installs from stopping or rewriting newer gateway services. Fixes
|
||||
#57079.
|
||||
- Logging: redact configured secret patterns at console and file-log sink exits so credentials that reach the logger are masked before terminal display or JSONL persistence. Fixes #67953. Thanks @Ziy1-Tan.
|
||||
- Gateway/services: refuse process and service mutations from an older OpenClaw binary when the config was last written by a newer version, preventing split-brain installs from stopping or rewriting newer gateway services. Fixes #57079.
|
||||
- Gateway: reserve `/healthz` and `/readyz` ahead of plugin, canvas, and Control UI HTTP stages so liveness/readiness probes still answer when a later route handler stalls. Fixes #69674. Thanks @Xike-Creek.
|
||||
- Logging: load `logging.file` and redaction settings directly from the active
|
||||
OpenClaw config path in bundled runtimes, so packaged gateways stop falling
|
||||
back to `/tmp/openclaw`. Fixes #59370, #67168, and #61295. Thanks @KeaneYan,
|
||||
@Pan9hu, and @zsjlovelike.
|
||||
- Logging: rotate file logs at `logging.maxFileBytes`, keep bounded numbered
|
||||
archives, and make long-lived rolling loggers follow the current-day file
|
||||
instead of suppressing diagnostics or writing stale dated files. Fixes #58583
|
||||
and #62381. Thanks @jpeghead and @zhaoleink.
|
||||
- Logging: load `logging.file` and redaction settings directly from the active OpenClaw config path in bundled runtimes, so packaged gateways stop falling back to `/tmp/openclaw`. Fixes #59370, #67168, and #61295. Thanks @KeaneYan, @Pan9hu, and @zsjlovelike.
|
||||
- Logging: rotate file logs at `logging.maxFileBytes`, keep bounded numbered archives, and make long-lived rolling loggers follow the current-day file instead of suppressing diagnostics or writing stale dated files. Fixes #58583 and #62381. Thanks @jpeghead and @zhaoleink.
|
||||
- Agents/groups: treat clean empty assistant stops as silent `NO_REPLY` only for always-on groups where silent replies are allowed, while keeping direct and mention-gated sessions on the incomplete-turn retry path. Thanks @MagnaAI.
|
||||
- macOS/Node: keep native remote app nodes from advertising `browser.proxy`,
|
||||
start browser-capable CLI node services through the restored
|
||||
`openclaw node start` command, and show an actionable browser-control error
|
||||
when the local control service is missing. Fixes #66637.
|
||||
- macOS/Node: keep native remote app nodes from advertising `browser.proxy`, start browser-capable CLI node services through the restored `openclaw node start` command, and show an actionable browser-control error when the local control service is missing. Fixes #66637.
|
||||
- Gateway/update: fail package updates when the restarted managed gateway reports the wrong version, including fallback restarts and JSON mode, avoiding false-success mixed-version restarts after macOS LaunchAgent updates. Fixes #71835. Thanks @abhinas90 and @jsompis.
|
||||
- Gateway/update: warn before package updates and bundled plugin runtime-dependency repairs when the target volume appears low on disk space, without blocking installs on best-effort filesystem checks. Fixes #71835. Thanks @abhinas90 and @jsompis.
|
||||
- Plugins/runtime deps: surface activated plugin load failures in health and fail package-update restart verification or doctor repair when bundled runtime deps still cannot load, avoiding false-success repairs. (#71883) Thanks @Solvely-Colin.
|
||||
@@ -136,52 +272,25 @@ Docs: https://docs.openclaw.ai
|
||||
- Plugins: scope setup and web-provider metadata manifest reads to explicit plugin ids when callers already know the owning plugin set. Thanks @vincentkoc.
|
||||
- Plugins/onboarding: defer onboarding install-record index writes until the guarded config commit so setup failures cannot leave the plugin index ahead of `openclaw.json`. Thanks @shakkernerd.
|
||||
- Plugins/registry: resolve web provider ownership from the installed plugin index instead of broad manifest scans on secret, tool, and pricing paths. Thanks @shakkernerd.
|
||||
- Config/providers: accept `video` and `audio` in configured model `input` values and
|
||||
preserve them in provider catalog entries. Fixes #20721. Thanks @alvinttang.
|
||||
- Config/providers: accept `video` and `audio` in configured model `input` values and preserve them in provider catalog entries. Fixes #20721. Thanks @alvinttang.
|
||||
- Models/auth: honor the parent `--agent` flag for auth write commands (`add`, `login`, `setup-token`, `paste-token`, and the GitHub Copilot shortcut) so OAuth/API-key/token results are written to the requested agent store instead of the default agent. Fixes #71864. (#71933) Thanks @balric-seo.
|
||||
- TTS: strip model-emitted TTS directives from streamed block text before channel
|
||||
delivery, including directives split across adjacent blocks, while preserving
|
||||
the accumulated raw reply for final-mode synthesis. Fixes #38937.
|
||||
- TTS: keep explicit `provider=...` directive keys scoped to that provider and
|
||||
warn on unsupported keys instead of letting another speech provider consume
|
||||
overlapping keys. Fixes #60131.
|
||||
- TTS/Feishu: normalize final-mode streamed TTS-only audio before delivery so
|
||||
generated voice-note files use the same safe media path and native voice
|
||||
routing as normal final replies. Fixes #71920.
|
||||
- Feishu: transcribe inbound voice-note audio with the shared media audio path
|
||||
before agent dispatch and keep raw Feishu `file_key` payloads out of message
|
||||
text. Fixes #67120 and #61876.
|
||||
- TTS: strip model-emitted TTS directives from streamed block text before channel delivery, including directives split across adjacent blocks, while preserving the accumulated raw reply for final-mode synthesis. Fixes #38937.
|
||||
- TTS: keep explicit `provider=...` directive keys scoped to that provider and warn on unsupported keys instead of letting another speech provider consume overlapping keys. Fixes #60131.
|
||||
- TTS/Feishu: normalize final-mode streamed TTS-only audio before delivery so generated voice-note files use the same safe media path and native voice routing as normal final replies. Fixes #71920.
|
||||
- Feishu: transcribe inbound voice-note audio with the shared media audio path before agent dispatch and keep raw Feishu `file_key` payloads out of message text. Fixes #67120 and #61876.
|
||||
- Tasks: terminalize async Gateway agent task records from the Gateway run result while preserving aborted, failed, and cancelled outcomes instead of leaving completed runs stuck as active or lost. (#71905) Thanks @likewen-tech.
|
||||
- WhatsApp: let authorized group voice-note transcripts satisfy mention gating
|
||||
before reply dispatch, while keeping unmentioned transcripts in pending group
|
||||
history. Fixes #44908.
|
||||
- Media understanding: carry channel voice-note preflight state into attachment
|
||||
selection so WhatsApp, Feishu, Telegram, and Discord do not transcribe the
|
||||
same inbound audio twice. Fixes #70580.
|
||||
- TTS/BlueBubbles: deliver compatible auto-TTS audio as iMessage voice memo
|
||||
bubbles instead of plain MP3/CAF file attachments. Fixes #16848.
|
||||
- TTS: resolve voice-note and voice-memo routing from channel plugin
|
||||
capabilities instead of speech-core-owned channel id lists.
|
||||
- ACP: send subagent and async-task completion wakes to external ACP harnesses as
|
||||
plain prompts instead of OpenClaw internal runtime-context envelopes, while
|
||||
keeping those envelopes out of ACP transcripts.
|
||||
- WhatsApp: let authorized group voice-note transcripts satisfy mention gating before reply dispatch, while keeping unmentioned transcripts in pending group history. Fixes #44908.
|
||||
- Media understanding: carry channel voice-note preflight state into attachment selection so WhatsApp, Feishu, Telegram, and Discord do not transcribe the same inbound audio twice. Fixes #70580.
|
||||
- TTS/BlueBubbles: deliver compatible auto-TTS audio as iMessage voice memo bubbles instead of plain MP3/CAF file attachments. Fixes #16848.
|
||||
- TTS: resolve voice-note and voice-memo routing from channel plugin capabilities instead of speech-core-owned channel id lists.
|
||||
- ACP: send subagent and async-task completion wakes to external ACP harnesses as plain prompts instead of OpenClaw internal runtime-context envelopes, while keeping those envelopes out of ACP transcripts.
|
||||
- TTS/status: show configured TTS model, voice, and sanitized custom endpoint in `/status`, preserve OpenAI-compatible TTS instructions on custom endpoints, and retry empty Microsoft/Edge TTS output once. Addresses #46602, #47232, and #43936. Thanks @leekuangtao, @Huntterxx, and @rex993.
|
||||
- Agents/Gateway: steer agent-driven config edits and restarts through the owner-only `gateway` tool, document `config.schema.lookup` as the field-doc source, and warn against using `gateway stop && gateway start` as a restart substitute on macOS. Fixes #71929. Thanks @ygc3817922006-sketch.
|
||||
- Media understanding/audio: inject a deterministic transcript placeholder for too-small voice notes so agents do not hallucinate transcription or provider failures. Fixes #48944. Thanks @eulicesl.
|
||||
- Providers/vLLM: send Nemotron 3 chat-template kwargs when thinking is off
|
||||
and honor configured `params.chat_template_kwargs` for OpenAI-compatible
|
||||
completions, so vLLM/Nemotron replies stay visible instead of becoming
|
||||
thinking-only. Fixes #71891. Thanks @jmystaki-create and @dennis-lynch.
|
||||
- Channels/replies: strip copied inbound metadata blocks from user-facing
|
||||
assistant replies and model replay history, so Discord/vLLM sessions do not
|
||||
leak `Conversation info` / `UNTRUSTED ... message body` envelopes after a
|
||||
model echoes them. Fixes #71847. Thanks @jmystaki-create.
|
||||
- Subagents/memory: keep inter-session completion wakes out of memory and
|
||||
dreaming session exports, and strip internal runtime-context blocks from
|
||||
realtime Control UI chat events.
|
||||
- Agents/Claude: treat zero-token empty `stop` turns as failed provider output,
|
||||
retry once, repair replay, and allow configured model fallback instead of
|
||||
preserving them as successful silent replies. Fixes #71880. Thanks @MagnaAI.
|
||||
- Providers/vLLM: send Nemotron 3 chat-template kwargs when thinking is off and honor configured `params.chat_template_kwargs` for OpenAI-compatible completions, so vLLM/Nemotron replies stay visible instead of becoming thinking-only. Fixes #71891. Thanks @jmystaki-create and @dennis-lynch.
|
||||
- Channels/replies: strip copied inbound metadata blocks from user-facing assistant replies and model replay history, so Discord/vLLM sessions do not leak `Conversation info` / `UNTRUSTED ... message body` envelopes after a model echoes them. Fixes #71847. Thanks @jmystaki-create.
|
||||
- Subagents/memory: keep inter-session completion wakes out of memory and dreaming session exports, and strip internal runtime-context blocks from realtime Control UI chat events.
|
||||
- Agents/Claude: treat zero-token empty `stop` turns as failed provider output, retry once, repair replay, and allow configured model fallback instead of preserving them as successful silent replies. Fixes #71880. Thanks @MagnaAI.
|
||||
- Tasks: normalize task lifecycle timestamps at create, update, and restore time, and report retained lost tasks as audit warnings until their cleanup window expires. (#71871) Thanks @likewen-tech.
|
||||
- Diagnostics/OTEL: treat normal early model stream cleanup as a completed model call instead of exporting a misleading `StreamAbandoned` error span. Thanks @vincentkoc.
|
||||
- Gateway/pairing: stop corrupt or unreadable device/node pairing stores from being treated as empty state, preserving `paired.json` for repair instead of overwriting approved pairings. Fixes #71873. Thanks @iret77.
|
||||
@@ -193,13 +302,8 @@ Docs: https://docs.openclaw.ai
|
||||
- Control UI: hide the chat loading skeleton during background history reloads when existing messages or active stream content are already visible, avoiding reload flashes on high-latency local gateways. Fixes #71844. Thanks @WolvenRA.
|
||||
- Control UI: keep locally optimistic chat messages visible when a history reload temporarily returns empty, avoiding lost first-turn messages on high-latency gateways. Fixes #71878. Thanks @WolvenRA.
|
||||
- Control UI: keep chat history limits based on visible messages after filtering heartbeat and control-only transcript rows, so recent hidden entries no longer make older visible replies disappear. Thanks @WolvenRA.
|
||||
- Agents/images: scrub old `[media attached: ...]`, `[Image: source: ...]`,
|
||||
and `media://inbound/...` markers from pruned model replay context so stale
|
||||
media refs are not rehydrated as fresh prompt images. Fixes #71868. Thanks
|
||||
@jmeadlock.
|
||||
- Docker/Bonjour: disable Bonjour/mDNS advertising by default for bundled
|
||||
Compose gateways on bridge networking, while keeping host/macvlan opt-in with
|
||||
`OPENCLAW_DISABLE_BONJOUR=0`. Fixes #71879. Thanks @gbballpack.
|
||||
- Agents/images: scrub old `[media attached: ...]`, `[Image: source: ...]`, and `media://inbound/...` markers from pruned model replay context so stale media refs are not rehydrated as fresh prompt images. Fixes #71868. Thanks @jmeadlock.
|
||||
- Docker/Bonjour: disable Bonjour/mDNS advertising by default for bundled Compose gateways on bridge networking, while keeping host/macvlan opt-in with `OPENCLAW_DISABLE_BONJOUR=0`. Fixes #71879. Thanks @gbballpack.
|
||||
- CLI/status: label the OpenClaw Serve/Funnel setting as `Tailscale exposure` and show daemon state separately when available, so `gateway.tailscale.mode: "off"` no longer reads like the Tailscale daemon is stopped. Fixes #71790. Thanks @pesvobodak.
|
||||
- Plugins/Bonjour: stop ciao mDNS watchdog failures from looping forever when the advertiser stays stuck in `probing` or `announcing`; Bonjour now disables itself for the current Gateway process after repeated failed restarts while the Gateway keeps running. Fixes #69011. Thanks @siddharthaagarwalofficial-ux, @FiredMosquito831, and @spikefcz.
|
||||
- Gateway/Fly.io: seed Control UI allowed origins from the actual runtime bind and port so CLI-driven non-loopback starts do not crash before config exists. Fixes #71823.
|
||||
@@ -218,9 +322,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Agents/Codex: keep ACP prompt/skill routing hidden unless an ACP runtime backend is available, and warn in doctor when enabled Codex plugin configs still route `openai-codex/*` models through PI. Thanks @vincentkoc.
|
||||
- Media delivery: avoid sending generated image attachments twice when the assistant reply already includes explicit `MEDIA:` lines for the same turn, and reject unsafe remote `MEDIA:` URLs before delivery. Thanks @pashpashpash.
|
||||
- Codex harness: ignore retryable app-server error notifications after Codex recovers, and preserve the real nested error message for terminal app-server failures instead of replacing it with a generic failure. Thanks @pashpashpash.
|
||||
- Agents/Codex: prepare native Codex sub-agent session metadata without a
|
||||
nested Gateway session patch and add a focused Docker smoke for the app-server
|
||||
sub-agent path. Thanks @vincentkoc.
|
||||
- Agents/Codex: prepare native Codex sub-agent session metadata without a nested Gateway session patch and add a focused Docker smoke for the app-server sub-agent path. Thanks @vincentkoc.
|
||||
- Agents/subagents: keep queued subagent announces session-only when the requester has no external channel target, avoiding ambiguous multi-channel delivery failures. Fixes #59201. Thanks @larrylhollan.
|
||||
- Image understanding: preserve configured provider-prefixed vision model metadata when callers request the model without the provider prefix, so custom image models keep their `input: ["text", "image"]` capability. Fixes #33185. Thanks @Kobe9312 and @vincentkoc.
|
||||
- Plugins/install: restore the previous plugin index records if a concurrent config write conflict interrupts install, update, or uninstall metadata commits. Thanks @shakkernerd.
|
||||
@@ -234,8 +336,8 @@ Docs: https://docs.openclaw.ai
|
||||
- Sessions: keep embedded runtime context out of the visible user prompt by sending it as a hidden next-turn custom message, and teach doctor to repair affected 2026.4.24 transcripts with duplicated prompt-rewrite branches. Fixes #71761.
|
||||
- Gateway/subagents: keep direct-loopback backend RPCs authenticated with the shared gateway token/password off stale CLI paired-device scope baselines, so internal calls no longer hit `scope-upgrade` pairing prompts while remote, browser, node, device-token, and explicit-device paths still require normal pairing approval. Fixes #63548.
|
||||
- Providers/Azure OpenAI: give deployment-scoped image generation requests a longer 600s default timeout so slow `gpt-image-2` generations can complete without a per-call `timeoutMs`. Fixes #71705. Thanks @voytas75.
|
||||
- Gateway/plugins: link source-checkout bundled runtime dependency caches instead of recursively copying `node_modules` on the gateway main thread, preventing local status, node, and skill probes from timing out during startup cache restores. Thanks @steipete.
|
||||
- Skills/remote nodes: only expose remote macOS skill bins for connected nodes, clear stale bin matches when node probes fail, and include probe command, timeout, bin count, and connection state in timeout logs. Thanks @steipete.
|
||||
- Gateway/plugins: link source-checkout bundled runtime dependency caches instead of recursively copying `node_modules` on the gateway main thread, preventing local status, node, and skill probes from timing out during startup cache restores.
|
||||
- Skills/remote nodes: only expose remote macOS skill bins for connected nodes, clear stale bin matches when node probes fail, and include probe command, timeout, bin count, and connection state in timeout logs.
|
||||
- Skills/remote nodes: recognize `system.which` object-map responses when probing connected macOS nodes, so Linux gateways can expose macOS-only skills such as Apple Notes when the required binaries are installed remotely. Fixes #71877. Thanks @miguelarios.
|
||||
- CLI/gateway: keep diagnostic probes from creating first-time read-only device pairings, while still reusing cached device tokens for detailed read probes. Fixes #71766. Thanks @SunboZ.
|
||||
- CLI/plugins: keep `message` startup, `channels logs`, `agents delete`, and `agents set-identity` off broad plugin preloading; message delivery still loads plugins when the action actually runs.
|
||||
@@ -245,12 +347,12 @@ Docs: https://docs.openclaw.ai
|
||||
- Sessions: clear queued system-event notices during `/new`, `/reset`, gateway `sessions.reset`, and daily/idle rollover so stale background updates cannot leak into the first prompt of the fresh session. Fixes #66864. Thanks @opeyio, @Magicray1217, and @cedillarack.
|
||||
- CLI/agents: keep `agents bind`, `agents unbind`, and `agents bindings` on setup-safe channel metadata paths so they do not preload bundled plugin runtimes or stage runtime dependencies. Fixes #71743.
|
||||
- Plugins/registry: preserve explicit disabled plugin records during registry migration without persisting every unused bundled plugin discovered on disk. Thanks @shakkernerd.
|
||||
- Windows/native: keep CLI startup and bundled provider plugin loading off Windows ESM raw-path failure paths, fixing native onboarding/install smoke on Node 24. Thanks @steipete.
|
||||
- Windows/native: keep CLI startup and bundled provider plugin loading off Windows ESM raw-path failure paths, fixing native onboarding/install smoke on Node 24.
|
||||
- Plugins/doctor: read bundled channel doctor capabilities through the same packaged plugin directory resolver used by plugin loading, so published installs keep Matrix DM allowlist repairs on `channels.matrix.dm.*` instead of writing invalid top-level `dmPolicy` keys. Fixes #71757.
|
||||
- Plugins/Windows: keep bundled plugin Jiti loaders off the native import path on Windows so channel plugins such as Telegram no longer crash with `ERR_UNSUPPORTED_ESM_URL_SCHEME` on `C:\...` paths. Fixes #71749. Thanks @smeyer9.
|
||||
- Providers/Ollama: use Ollama's current `/api/web_search` endpoint and honor `https://ollama.com` model-provider base URLs for Ollama Web Search. Fixes #71741. Thanks @madhvidua.
|
||||
- Memory/Ollama: serialize Ollama memory embedding batches and add an inline batch timeout override, with longer defaults for local/self-hosted embedding providers. Thanks @steipete.
|
||||
- Sessions/usage: exclude compaction checkpoint transcript snapshots from usage totals and session discovery, while keeping old checkpoint files removable. Thanks @steipete.
|
||||
- Memory/Ollama: serialize Ollama memory embedding batches and add an inline batch timeout override, with longer defaults for local/self-hosted embedding providers.
|
||||
- Sessions/usage: exclude compaction checkpoint transcript snapshots from usage totals and session discovery, while keeping old checkpoint files removable.
|
||||
- CLI/agents: keep `openclaw agents list --json` on the config-only path by default, avoiding bundled plugin loading unless callers request `--bindings`. Fixes #71739. Thanks @kaloster.
|
||||
- Plugins/install: force plugin dependency installs to stay project-local even when inherited npm config requests global installs, so successful installs still materialize the plugin's staged `node_modules`.
|
||||
- Providers/Google: transcode Gemini TTS PCM to Opus for voice-note targets so WhatsApp and other native voice-note replies can play as voice messages.
|
||||
@@ -264,16 +366,16 @@ Docs: https://docs.openclaw.ai
|
||||
- ACP/oneshot: reconcile runtime session identity before closing completed oneshot ACP runs, so finished `sessions.json` entries do not stay stuck with `acp.identity.state="pending"`.
|
||||
- ACPX: bundle `acpx@0.6.1` so unsupported generic model overrides fail clearly instead of silently falling back to the target adapter default.
|
||||
- ACP/models: document that non-Codex ACP model overrides require adapter support for ACP `models` plus `session/set_model`, so unsupported harnesses fail clearly instead of silently falling back to their defaults.
|
||||
- Plugins/Voice Call: treat missing provider credentials as setup-incomplete during Gateway startup and log the missing keys as a warning instead of a runtime startup error, while keeping explicit command/tool errors when used. Thanks @steipete.
|
||||
- Plugins/Voice Call: treat missing provider credentials as setup-incomplete during Gateway startup and log the missing keys as a warning instead of a runtime startup error, while keeping explicit command/tool errors when used.
|
||||
- Android/Talk Mode: prevent duplicate TTS playback when fast or repeated final chat events arrive while Talk Mode is waiting for its own response. Fixes #46546.
|
||||
- Tooling/check:changed: pass parent heavy-check lock markers to lint lanes so `pnpm check:changed` no longer waits on its own `lint:extensions` child. Thanks @steipete.
|
||||
- Tooling/check:changed: pass parent heavy-check lock markers to lint lanes so `pnpm check:changed` no longer waits on its own `lint:extensions` child.
|
||||
- CLI/completion: dedupe provider auth flags before registering `openclaw onboard` options, so completion-cache refresh during update no longer fails when stale core fallback flags overlap plugin manifest flags. Fixes #71667.
|
||||
- Diagnostics/trace: report live context usage from the current prompt snapshot instead of provider turn totals, avoiding false near-full context spikes on cached or tool-heavy runs.
|
||||
- Providers/Google: honor `models.providers.google.request.allowPrivateNetwork` for Gemini TTS and telephony TTS, matching Google image generation and media understanding. (#71723) Thanks @ro-hansolo.
|
||||
- Providers/MiniMax: register `minimax-portal` for music and video generation, preserving OAuth auth and regional MiniMax base URLs across the shared `music_generate` and `video_generate` tools. (#63241) Thanks @tars90percent.
|
||||
- Providers/onboarding: keep Runway and Alibaba Model Studio out of the text-inference setup picker by scoping their video-generation auth choices to the media setup flow. (#65856) Thanks @Jah-yee.
|
||||
- Plugins/Bonjour: stop the gateway from crash-looping on `CIAO PROBING CANCELLED` when the mDNS watchdog cancels a stuck probe. Restores the rejection-handler wiring dropped during the bonjour plugin migration and shares unhandled-rejection state across module instances so plugin-staged copies of `openclaw/plugin-sdk/runtime` register into the same handler set the host consults. Especially affects Docker on macOS, where mDNS probing reliably hits the watchdog. Thanks @troyhitch.
|
||||
- Google Meet: report pinned Chrome nodes as offline or missing capabilities in setup/join diagnostics, keep inaccessible nodes out of auto-selection, and preflight local BlackHole/SoX requirements before agents try local Chrome. Thanks @steipete.
|
||||
- Google Meet: report pinned Chrome nodes as offline or missing capabilities in setup/join diagnostics, keep inaccessible nodes out of auto-selection, and preflight local BlackHole/SoX requirements before agents try local Chrome.
|
||||
- Providers/MiniMax: route `image-01` requests to the dedicated image generation endpoint while preserving CN endpoint selection. Fixes #61149. Thanks @mushuiyu886.
|
||||
- Plugins/startup: remove ownerless bundled runtime-dependency install locks after a short grace window and include lock owner details when startup times out waiting for a plugin runtime-deps lock.
|
||||
- Plugins/install: anchor bundled runtime-dependency npm installs with an OpenClaw-owned package manifest so Linux updates cannot accidentally write to a parent `$HOME/node_modules` tree. Fixes #71730.
|
||||
@@ -330,6 +432,7 @@ Docs: https://docs.openclaw.ai
|
||||
- CLI/models: make `openclaw models scan` fall back to public OpenRouter free-model metadata when no `OPENROUTER_API_KEY` is configured, avoid config secret resolution for explicit `--no-probe` scans, and apply the scan timeout to the OpenRouter catalog request.
|
||||
- Feishu: keep streaming cards to one live card per turn, flush throttled card edits after meaningful text boundaries, and skip exact block/partial repeats so tool-heavy replies do not duplicate card output. Thanks @allan0509.
|
||||
- Feishu: finish the streaming-card duplicate closeout by stripping leaked reasoning tags, preserving cross-block partial snapshots, enabling topic-thread streaming cards, omitting the generic `main` card header, surfacing transient tool/compaction status, and cleaning streaming state after close failures. Thanks @sesame437, @Vicky-v7, @maoku-family, @Pengxiao-Wang, and @Maple778.
|
||||
- Telegram: keep final-only answers on the normal final-send path instead of creating synthetic draft previews, while preserving real partial preview finalization. Credited from #39213. Thanks @chalawbot.
|
||||
- Telegram: recover incomplete partial-stream previews by falling back to a final send when an ambiguous final edit failure would otherwise retain a strict prefix of the answer. Fixes #71525. (#71554) Thanks @sahilsatralkar.
|
||||
- Control UI/chat: collapse assistant token/model context details behind an explicit Context disclosure and show full dates in message footers, making historical transcript timing clear without noisy default metadata. (#71337) Thanks @BunsDev.
|
||||
- OpenAI/Codex OAuth: explain `unsupported_country_region_territory` token-exchange failures with a proxy/region hint instead of surfacing a generic OAuth error. Fixes #51175. (#71501) Thanks @vincentkoc and @wulala-xjj.
|
||||
@@ -796,16 +899,9 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- Dependencies: refresh workspace package pins and lockfile entries for AWS SDK,
|
||||
Anthropic SDK, ACP SDK, Matrix crypto, TypeBox, Vite, tsdown, Slack Bolt,
|
||||
CopilotKit AIMock, and related bundled plugin packages. Thanks @steipete.
|
||||
- Gateway/env: import each missing expected login-shell env var independently,
|
||||
so an existing gateway token no longer prevents `env.shellEnv` from loading
|
||||
plugin credentials such as `TWILIO_*` from `.profile`. Thanks @steipete.
|
||||
- macOS/Gateway pairing: silently accept same-host native app
|
||||
`metadata-upgrade` reconnects, so macOS patch-version changes update paired
|
||||
metadata instead of spamming security audit warnings and `pairing required`
|
||||
disconnects. Thanks @steipete.
|
||||
- Dependencies: refresh workspace package pins and lockfile entries for AWS SDK, Anthropic SDK, ACP SDK, Matrix crypto, TypeBox, Vite, tsdown, Slack Bolt, CopilotKit AIMock, and related bundled plugin packages. Thanks @steipete.
|
||||
- Gateway/env: import each missing expected login-shell env var independently, so an existing gateway token no longer prevents `env.shellEnv` from loading plugin credentials such as `TWILIO_*` from `.profile`. Thanks @steipete.
|
||||
- macOS/Gateway pairing: silently accept same-host native app `metadata-upgrade` reconnects, so macOS patch-version changes update paired metadata instead of spamming security audit warnings and `pairing required` disconnects. Thanks @steipete.
|
||||
- CLI/Gateway: wait for one-shot gateway RPC clients to finish WebSocket teardown before the CLI process exits, reducing hangs where commands like `openclaw status` or `openclaw version` could finish their work but stay alive until an external timeout killed them (#70691). Thanks @Takhoffman.
|
||||
- Thinking defaults/status: raise the implicit default thinking level for reasoning-capable models from legacy `off`/`low` fallback behavior to a safe provider-supported `medium` equivalent when no explicit config default is set, preserve configured-model reasoning metadata when runtime catalog loading is empty, and make `/status` report the same resolved default as runtime (#70601). Thanks @Takhoffman.
|
||||
- Gateway/model pricing: extend OpenRouter and LiteLLM catalog fetch timeouts to 60 seconds, reducing noisy timeout warnings during slow upstream responses. Thanks @steipete.
|
||||
@@ -844,9 +940,7 @@ Docs: https://docs.openclaw.ai
|
||||
- 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 runs.
|
||||
`coding` and `messaging` sessions while preserving `minimal` profile and
|
||||
`tools.deny: ["bundle-mcp"]` opt-out behavior. Fixes #68875 and #68818.
|
||||
- Agents/MCP: keep `mcp.servers` and bundle MCP tools available in Pi embedded runs. `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.
|
||||
|
||||
36
Dockerfile
36
Dockerfile
@@ -9,22 +9,19 @@
|
||||
# bundled plugin workspace tree, so the main build layer is not invalidated by
|
||||
# unrelated plugin source changes.
|
||||
#
|
||||
# Two runtime variants:
|
||||
# Default (bookworm): docker build .
|
||||
# Slim (bookworm-slim): docker build --build-arg OPENCLAW_VARIANT=slim .
|
||||
# Build stages use full bookworm; the runtime image is always bookworm-slim.
|
||||
ARG OPENCLAW_EXTENSIONS=""
|
||||
ARG OPENCLAW_VARIANT=default
|
||||
ARG OPENCLAW_BUNDLED_PLUGIN_DIR=extensions
|
||||
ARG OPENCLAW_DOCKER_APT_UPGRADE=1
|
||||
ARG OPENCLAW_NODE_BOOKWORM_IMAGE="node:24-bookworm@sha256:3a09aa6354567619221ef6c45a5051b671f953f0a1924d1f819ffb236e520e6b"
|
||||
ARG OPENCLAW_NODE_BOOKWORM_DIGEST="sha256:3a09aa6354567619221ef6c45a5051b671f953f0a1924d1f819ffb236e520e6b"
|
||||
ARG OPENCLAW_NODE_BOOKWORM_SLIM_IMAGE="node:24-bookworm-slim@sha256:e8e2e91b1378f83c5b2dd15f0247f34110e2fe895f6ca7719dbb780f929368eb"
|
||||
ARG OPENCLAW_NODE_BOOKWORM_SLIM_DIGEST="sha256:e8e2e91b1378f83c5b2dd15f0247f34110e2fe895f6ca7719dbb780f929368eb"
|
||||
|
||||
# Base images are pinned to SHA256 digests for reproducible builds.
|
||||
# Trade-off: digests must be updated manually when upstream tags move.
|
||||
# To update, run: docker buildx imagetools inspect node:24-bookworm (or podman)
|
||||
# and replace the digest below with the current multi-arch manifest list entry.
|
||||
# Dependabot refreshes these blessed digests; release builds consume the
|
||||
# reviewed base snapshot instead of mutating distro state on every build.
|
||||
# To update, run: docker buildx imagetools inspect node:24-bookworm and
|
||||
# node:24-bookworm-slim (or podman) and replace the digests below with the
|
||||
# current multi-arch manifest list entries.
|
||||
|
||||
FROM ${OPENCLAW_NODE_BOOKWORM_IMAGE} AS ext-deps
|
||||
ARG OPENCLAW_EXTENSIONS
|
||||
@@ -125,22 +122,15 @@ RUN printf 'packages:\n - .\n - ui\n' > /tmp/pnpm-workspace.runtime.yaml && \
|
||||
node scripts/postinstall-bundled-plugins.mjs && \
|
||||
find dist -type f \( -name '*.d.ts' -o -name '*.d.mts' -o -name '*.d.cts' -o -name '*.map' \) -delete
|
||||
|
||||
# ── Runtime base images ─────────────────────────────────────────
|
||||
FROM ${OPENCLAW_NODE_BOOKWORM_IMAGE} AS base-default
|
||||
ARG OPENCLAW_NODE_BOOKWORM_DIGEST
|
||||
LABEL org.opencontainers.image.base.name="docker.io/library/node:24-bookworm" \
|
||||
org.opencontainers.image.base.digest="${OPENCLAW_NODE_BOOKWORM_DIGEST}"
|
||||
|
||||
FROM ${OPENCLAW_NODE_BOOKWORM_SLIM_IMAGE} AS base-slim
|
||||
# ── Runtime base image ──────────────────────────────────────────
|
||||
FROM ${OPENCLAW_NODE_BOOKWORM_SLIM_IMAGE} AS base-runtime
|
||||
ARG OPENCLAW_NODE_BOOKWORM_SLIM_DIGEST
|
||||
LABEL org.opencontainers.image.base.name="docker.io/library/node:24-bookworm-slim" \
|
||||
org.opencontainers.image.base.digest="${OPENCLAW_NODE_BOOKWORM_SLIM_DIGEST}"
|
||||
|
||||
# ── Stage 3: Runtime ────────────────────────────────────────────
|
||||
FROM base-${OPENCLAW_VARIANT}
|
||||
ARG OPENCLAW_VARIANT
|
||||
FROM base-runtime
|
||||
ARG OPENCLAW_BUNDLED_PLUGIN_DIR
|
||||
ARG OPENCLAW_DOCKER_APT_UPGRADE
|
||||
|
||||
# OCI base-image metadata for downstream image consumers.
|
||||
# If you change these annotations, also update:
|
||||
@@ -155,16 +145,10 @@ LABEL org.opencontainers.image.source="https://github.com/openclaw/openclaw" \
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install system utilities present in bookworm but missing in bookworm-slim.
|
||||
# On the full bookworm image these are already installed (apt-get is a no-op).
|
||||
# Smoke workflows can opt out of distro upgrades to cut repeated CI time while
|
||||
# keeping the default runtime image behavior unchanged.
|
||||
# Install runtime system utilities missing from bookworm-slim.
|
||||
RUN --mount=type=cache,id=openclaw-bookworm-apt-cache,target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,id=openclaw-bookworm-apt-lists,target=/var/lib/apt,sharing=locked \
|
||||
apt-get update && \
|
||||
if [ "${OPENCLAW_DOCKER_APT_UPGRADE}" != "0" ]; then \
|
||||
DEBIAN_FRONTEND=noninteractive apt-get upgrade -y --no-install-recommends; \
|
||||
fi && \
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
||||
procps hostname curl git lsof openssl
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ ENV DEBIAN_FRONTEND=noninteractive
|
||||
RUN --mount=type=cache,id=openclaw-sandbox-bookworm-apt-cache,target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,id=openclaw-sandbox-bookworm-apt-lists,target=/var/lib/apt,sharing=locked \
|
||||
apt-get update \
|
||||
&& apt-get upgrade -y --no-install-recommends \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
bash \
|
||||
ca-certificates \
|
||||
|
||||
@@ -7,7 +7,6 @@ ENV DEBIAN_FRONTEND=noninteractive
|
||||
RUN --mount=type=cache,id=openclaw-sandbox-bookworm-apt-cache,target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,id=openclaw-sandbox-bookworm-apt-lists,target=/var/lib/apt,sharing=locked \
|
||||
apt-get update \
|
||||
&& apt-get upgrade -y --no-install-recommends \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
bash \
|
||||
ca-certificates \
|
||||
|
||||
@@ -24,7 +24,6 @@ ENV PATH=${BUN_INSTALL_DIR}/bin:${BREW_INSTALL_DIR}/bin:${BREW_INSTALL_DIR}/sbin
|
||||
RUN --mount=type=cache,id=openclaw-sandbox-common-apt-cache,target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,id=openclaw-sandbox-common-apt-lists,target=/var/lib/apt,sharing=locked \
|
||||
apt-get update \
|
||||
&& apt-get upgrade -y --no-install-recommends \
|
||||
&& apt-get install -y --no-install-recommends ${PACKAGES}
|
||||
|
||||
RUN if [ "${INSTALL_PNPM}" = "1" ]; then npm install -g pnpm; fi
|
||||
|
||||
@@ -65,8 +65,8 @@ android {
|
||||
applicationId = "ai.openclaw.app"
|
||||
minSdk = 31
|
||||
targetSdk = 36
|
||||
versionCode = 2026042500
|
||||
versionName = "2026.4.25"
|
||||
versionCode = 2026042600
|
||||
versionName = "2026.4.26"
|
||||
ndk {
|
||||
// Support all major ABIs — native libs are tiny (~47 KB per ABI)
|
||||
abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# OpenClaw iOS Changelog
|
||||
|
||||
## 2026.4.26 - 2026-04-26
|
||||
|
||||
Maintenance update for the current OpenClaw development release.
|
||||
|
||||
## 2026.4.25 - 2026-04-25
|
||||
|
||||
Maintenance update for the current OpenClaw development release.
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// Source of truth: apps/ios/version.json
|
||||
// Generated by scripts/ios-sync-versioning.ts.
|
||||
|
||||
OPENCLAW_IOS_VERSION = 2026.4.25
|
||||
OPENCLAW_MARKETING_VERSION = 2026.4.25
|
||||
OPENCLAW_IOS_VERSION = 2026.4.26
|
||||
OPENCLAW_MARKETING_VERSION = 2026.4.26
|
||||
OPENCLAW_BUILD_VERSION = 1
|
||||
|
||||
#include? "../build/Version.xcconfig"
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"version": "2026.4.25"
|
||||
"version": "2026.4.26"
|
||||
}
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2026.4.25</string>
|
||||
<string>2026.4.26</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>2026042500</string>
|
||||
<string>2026042600</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>OpenClaw</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
|
||||
@@ -6,9 +6,19 @@ services:
|
||||
TERM: xterm-256color
|
||||
OPENCLAW_GATEWAY_TOKEN: ${OPENCLAW_GATEWAY_TOKEN:-}
|
||||
OPENCLAW_ALLOW_INSECURE_PRIVATE_WS: ${OPENCLAW_ALLOW_INSECURE_PRIVATE_WS:-}
|
||||
# Docker bridge networks usually do not carry mDNS multicast reliably.
|
||||
# Set OPENCLAW_DISABLE_BONJOUR=0 only on host/macvlan/mDNS-capable networks.
|
||||
OPENCLAW_DISABLE_BONJOUR: ${OPENCLAW_DISABLE_BONJOUR:-1}
|
||||
# Empty means auto: Bonjour disables itself in detected containers.
|
||||
# Set 0 only on host/macvlan/mDNS-capable networks; set 1 to force off.
|
||||
OPENCLAW_DISABLE_BONJOUR: ${OPENCLAW_DISABLE_BONJOUR:-}
|
||||
# OpenTelemetry export is outbound OTLP/HTTP from the Gateway. Prometheus
|
||||
# uses the existing authenticated Gateway route; it does not need a port.
|
||||
OTEL_EXPORTER_OTLP_ENDPOINT: ${OTEL_EXPORTER_OTLP_ENDPOINT:-}
|
||||
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: ${OTEL_EXPORTER_OTLP_TRACES_ENDPOINT:-}
|
||||
OTEL_EXPORTER_OTLP_METRICS_ENDPOINT: ${OTEL_EXPORTER_OTLP_METRICS_ENDPOINT:-}
|
||||
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT: ${OTEL_EXPORTER_OTLP_LOGS_ENDPOINT:-}
|
||||
OTEL_EXPORTER_OTLP_PROTOCOL: ${OTEL_EXPORTER_OTLP_PROTOCOL:-http/protobuf}
|
||||
OTEL_SERVICE_NAME: ${OTEL_SERVICE_NAME:-}
|
||||
OTEL_SEMCONV_STABILITY_OPT_IN: ${OTEL_SEMCONV_STABILITY_OPT_IN:-}
|
||||
OPENCLAW_OTEL_PRELOADED: ${OPENCLAW_OTEL_PRELOADED:-}
|
||||
CLAUDE_AI_SESSION_KEY: ${CLAUDE_AI_SESSION_KEY:-}
|
||||
CLAUDE_WEB_SESSION_KEY: ${CLAUDE_WEB_SESSION_KEY:-}
|
||||
CLAUDE_WEB_COOKIE: ${CLAUDE_WEB_COOKIE:-}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
f1eefb91a486188915373b09199959f0f1a7cd01dc75ef923832741f72a12543 config-baseline.json
|
||||
9f0e386d5118cbca785a2e8e9c8b170d844faf1b7ef5e82e6b15d9e1c39f3796 config-baseline.core.json
|
||||
7cd9c908f066c143eab2a201efbc9640f483ab28bba92ddeca1d18cc2b528bc3 config-baseline.channel.json
|
||||
a5479c182ec987bb21e814b8a4e7b3bda7190ae5c2b35fd5ca403dfa48afa115 config-baseline.plugin.json
|
||||
c4b54de7557cd14b35a629585ad706a4e7de411cc725bcbce921f22bfaf14ada config-baseline.json
|
||||
3fd4da36f28b508f8e6ac4fceb18262244d8ed70df15244192032ec71027bb4f config-baseline.core.json
|
||||
07963db49502132f26db396c56b36e018b110e6c55a68b3cb012d3ec96f43901 config-baseline.channel.json
|
||||
74b74cb18ac37c0acaa765f398f1f9edbcee4c43567f02d45c89598a1e13afb4 config-baseline.plugin.json
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
947221d62a0eb0b66250fba2b011ca28a11cb1058bc542b9c155d55479f15935 plugin-sdk-api-baseline.json
|
||||
0d750f785adbe4d90f209842ed9297476669dd62f7be81fa41e06b6736cc2aaf plugin-sdk-api-baseline.jsonl
|
||||
2a3fb85feb7420de8b166a695c3693dcc1eaa7a7f31de0dd139da856f10b2085 plugin-sdk-api-baseline.json
|
||||
6bdb96f7f92c34d7ae698784c0073343c34fb4274ab7eeded49acebb81056074 plugin-sdk-api-baseline.jsonl
|
||||
|
||||
@@ -3,7 +3,7 @@ summary: "Redirect to /gateway/authentication"
|
||||
title: "Auth monitoring"
|
||||
---
|
||||
|
||||
This page moved to [Authentication](/gateway/authentication). See [Authentication](/gateway/authentication) for auth monitoring documentation.
|
||||
Auth monitoring lives under [Authentication](/gateway/authentication).
|
||||
|
||||
## Related
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ summary: "Redirect to Task Flow"
|
||||
title: "ClawFlow"
|
||||
---
|
||||
|
||||
ClawFlow was renamed to [Task Flow](/automation/taskflow). See [Task Flow](/automation/taskflow) for the current documentation.
|
||||
ClawFlow was renamed to [Task flow](/automation/taskflow).
|
||||
|
||||
## Related
|
||||
|
||||
|
||||
@@ -47,6 +47,8 @@ Cron is the Gateway's built-in scheduler. It persists jobs, wakes the agent at t
|
||||
- One-shot jobs (`--at`) auto-delete after success by default.
|
||||
- Isolated cron runs best-effort close tracked browser tabs/processes for their `cron:<jobId>` session when the run completes, so detached browser automation does not leave orphaned processes behind.
|
||||
- Isolated cron runs also guard against stale acknowledgement replies. If the first result is just an interim status update (`on it`, `pulling everything together`, and similar hints) and no descendant subagent run is still responsible for the final answer, OpenClaw re-prompts once for the actual result before delivery.
|
||||
- Isolated cron runs prefer structured execution-denial metadata from the embedded run, then fall back to known final summary/output markers such as `SYSTEM_RUN_DENIED` and `INVALID_REQUEST`, so a blocked command is not reported as a green run.
|
||||
- Isolated cron runs also treat run-level agent failures as job errors even when no reply payload is produced, so model/provider failures increment error counters and trigger failure notifications instead of clearing the job as successful.
|
||||
|
||||
<a id="maintenance"></a>
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ summary: "Redirect to /automation"
|
||||
title: "Cron vs heartbeat"
|
||||
---
|
||||
|
||||
This page moved to [Automation & Tasks](/automation). See [Automation & Tasks](/automation) for the decision guide comparing cron and heartbeat.
|
||||
The decision guide for cron vs heartbeat lives under [Automation and tasks](/automation).
|
||||
|
||||
## Related
|
||||
|
||||
|
||||
@@ -173,7 +173,7 @@ openclaw hooks enable <hook-name>
|
||||
|
||||
### session-memory details
|
||||
|
||||
Extracts the last 15 user/assistant messages, generates a descriptive filename slug via LLM, and saves to `<workspace>/memory/YYYY-MM-DD-slug.md`. Requires `workspace.dir` to be configured.
|
||||
Extracts the last 15 user/assistant messages, generates a descriptive filename slug via LLM, and saves to `<workspace>/memory/YYYY-MM-DD-slug.md` using the host local date. Requires `workspace.dir` to be configured.
|
||||
|
||||
<a id="bootstrap-extra-files"></a>
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ sidebarTitle: "Background tasks"
|
||||
---
|
||||
|
||||
<Note>
|
||||
Looking for scheduling? See [Automation & Tasks](/automation) for choosing the right mechanism. This page covers **tracking** background work, not scheduling it.
|
||||
Looking for scheduling? See [Automation and tasks](/automation) for choosing the right mechanism. This page is the activity ledger for background work, not the scheduler.
|
||||
</Note>
|
||||
|
||||
Background tasks track work that runs **outside your main conversation session**: ACP runs, subagent spawns, isolated cron job executions, and CLI-initiated operations.
|
||||
|
||||
@@ -5,10 +5,12 @@ read_when:
|
||||
- Debugging multi-agent replies in WhatsApp
|
||||
status: experimental
|
||||
title: "Broadcast groups"
|
||||
sidebarTitle: "Broadcast groups"
|
||||
---
|
||||
|
||||
**Status:** Experimental
|
||||
**Version:** Added in 2026.1.9
|
||||
<Note>
|
||||
**Status:** Experimental. Added in 2026.1.9.
|
||||
</Note>
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -18,55 +20,55 @@ Current scope: **WhatsApp only** (web channel).
|
||||
|
||||
Broadcast groups are evaluated after channel allowlists and group activation rules. In WhatsApp groups, this means broadcasts happen when OpenClaw would normally reply (for example: on mention, depending on your group settings).
|
||||
|
||||
## Use Cases
|
||||
## Use cases
|
||||
|
||||
### 1. Specialized Agent Teams
|
||||
<AccordionGroup>
|
||||
<Accordion title="1. Specialized agent teams">
|
||||
Deploy multiple agents with atomic, focused responsibilities:
|
||||
|
||||
Deploy multiple agents with atomic, focused responsibilities:
|
||||
```
|
||||
Group: "Development Team"
|
||||
Agents:
|
||||
- CodeReviewer (reviews code snippets)
|
||||
- DocumentationBot (generates docs)
|
||||
- SecurityAuditor (checks for vulnerabilities)
|
||||
- TestGenerator (suggests test cases)
|
||||
```
|
||||
|
||||
```
|
||||
Group: "Development Team"
|
||||
Agents:
|
||||
- CodeReviewer (reviews code snippets)
|
||||
- DocumentationBot (generates docs)
|
||||
- SecurityAuditor (checks for vulnerabilities)
|
||||
- TestGenerator (suggests test cases)
|
||||
```
|
||||
Each agent processes the same message and provides its specialized perspective.
|
||||
|
||||
Each agent processes the same message and provides its specialized perspective.
|
||||
|
||||
### 2. Multi-Language Support
|
||||
|
||||
```
|
||||
Group: "International Support"
|
||||
Agents:
|
||||
- Agent_EN (responds in English)
|
||||
- Agent_DE (responds in German)
|
||||
- Agent_ES (responds in Spanish)
|
||||
```
|
||||
|
||||
### 3. Quality Assurance Workflows
|
||||
|
||||
```
|
||||
Group: "Customer Support"
|
||||
Agents:
|
||||
- SupportAgent (provides answer)
|
||||
- QAAgent (reviews quality, only responds if issues found)
|
||||
```
|
||||
|
||||
### 4. Task Automation
|
||||
|
||||
```
|
||||
Group: "Project Management"
|
||||
Agents:
|
||||
- TaskTracker (updates task database)
|
||||
- TimeLogger (logs time spent)
|
||||
- ReportGenerator (creates summaries)
|
||||
```
|
||||
</Accordion>
|
||||
<Accordion title="2. Multi-language support">
|
||||
```
|
||||
Group: "International Support"
|
||||
Agents:
|
||||
- Agent_EN (responds in English)
|
||||
- Agent_DE (responds in German)
|
||||
- Agent_ES (responds in Spanish)
|
||||
```
|
||||
</Accordion>
|
||||
<Accordion title="3. Quality assurance workflows">
|
||||
```
|
||||
Group: "Customer Support"
|
||||
Agents:
|
||||
- SupportAgent (provides answer)
|
||||
- QAAgent (reviews quality, only responds if issues found)
|
||||
```
|
||||
</Accordion>
|
||||
<Accordion title="4. Task automation">
|
||||
```
|
||||
Group: "Project Management"
|
||||
Agents:
|
||||
- TaskTracker (updates task database)
|
||||
- TimeLogger (logs time spent)
|
||||
- ReportGenerator (creates summaries)
|
||||
```
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Configuration
|
||||
|
||||
### Basic Setup
|
||||
### Basic setup
|
||||
|
||||
Add a top-level `broadcast` section (next to `bindings`). Keys are WhatsApp peer ids:
|
||||
|
||||
@@ -83,37 +85,40 @@ Add a top-level `broadcast` section (next to `bindings`). Keys are WhatsApp peer
|
||||
|
||||
**Result:** When OpenClaw would reply in this chat, it will run all three agents.
|
||||
|
||||
### Processing Strategy
|
||||
### Processing strategy
|
||||
|
||||
Control how agents process messages:
|
||||
|
||||
#### Parallel (Default)
|
||||
<Tabs>
|
||||
<Tab title="parallel (default)">
|
||||
All agents process simultaneously:
|
||||
|
||||
All agents process simultaneously:
|
||||
```json
|
||||
{
|
||||
"broadcast": {
|
||||
"strategy": "parallel",
|
||||
"120363403215116621@g.us": ["alfred", "baerbel"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"broadcast": {
|
||||
"strategy": "parallel",
|
||||
"120363403215116621@g.us": ["alfred", "baerbel"]
|
||||
}
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="sequential">
|
||||
Agents process in order (one waits for previous to finish):
|
||||
|
||||
#### Sequential
|
||||
```json
|
||||
{
|
||||
"broadcast": {
|
||||
"strategy": "sequential",
|
||||
"120363403215116621@g.us": ["alfred", "baerbel"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Agents process in order (one waits for previous to finish):
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
```json
|
||||
{
|
||||
"broadcast": {
|
||||
"strategy": "sequential",
|
||||
"120363403215116621@g.us": ["alfred", "baerbel"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Complete Example
|
||||
### Complete example
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -148,22 +153,32 @@ Agents process in order (one waits for previous to finish):
|
||||
}
|
||||
```
|
||||
|
||||
## How It Works
|
||||
## How it works
|
||||
|
||||
### Message Flow
|
||||
### Message flow
|
||||
|
||||
1. **Incoming message** arrives in a WhatsApp group
|
||||
2. **Broadcast check**: System checks if peer ID is in `broadcast`
|
||||
3. **If in broadcast list**:
|
||||
- All listed agents process the message
|
||||
- Each agent has its own session key and isolated context
|
||||
- Agents process in parallel (default) or sequentially
|
||||
4. **If not in broadcast list**:
|
||||
- Normal routing applies (first matching binding)
|
||||
<Steps>
|
||||
<Step title="Incoming message arrives">
|
||||
A WhatsApp group or DM message arrives.
|
||||
</Step>
|
||||
<Step title="Broadcast check">
|
||||
System checks if peer ID is in `broadcast`.
|
||||
</Step>
|
||||
<Step title="If in broadcast list">
|
||||
- All listed agents process the message.
|
||||
- Each agent has its own session key and isolated context.
|
||||
- Agents process in parallel (default) or sequentially.
|
||||
</Step>
|
||||
<Step title="If not in broadcast list">
|
||||
Normal routing applies (first matching binding).
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
Note: broadcast groups do not bypass channel allowlists or group activation rules (mentions/commands/etc). They only change _which agents run_ when a message is eligible for processing.
|
||||
<Note>
|
||||
Broadcast groups do not bypass channel allowlists or group activation rules (mentions/commands/etc). They only change _which agents run_ when a message is eligible for processing.
|
||||
</Note>
|
||||
|
||||
### Session Isolation
|
||||
### Session isolation
|
||||
|
||||
Each agent in a broadcast group maintains completely separate:
|
||||
|
||||
@@ -181,92 +196,95 @@ This allows each agent to have:
|
||||
- Different models (e.g., opus vs. sonnet)
|
||||
- Different skills installed
|
||||
|
||||
### Example: Isolated Sessions
|
||||
### Example: isolated sessions
|
||||
|
||||
In group `120363403215116621@g.us` with agents `["alfred", "baerbel"]`:
|
||||
|
||||
**Alfred's context:**
|
||||
<Tabs>
|
||||
<Tab title="Alfred's context">
|
||||
```
|
||||
Session: agent:alfred:whatsapp:group:120363403215116621@g.us
|
||||
History: [user message, alfred's previous responses]
|
||||
Workspace: /Users/user/openclaw-alfred/
|
||||
Tools: read, write, exec
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Bärbel's context">
|
||||
```
|
||||
Session: agent:baerbel:whatsapp:group:120363403215116621@g.us
|
||||
History: [user message, baerbel's previous responses]
|
||||
Workspace: /Users/user/openclaw-baerbel/
|
||||
Tools: read only
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
```
|
||||
Session: agent:alfred:whatsapp:group:120363403215116621@g.us
|
||||
History: [user message, alfred's previous responses]
|
||||
Workspace: /Users/user/openclaw-alfred/
|
||||
Tools: read, write, exec
|
||||
```
|
||||
## Best practices
|
||||
|
||||
**Bärbel's context:**
|
||||
<AccordionGroup>
|
||||
<Accordion title="1. Keep agents focused">
|
||||
Design each agent with a single, clear responsibility:
|
||||
|
||||
```
|
||||
Session: agent:baerbel:whatsapp:group:120363403215116621@g.us
|
||||
History: [user message, baerbel's previous responses]
|
||||
Workspace: /Users/user/openclaw-baerbel/
|
||||
Tools: read only
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Keep Agents Focused
|
||||
|
||||
Design each agent with a single, clear responsibility:
|
||||
|
||||
```json
|
||||
{
|
||||
"broadcast": {
|
||||
"DEV_GROUP": ["formatter", "linter", "tester"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
✅ **Good:** Each agent has one job
|
||||
❌ **Bad:** One generic "dev-helper" agent
|
||||
|
||||
### 2. Use Descriptive Names
|
||||
|
||||
Make it clear what each agent does:
|
||||
|
||||
```json
|
||||
{
|
||||
"agents": {
|
||||
"security-scanner": { "name": "Security Scanner" },
|
||||
"code-formatter": { "name": "Code Formatter" },
|
||||
"test-generator": { "name": "Test Generator" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Configure Different Tool Access
|
||||
|
||||
Give agents only the tools they need:
|
||||
|
||||
```json
|
||||
{
|
||||
"agents": {
|
||||
"reviewer": {
|
||||
"tools": { "allow": ["read", "exec"] } // Read-only
|
||||
},
|
||||
"fixer": {
|
||||
"tools": { "allow": ["read", "write", "edit", "exec"] } // Read-write
|
||||
```json
|
||||
{
|
||||
"broadcast": {
|
||||
"DEV_GROUP": ["formatter", "linter", "tester"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
### 4. Monitor Performance
|
||||
✅ **Good:** Each agent has one job. ❌ **Bad:** One generic "dev-helper" agent.
|
||||
|
||||
With many agents, consider:
|
||||
</Accordion>
|
||||
<Accordion title="2. Use descriptive names">
|
||||
Make it clear what each agent does:
|
||||
|
||||
- Using `"strategy": "parallel"` (default) for speed
|
||||
- Limiting broadcast groups to 5-10 agents
|
||||
- Using faster models for simpler agents
|
||||
```json
|
||||
{
|
||||
"agents": {
|
||||
"security-scanner": { "name": "Security Scanner" },
|
||||
"code-formatter": { "name": "Code Formatter" },
|
||||
"test-generator": { "name": "Test Generator" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Handle Failures Gracefully
|
||||
</Accordion>
|
||||
<Accordion title="3. Configure different tool access">
|
||||
Give agents only the tools they need:
|
||||
|
||||
Agents fail independently. One agent's error doesn't block others:
|
||||
```json
|
||||
{
|
||||
"agents": {
|
||||
"reviewer": {
|
||||
"tools": { "allow": ["read", "exec"] } // Read-only
|
||||
},
|
||||
"fixer": {
|
||||
"tools": { "allow": ["read", "write", "edit", "exec"] } // Read-write
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
Message → [Agent A ✓, Agent B ✗ error, Agent C ✓]
|
||||
Result: Agent A and C respond, Agent B logs error
|
||||
```
|
||||
</Accordion>
|
||||
<Accordion title="4. Monitor performance">
|
||||
With many agents, consider:
|
||||
|
||||
- Using `"strategy": "parallel"` (default) for speed
|
||||
- Limiting broadcast groups to 5-10 agents
|
||||
- Using faster models for simpler agents
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="5. Handle failures gracefully">
|
||||
Agents fail independently. One agent's error doesn't block others:
|
||||
|
||||
```
|
||||
Message → [Agent A ✓, Agent B ✗ error, Agent C ✓]
|
||||
Result: Agent A and C respond, Agent B logs error
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Compatibility
|
||||
|
||||
@@ -297,108 +315,116 @@ Broadcast groups work alongside existing routing:
|
||||
}
|
||||
```
|
||||
|
||||
- `GROUP_A`: Only alfred responds (normal routing)
|
||||
- `GROUP_B`: agent1 AND agent2 respond (broadcast)
|
||||
- `GROUP_A`: Only alfred responds (normal routing).
|
||||
- `GROUP_B`: agent1 AND agent2 respond (broadcast).
|
||||
|
||||
<Note>
|
||||
**Precedence:** `broadcast` takes priority over `bindings`.
|
||||
</Note>
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Agents Not Responding
|
||||
<AccordionGroup>
|
||||
<Accordion title="Agents not responding">
|
||||
**Check:**
|
||||
|
||||
**Check:**
|
||||
1. Agent IDs exist in `agents.list`.
|
||||
2. Peer ID format is correct (e.g., `120363403215116621@g.us`).
|
||||
3. Agents are not in deny lists.
|
||||
|
||||
1. Agent IDs exist in `agents.list`
|
||||
2. Peer ID format is correct (e.g., `120363403215116621@g.us`)
|
||||
3. Agents are not in deny lists
|
||||
**Debug:**
|
||||
|
||||
**Debug:**
|
||||
```bash
|
||||
tail -f ~/.openclaw/logs/gateway.log | grep broadcast
|
||||
```
|
||||
|
||||
```bash
|
||||
tail -f ~/.openclaw/logs/gateway.log | grep broadcast
|
||||
```
|
||||
</Accordion>
|
||||
<Accordion title="Only one agent responding">
|
||||
**Cause:** Peer ID might be in `bindings` but not `broadcast`.
|
||||
|
||||
### Only One Agent Responding
|
||||
**Fix:** Add to broadcast config or remove from bindings.
|
||||
|
||||
**Cause:** Peer ID might be in `bindings` but not `broadcast`.
|
||||
</Accordion>
|
||||
<Accordion title="Performance issues">
|
||||
If slow with many agents:
|
||||
|
||||
**Fix:** Add to broadcast config or remove from bindings.
|
||||
- Reduce number of agents per group.
|
||||
- Use lighter models (sonnet instead of opus).
|
||||
- Check sandbox startup time.
|
||||
|
||||
### Performance Issues
|
||||
|
||||
**If slow with many agents:**
|
||||
|
||||
- Reduce number of agents per group
|
||||
- Use lighter models (sonnet instead of opus)
|
||||
- Check sandbox startup time
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Examples
|
||||
|
||||
### Example 1: Code Review Team
|
||||
|
||||
```json
|
||||
{
|
||||
"broadcast": {
|
||||
"strategy": "parallel",
|
||||
"120363403215116621@g.us": [
|
||||
"code-formatter",
|
||||
"security-scanner",
|
||||
"test-coverage",
|
||||
"docs-checker"
|
||||
]
|
||||
},
|
||||
"agents": {
|
||||
"list": [
|
||||
{
|
||||
"id": "code-formatter",
|
||||
"workspace": "~/agents/formatter",
|
||||
"tools": { "allow": ["read", "write"] }
|
||||
<AccordionGroup>
|
||||
<Accordion title="Example 1: Code review team">
|
||||
```json
|
||||
{
|
||||
"broadcast": {
|
||||
"strategy": "parallel",
|
||||
"120363403215116621@g.us": [
|
||||
"code-formatter",
|
||||
"security-scanner",
|
||||
"test-coverage",
|
||||
"docs-checker"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "security-scanner",
|
||||
"workspace": "~/agents/security",
|
||||
"tools": { "allow": ["read", "exec"] }
|
||||
"agents": {
|
||||
"list": [
|
||||
{
|
||||
"id": "code-formatter",
|
||||
"workspace": "~/agents/formatter",
|
||||
"tools": { "allow": ["read", "write"] }
|
||||
},
|
||||
{
|
||||
"id": "security-scanner",
|
||||
"workspace": "~/agents/security",
|
||||
"tools": { "allow": ["read", "exec"] }
|
||||
},
|
||||
{
|
||||
"id": "test-coverage",
|
||||
"workspace": "~/agents/testing",
|
||||
"tools": { "allow": ["read", "exec"] }
|
||||
},
|
||||
{ "id": "docs-checker", "workspace": "~/agents/docs", "tools": { "allow": ["read"] } }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**User sends:** Code snippet.
|
||||
|
||||
**Responses:**
|
||||
|
||||
- code-formatter: "Fixed indentation and added type hints"
|
||||
- security-scanner: "⚠️ SQL injection vulnerability in line 12"
|
||||
- test-coverage: "Coverage is 45%, missing tests for error cases"
|
||||
- docs-checker: "Missing docstring for function `process_data`"
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Example 2: Multi-language support">
|
||||
```json
|
||||
{
|
||||
"broadcast": {
|
||||
"strategy": "sequential",
|
||||
"+15555550123": ["detect-language", "translator-en", "translator-de"]
|
||||
},
|
||||
{
|
||||
"id": "test-coverage",
|
||||
"workspace": "~/agents/testing",
|
||||
"tools": { "allow": ["read", "exec"] }
|
||||
},
|
||||
{ "id": "docs-checker", "workspace": "~/agents/docs", "tools": { "allow": ["read"] } }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
"agents": {
|
||||
"list": [
|
||||
{ "id": "detect-language", "workspace": "~/agents/lang-detect" },
|
||||
{ "id": "translator-en", "workspace": "~/agents/translate-en" },
|
||||
{ "id": "translator-de", "workspace": "~/agents/translate-de" }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
**User sends:** Code snippet
|
||||
**Responses:**
|
||||
## API reference
|
||||
|
||||
- code-formatter: "Fixed indentation and added type hints"
|
||||
- security-scanner: "⚠️ SQL injection vulnerability in line 12"
|
||||
- test-coverage: "Coverage is 45%, missing tests for error cases"
|
||||
- docs-checker: "Missing docstring for function `process_data`"
|
||||
|
||||
### Example 2: Multi-Language Support
|
||||
|
||||
```json
|
||||
{
|
||||
"broadcast": {
|
||||
"strategy": "sequential",
|
||||
"+15555550123": ["detect-language", "translator-en", "translator-de"]
|
||||
},
|
||||
"agents": {
|
||||
"list": [
|
||||
{ "id": "detect-language", "workspace": "~/agents/lang-detect" },
|
||||
{ "id": "translator-en", "workspace": "~/agents/translate-en" },
|
||||
{ "id": "translator-de", "workspace": "~/agents/translate-de" }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### Config Schema
|
||||
### Config schema
|
||||
|
||||
```typescript
|
||||
interface OpenClawConfig {
|
||||
@@ -411,20 +437,21 @@ interface OpenClawConfig {
|
||||
|
||||
### Fields
|
||||
|
||||
- `strategy` (optional): How to process agents
|
||||
- `"parallel"` (default): All agents process simultaneously
|
||||
- `"sequential"`: Agents process in array order
|
||||
- `[peerId]`: WhatsApp group JID, E.164 number, or other peer ID
|
||||
- Value: Array of agent IDs that should process messages
|
||||
<ParamField path="strategy" type='"parallel" | "sequential"' default='"parallel"'>
|
||||
How to process agents. `parallel` runs all agents simultaneously; `sequential` runs them in array order.
|
||||
</ParamField>
|
||||
<ParamField path="[peerId]" type="string[]">
|
||||
WhatsApp group JID, E.164 number, or other peer ID. Value is the array of agent IDs that should process messages.
|
||||
</ParamField>
|
||||
|
||||
## Limitations
|
||||
|
||||
1. **Max agents:** No hard limit, but 10+ agents may be slow
|
||||
2. **Shared context:** Agents don't see each other's responses (by design)
|
||||
3. **Message ordering:** Parallel responses may arrive in any order
|
||||
4. **Rate limits:** All agents count toward WhatsApp rate limits
|
||||
1. **Max agents:** No hard limit, but 10+ agents may be slow.
|
||||
2. **Shared context:** Agents don't see each other's responses (by design).
|
||||
3. **Message ordering:** Parallel responses may arrive in any order.
|
||||
4. **Rate limits:** All agents count toward WhatsApp rate limits.
|
||||
|
||||
## Future Enhancements
|
||||
## Future enhancements
|
||||
|
||||
Planned features:
|
||||
|
||||
@@ -435,8 +462,8 @@ Planned features:
|
||||
|
||||
## Related
|
||||
|
||||
- [Groups](/channels/groups)
|
||||
- [Channel routing](/channels/channel-routing)
|
||||
- [Pairing](/channels/pairing)
|
||||
- [Groups](/channels/groups)
|
||||
- [Multi-agent sandbox tools](/tools/multi-agent-sandbox-tools)
|
||||
- [Pairing](/channels/pairing)
|
||||
- [Session management](/concepts/session)
|
||||
|
||||
@@ -16,7 +16,9 @@ Feishu/Lark is an all-in-one collaboration platform where teams chat, share docu
|
||||
|
||||
## Quick start
|
||||
|
||||
> **Requires OpenClaw 2026.4.25 or above.** Run `openclaw --version` to check. Upgrade with `openclaw update`.
|
||||
<Note>
|
||||
Requires OpenClaw 2026.4.25 or above. Run `openclaw --version` to check. Upgrade with `openclaw update`.
|
||||
</Note>
|
||||
|
||||
<Steps>
|
||||
<Step title="Run the channel setup wizard">
|
||||
@@ -169,7 +171,9 @@ openclaw pairing list feishu
|
||||
| `/reset` | Reset the current session |
|
||||
| `/model` | Show or switch the AI model |
|
||||
|
||||
> Feishu/Lark does not support native slash-command menus, so send these as plain text messages.
|
||||
<Note>
|
||||
Feishu/Lark does not support native slash-command menus, so send these as plain text messages.
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
@@ -213,6 +217,11 @@ openclaw pairing list feishu
|
||||
appId: "cli_xxx",
|
||||
appSecret: "xxx",
|
||||
name: "Primary bot",
|
||||
tts: {
|
||||
providers: {
|
||||
openai: { voice: "shimmer" },
|
||||
},
|
||||
},
|
||||
},
|
||||
backup: {
|
||||
appId: "cli_yyy",
|
||||
@@ -227,6 +236,10 @@ openclaw pairing list feishu
|
||||
```
|
||||
|
||||
`defaultAccount` controls which account is used when outbound APIs do not specify an `accountId`.
|
||||
`accounts.<id>.tts` uses the same shape as `messages.tts` and deep-merges over
|
||||
global TTS config, so multi-bot Feishu setups can keep shared provider
|
||||
credentials globally while overriding only voice, model, persona, or auto mode
|
||||
per account.
|
||||
|
||||
### Message limits
|
||||
|
||||
@@ -386,6 +399,7 @@ Full configuration: [Gateway configuration](/gateway/configuration)
|
||||
| `channels.feishu.accounts.<id>.appId` | App ID | — |
|
||||
| `channels.feishu.accounts.<id>.appSecret` | App Secret | — |
|
||||
| `channels.feishu.accounts.<id>.domain` | Per-account domain override | `feishu` |
|
||||
| `channels.feishu.accounts.<id>.tts` | Per-account TTS override | `messages.tts` |
|
||||
| `channels.feishu.dmPolicy` | DM policy | `allowlist` |
|
||||
| `channels.feishu.allowFrom` | DM allowlist (open_id list) | [BotOwnerId] |
|
||||
| `channels.feishu.groupPolicy` | Group policy | `allowlist` |
|
||||
|
||||
@@ -7,7 +7,9 @@ title: "Group messages"
|
||||
|
||||
Goal: let Clawd sit in WhatsApp groups, wake up only when pinged, and keep that thread separate from the personal DM session.
|
||||
|
||||
Note: `agents.list[].groupChat.mentionPatterns` is now used by Telegram/Discord/Slack/iMessage as well; this doc focuses on WhatsApp-specific behavior. For multi-agent setups, set `agents.list[].groupChat.mentionPatterns` per agent (or use `messages.groupChat.mentionPatterns` as a global fallback).
|
||||
<Note>
|
||||
`agents.list[].groupChat.mentionPatterns` is also used by Telegram, Discord, Slack, and iMessage. This doc focuses on WhatsApp-specific behavior. For multi-agent setups, set `agents.list[].groupChat.mentionPatterns` per agent, or use `messages.groupChat.mentionPatterns` as a global fallback.
|
||||
</Note>
|
||||
|
||||
## Current implementation (2025-12-03)
|
||||
|
||||
|
||||
@@ -3,14 +3,14 @@ summary: "Group chat behavior across surfaces (Discord/iMessage/Matrix/Microsoft
|
||||
read_when:
|
||||
- Changing group chat behavior or mention gating
|
||||
title: "Groups"
|
||||
sidebarTitle: "Groups"
|
||||
---
|
||||
|
||||
OpenClaw treats group chats consistently across surfaces: Discord, iMessage, Matrix, Microsoft Teams, Signal, Slack, Telegram, WhatsApp, Zalo.
|
||||
|
||||
## Beginner intro (2 minutes)
|
||||
|
||||
OpenClaw “lives” on your own messaging accounts. There is no separate WhatsApp bot user.
|
||||
If **you** are in a group, OpenClaw can see that group and respond there.
|
||||
OpenClaw "lives" on your own messaging accounts. There is no separate WhatsApp bot user. If **you** are in a group, OpenClaw can see that group and respond there.
|
||||
|
||||
Default behavior:
|
||||
|
||||
@@ -19,11 +19,13 @@ Default behavior:
|
||||
|
||||
Translation: allowlisted senders can trigger OpenClaw by mentioning it.
|
||||
|
||||
> TL;DR
|
||||
>
|
||||
> - **DM access** is controlled by `*.allowFrom`.
|
||||
> - **Group access** is controlled by `*.groupPolicy` + allowlists (`*.groups`, `*.groupAllowFrom`).
|
||||
> - **Reply triggering** is controlled by mention gating (`requireMention`, `/activation`).
|
||||
<Note>
|
||||
**TL;DR**
|
||||
|
||||
- **DM access** is controlled by `*.allowFrom`.
|
||||
- **Group access** is controlled by `*.groupPolicy` + allowlists (`*.groups`, `*.groupAllowFrom`).
|
||||
- **Reply triggering** is controlled by mention gating (`requireMention`, `/activation`).
|
||||
</Note>
|
||||
|
||||
Quick flow (what happens to a group message):
|
||||
|
||||
@@ -43,18 +45,20 @@ Two different controls are involved in group safety:
|
||||
|
||||
By default, OpenClaw prioritizes normal chat behavior and keeps context mostly as received. This means allowlists primarily decide who can trigger actions, not a universal redaction boundary for every quoted or historical snippet.
|
||||
|
||||
Current behavior is channel-specific:
|
||||
<AccordionGroup>
|
||||
<Accordion title="Current behavior is channel-specific">
|
||||
- Some channels already apply sender-based filtering for supplemental context in specific paths (for example Slack thread seeding, Matrix reply/thread lookups).
|
||||
- Other channels still pass quote/reply/forward context through as received.
|
||||
</Accordion>
|
||||
<Accordion title="Hardening direction (planned)">
|
||||
- `contextVisibility: "all"` (default) keeps current as-received behavior.
|
||||
- `contextVisibility: "allowlist"` filters supplemental context to allowlisted senders.
|
||||
- `contextVisibility: "allowlist_quote"` is `allowlist` plus one explicit quote/reply exception.
|
||||
|
||||
- Some channels already apply sender-based filtering for supplemental context in specific paths (for example Slack thread seeding, Matrix reply/thread lookups).
|
||||
- Other channels still pass quote/reply/forward context through as received.
|
||||
Until this hardening model is implemented consistently across channels, expect differences by surface.
|
||||
|
||||
Hardening direction (planned):
|
||||
|
||||
- `contextVisibility: "all"` (default) keeps current as-received behavior.
|
||||
- `contextVisibility: "allowlist"` filters supplemental context to allowlisted senders.
|
||||
- `contextVisibility: "allowlist_quote"` is `allowlist` plus one explicit quote/reply exception.
|
||||
|
||||
Until this hardening model is implemented consistently across channels, expect differences by surface.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||

|
||||
|
||||
@@ -78,63 +82,69 @@ If you want...
|
||||
|
||||
## Pattern: personal DMs + public groups (single agent)
|
||||
|
||||
Yes — this works well if your “personal” traffic is **DMs** and your “public” traffic is **groups**.
|
||||
Yes — this works well if your "personal" traffic is **DMs** and your "public" traffic is **groups**.
|
||||
|
||||
Why: in single-agent mode, DMs typically land in the **main** session key (`agent:main:main`), while groups always use **non-main** session keys (`agent:main:<channel>:group:<id>`). If you enable sandboxing with `mode: "non-main"`, those group sessions run in the configured sandbox backend while your main DM session stays on-host. Docker is the default backend if you do not choose one.
|
||||
|
||||
This gives you one agent “brain” (shared workspace + memory), but two execution postures:
|
||||
This gives you one agent "brain" (shared workspace + memory), but two execution postures:
|
||||
|
||||
- **DMs**: full tools (host)
|
||||
- **Groups**: sandbox + restricted tools
|
||||
|
||||
> If you need truly separate workspaces/personas (“personal” and “public” must never mix), use a second agent + bindings. See [Multi-Agent Routing](/concepts/multi-agent).
|
||||
<Note>
|
||||
If you need truly separate workspaces/personas ("personal" and "public" must never mix), use a second agent + bindings. See [Multi-Agent Routing](/concepts/multi-agent).
|
||||
</Note>
|
||||
|
||||
Example (DMs on host, groups sandboxed + messaging-only tools):
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
defaults: {
|
||||
sandbox: {
|
||||
mode: "non-main", // groups/channels are non-main -> sandboxed
|
||||
scope: "session", // strongest isolation (one container per group/channel)
|
||||
workspaceAccess: "none",
|
||||
},
|
||||
},
|
||||
},
|
||||
tools: {
|
||||
sandbox: {
|
||||
tools: {
|
||||
// If allow is non-empty, everything else is blocked (deny still wins).
|
||||
allow: ["group:messaging", "group:sessions"],
|
||||
deny: ["group:runtime", "group:fs", "group:ui", "nodes", "cron", "gateway"],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Want “groups can only see folder X” instead of “no host access”? Keep `workspaceAccess: "none"` and mount only allowlisted paths into the sandbox:
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
defaults: {
|
||||
sandbox: {
|
||||
mode: "non-main",
|
||||
scope: "session",
|
||||
workspaceAccess: "none",
|
||||
docker: {
|
||||
binds: [
|
||||
// hostPath:containerPath:mode
|
||||
"/home/user/FriendsShared:/data:ro",
|
||||
],
|
||||
<Tabs>
|
||||
<Tab title="DMs on host, groups sandboxed">
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
defaults: {
|
||||
sandbox: {
|
||||
mode: "non-main", // groups/channels are non-main -> sandboxed
|
||||
scope: "session", // strongest isolation (one container per group/channel)
|
||||
workspaceAccess: "none",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
tools: {
|
||||
sandbox: {
|
||||
tools: {
|
||||
// If allow is non-empty, everything else is blocked (deny still wins).
|
||||
allow: ["group:messaging", "group:sessions"],
|
||||
deny: ["group:runtime", "group:fs", "group:ui", "nodes", "cron", "gateway"],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Groups see only an allowlisted folder">
|
||||
Want "groups can only see folder X" instead of "no host access"? Keep `workspaceAccess: "none"` and mount only allowlisted paths into the sandbox:
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
defaults: {
|
||||
sandbox: {
|
||||
mode: "non-main",
|
||||
scope: "session",
|
||||
workspaceAccess: "none",
|
||||
docker: {
|
||||
binds: [
|
||||
// hostPath:containerPath:mode
|
||||
"/home/user/FriendsShared:/data:ro",
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
Related:
|
||||
|
||||
@@ -202,33 +212,40 @@ Control how group/room messages are handled per channel:
|
||||
| `"disabled"` | Block all group messages entirely. |
|
||||
| `"allowlist"` | Only allow groups/rooms that match the configured allowlist. |
|
||||
|
||||
Notes:
|
||||
|
||||
- `groupPolicy` is separate from mention-gating (which requires @mentions).
|
||||
- WhatsApp/Telegram/Signal/iMessage/Microsoft Teams/Zalo: use `groupAllowFrom` (fallback: explicit `allowFrom`).
|
||||
- DM pairing approvals (`*-allowFrom` store entries) apply to DM access only; group sender authorization stays explicit to group allowlists.
|
||||
- Discord: allowlist uses `channels.discord.guilds.<id>.channels`.
|
||||
- Slack: allowlist uses `channels.slack.channels`.
|
||||
- Matrix: allowlist uses `channels.matrix.groups`. Prefer room IDs or aliases; joined-room name lookup is best-effort, and unresolved names are ignored at runtime. Use `channels.matrix.groupAllowFrom` to restrict senders; per-room `users` allowlists are also supported.
|
||||
- Group DMs are controlled separately (`channels.discord.dm.*`, `channels.slack.dm.*`).
|
||||
- Telegram allowlist can match user IDs (`"123456789"`, `"telegram:123456789"`, `"tg:123456789"`) or usernames (`"@alice"` or `"alice"`); prefixes are case-insensitive.
|
||||
- Default is `groupPolicy: "allowlist"`; if your group allowlist is empty, group messages are blocked.
|
||||
- Runtime safety: when a provider block is completely missing (`channels.<provider>` absent), group policy falls back to a fail-closed mode (typically `allowlist`) instead of inheriting `channels.defaults.groupPolicy`.
|
||||
<AccordionGroup>
|
||||
<Accordion title="Per-channel notes">
|
||||
- `groupPolicy` is separate from mention-gating (which requires @mentions).
|
||||
- WhatsApp/Telegram/Signal/iMessage/Microsoft Teams/Zalo: use `groupAllowFrom` (fallback: explicit `allowFrom`).
|
||||
- DM pairing approvals (`*-allowFrom` store entries) apply to DM access only; group sender authorization stays explicit to group allowlists.
|
||||
- Discord: allowlist uses `channels.discord.guilds.<id>.channels`.
|
||||
- Slack: allowlist uses `channels.slack.channels`.
|
||||
- Matrix: allowlist uses `channels.matrix.groups`. Prefer room IDs or aliases; joined-room name lookup is best-effort, and unresolved names are ignored at runtime. Use `channels.matrix.groupAllowFrom` to restrict senders; per-room `users` allowlists are also supported.
|
||||
- Group DMs are controlled separately (`channels.discord.dm.*`, `channels.slack.dm.*`).
|
||||
- Telegram allowlist can match user IDs (`"123456789"`, `"telegram:123456789"`, `"tg:123456789"`) or usernames (`"@alice"` or `"alice"`); prefixes are case-insensitive.
|
||||
- Default is `groupPolicy: "allowlist"`; if your group allowlist is empty, group messages are blocked.
|
||||
- Runtime safety: when a provider block is completely missing (`channels.<provider>` absent), group policy falls back to a fail-closed mode (typically `allowlist`) instead of inheriting `channels.defaults.groupPolicy`.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
Quick mental model (evaluation order for group messages):
|
||||
|
||||
1. `groupPolicy` (open/disabled/allowlist)
|
||||
2. group allowlists (`*.groups`, `*.groupAllowFrom`, channel-specific allowlist)
|
||||
3. mention gating (`requireMention`, `/activation`)
|
||||
<Steps>
|
||||
<Step title="groupPolicy">
|
||||
`groupPolicy` (open/disabled/allowlist).
|
||||
</Step>
|
||||
<Step title="Group allowlists">
|
||||
Group allowlists (`*.groups`, `*.groupAllowFrom`, channel-specific allowlist).
|
||||
</Step>
|
||||
<Step title="Mention gating">
|
||||
Mention gating (`requireMention`, `/activation`).
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## Mention gating (default)
|
||||
|
||||
Group messages require a mention unless overridden per group. Defaults live per subsystem under `*.groups."*"`.
|
||||
|
||||
Replying to a bot message counts as an implicit mention when the channel
|
||||
supports reply metadata. Quoting a bot message can also count as an implicit
|
||||
mention on channels that expose quote metadata. Current built-in cases include
|
||||
Telegram, WhatsApp, Slack, Discord, Microsoft Teams, and ZaloUser.
|
||||
Replying to a bot message counts as an implicit mention when the channel supports reply metadata. Quoting a bot message can also count as an implicit mention on channels that expose quote metadata. Current built-in cases include Telegram, WhatsApp, Slack, Discord, Microsoft Teams, and ZaloUser.
|
||||
|
||||
```json5
|
||||
{
|
||||
@@ -266,32 +283,41 @@ Telegram, WhatsApp, Slack, Discord, Microsoft Teams, and ZaloUser.
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- `mentionPatterns` are case-insensitive safe regex patterns; invalid patterns and unsafe nested-repetition forms are ignored.
|
||||
- Surfaces that provide explicit mentions still pass; patterns are a fallback.
|
||||
- Per-agent override: `agents.list[].groupChat.mentionPatterns` (useful when multiple agents share a group).
|
||||
- Mention gating is only enforced when mention detection is possible (native mentions or `mentionPatterns` are configured).
|
||||
- Groups where silent replies are allowed treat clean empty or reasoning-only model turns as silent, equivalent to `NO_REPLY`. Direct chats still treat empty replies as a failed agent turn.
|
||||
- Discord defaults live in `channels.discord.guilds."*"` (overridable per guild/channel).
|
||||
- Group history context is wrapped uniformly across channels and is **pending-only** (messages skipped due to mention gating); use `messages.groupChat.historyLimit` for the global default and `channels.<channel>.historyLimit` (or `channels.<channel>.accounts.*.historyLimit`) for overrides. Set `0` to disable.
|
||||
<AccordionGroup>
|
||||
<Accordion title="Mention gating notes">
|
||||
- `mentionPatterns` are case-insensitive safe regex patterns; invalid patterns and unsafe nested-repetition forms are ignored.
|
||||
- Surfaces that provide explicit mentions still pass; patterns are a fallback.
|
||||
- Per-agent override: `agents.list[].groupChat.mentionPatterns` (useful when multiple agents share a group).
|
||||
- Mention gating is only enforced when mention detection is possible (native mentions or `mentionPatterns` are configured).
|
||||
- Groups where silent replies are allowed treat clean empty or reasoning-only model turns as silent, equivalent to `NO_REPLY`. Direct chats still treat empty replies as a failed agent turn.
|
||||
- Discord defaults live in `channels.discord.guilds."*"` (overridable per guild/channel).
|
||||
- Group history context is wrapped uniformly across channels and is **pending-only** (messages skipped due to mention gating); use `messages.groupChat.historyLimit` for the global default and `channels.<channel>.historyLimit` (or `channels.<channel>.accounts.*.historyLimit`) for overrides. Set `0` to disable.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Group/channel tool restrictions (optional)
|
||||
|
||||
Some channel configs support restricting which tools are available **inside a specific group/room/channel**.
|
||||
|
||||
- `tools`: allow/deny tools for the whole group.
|
||||
- `toolsBySender`: per-sender overrides within the group.
|
||||
Use explicit key prefixes:
|
||||
`id:<senderId>`, `e164:<phone>`, `username:<handle>`, `name:<displayName>`, and `"*"` wildcard.
|
||||
Legacy unprefixed keys are still accepted and matched as `id:` only.
|
||||
- `toolsBySender`: per-sender overrides within the group. Use explicit key prefixes: `id:<senderId>`, `e164:<phone>`, `username:<handle>`, `name:<displayName>`, and `"*"` wildcard. Legacy unprefixed keys are still accepted and matched as `id:` only.
|
||||
|
||||
Resolution order (most specific wins):
|
||||
|
||||
1. group/channel `toolsBySender` match
|
||||
2. group/channel `tools`
|
||||
3. default (`"*"`) `toolsBySender` match
|
||||
4. default (`"*"`) `tools`
|
||||
<Steps>
|
||||
<Step title="Group toolsBySender">
|
||||
Group/channel `toolsBySender` match.
|
||||
</Step>
|
||||
<Step title="Group tools">
|
||||
Group/channel `tools`.
|
||||
</Step>
|
||||
<Step title="Default toolsBySender">
|
||||
Default (`"*"`) `toolsBySender` match.
|
||||
</Step>
|
||||
<Step title="Default tools">
|
||||
Default (`"*"`) `tools`.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
Example (Telegram):
|
||||
|
||||
@@ -313,68 +339,67 @@ Example (Telegram):
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- Group/channel tool restrictions are applied in addition to global/agent tool policy (deny still wins).
|
||||
- Some channels use different nesting for rooms/channels (e.g., Discord `guilds.*.channels.*`, Slack `channels.*`, Microsoft Teams `teams.*.channels.*`).
|
||||
<Note>
|
||||
Group/channel tool restrictions are applied in addition to global/agent tool policy (deny still wins). Some channels use different nesting for rooms/channels (e.g., Discord `guilds.*.channels.*`, Slack `channels.*`, Microsoft Teams `teams.*.channels.*`).
|
||||
</Note>
|
||||
|
||||
## Group allowlists
|
||||
|
||||
When `channels.whatsapp.groups`, `channels.telegram.groups`, or `channels.imessage.groups` is configured, the keys act as a group allowlist. Use `"*"` to allow all groups while still setting default mention behavior.
|
||||
|
||||
Common confusion: DM pairing approval is not the same as group authorization.
|
||||
For channels that support DM pairing, the pairing store unlocks DMs only. Group commands still require explicit group sender authorization from config allowlists such as `groupAllowFrom` or the documented config fallback for that channel.
|
||||
<Warning>
|
||||
Common confusion: DM pairing approval is not the same as group authorization. For channels that support DM pairing, the pairing store unlocks DMs only. Group commands still require explicit group sender authorization from config allowlists such as `groupAllowFrom` or the documented config fallback for that channel.
|
||||
</Warning>
|
||||
|
||||
Common intents (copy/paste):
|
||||
|
||||
1. Disable all group replies
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: { whatsapp: { groupPolicy: "disabled" } },
|
||||
}
|
||||
```
|
||||
|
||||
2. Allow only specific groups (WhatsApp)
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
whatsapp: {
|
||||
groups: {
|
||||
"123@g.us": { requireMention: true },
|
||||
"456@g.us": { requireMention: false },
|
||||
<Tabs>
|
||||
<Tab title="Disable all group replies">
|
||||
```json5
|
||||
{
|
||||
channels: { whatsapp: { groupPolicy: "disabled" } },
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Allow only specific groups (WhatsApp)">
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
whatsapp: {
|
||||
groups: {
|
||||
"123@g.us": { requireMention: true },
|
||||
"456@g.us": { requireMention: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
3. Allow all groups but require mention (explicit)
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
whatsapp: {
|
||||
groups: { "*": { requireMention: true } },
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
4. Only the owner can trigger in groups (WhatsApp)
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
whatsapp: {
|
||||
groupPolicy: "allowlist",
|
||||
groupAllowFrom: ["+15551234567"],
|
||||
groups: { "*": { requireMention: true } },
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Allow all groups but require mention">
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
whatsapp: {
|
||||
groups: { "*": { requireMention: true } },
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Owner-only triggers (WhatsApp)">
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
whatsapp: {
|
||||
groupPolicy: "allowlist",
|
||||
groupAllowFrom: ["+15551234567"],
|
||||
groups: { "*": { requireMention: true } },
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Activation (owner-only)
|
||||
|
||||
@@ -383,7 +408,7 @@ Group owners can toggle per-group activation:
|
||||
- `/activation mention`
|
||||
- `/activation always`
|
||||
|
||||
Owner is determined by `channels.whatsapp.allowFrom` (or the bot’s self E.164 when unset). Send the command as a standalone message. Other surfaces currently ignore `/activation`.
|
||||
Owner is determined by `channels.whatsapp.allowFrom` (or the bot's self E.164 when unset). Send the command as a standalone message. Other surfaces currently ignore `/activation`.
|
||||
|
||||
## Context fields
|
||||
|
||||
@@ -395,7 +420,7 @@ Group inbound payloads set:
|
||||
- `WasMentioned` (mention gating result)
|
||||
- Telegram forum topics also include `MessageThreadId` and `IsForum`.
|
||||
|
||||
Channel specific notes:
|
||||
Channel-specific notes:
|
||||
|
||||
- BlueBubbles can optionally enrich unnamed macOS group participants from the local Contacts database before populating `GroupMembers`. This is off by default and only runs after normal group gating passes.
|
||||
|
||||
@@ -417,7 +442,7 @@ See [Group messages](/channels/group-messages) for WhatsApp-only behavior (histo
|
||||
|
||||
## Related
|
||||
|
||||
- [Group messages](/channels/group-messages)
|
||||
- [Broadcast groups](/channels/broadcast-groups)
|
||||
- [Channel routing](/channels/channel-routing)
|
||||
- [Group messages](/channels/group-messages)
|
||||
- [Pairing](/channels/pairing)
|
||||
|
||||
@@ -132,6 +132,8 @@ New user-defined `override` rules are inserted ahead of default suppress rules,
|
||||
|
||||
If you run Synapse behind a reverse proxy or workers, make sure `/_matrix/client/.../pushrules/` reaches Synapse correctly. Push delivery is handled by the main process or `synapse.app.pusher` / configured pusher workers — ensure those are healthy.
|
||||
|
||||
The rule uses the `event_property_is` push-rule condition (MSC3758, push rule v1.10), which was added to Synapse in 2023. Older Synapse releases accept the `PUT pushrules/...` call but silently never match the condition — upgrade Synapse if no notification arrives on a finalized preview edit.
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Tuwunel">
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,62 +4,68 @@ read_when:
|
||||
- Setting up Mattermost
|
||||
- Debugging Mattermost routing
|
||||
title: "Mattermost"
|
||||
sidebarTitle: "Mattermost"
|
||||
---
|
||||
|
||||
Status: bundled plugin (bot token + WebSocket events). Channels, groups, and DMs are supported.
|
||||
Mattermost is a self-hostable team messaging platform; see the official site at
|
||||
[mattermost.com](https://mattermost.com) for product details and downloads.
|
||||
Status: bundled plugin (bot token + WebSocket events). Channels, groups, and DMs are supported. Mattermost is a self-hostable team messaging platform; see the official site at [mattermost.com](https://mattermost.com) for product details and downloads.
|
||||
|
||||
## Bundled plugin
|
||||
|
||||
Mattermost ships as a bundled plugin in current OpenClaw releases, so normal
|
||||
packaged builds do not need a separate install.
|
||||
<Note>
|
||||
Mattermost ships as a bundled plugin in current OpenClaw releases, so normal packaged builds do not need a separate install.
|
||||
</Note>
|
||||
|
||||
If you are on an older build or a custom install that excludes Mattermost,
|
||||
install it manually:
|
||||
If you are on an older build or a custom install that excludes Mattermost, install it manually:
|
||||
|
||||
Install via CLI (npm registry):
|
||||
|
||||
```bash
|
||||
openclaw plugins install @openclaw/mattermost
|
||||
```
|
||||
|
||||
Local checkout (when running from a git repo):
|
||||
|
||||
```bash
|
||||
openclaw plugins install ./path/to/local/mattermost-plugin
|
||||
```
|
||||
<Tabs>
|
||||
<Tab title="npm registry">
|
||||
```bash
|
||||
openclaw plugins install @openclaw/mattermost
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Local checkout">
|
||||
```bash
|
||||
openclaw plugins install ./path/to/local/mattermost-plugin
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
Details: [Plugins](/tools/plugin)
|
||||
|
||||
## Quick setup
|
||||
|
||||
1. Ensure the Mattermost plugin is available.
|
||||
- Current packaged OpenClaw releases already bundle it.
|
||||
- Older/custom installs can add it manually with the commands above.
|
||||
2. Create a Mattermost bot account and copy the **bot token**.
|
||||
3. Copy the Mattermost **base URL** (e.g., `https://chat.example.com`).
|
||||
4. Configure OpenClaw and start the gateway.
|
||||
<Steps>
|
||||
<Step title="Ensure plugin is available">
|
||||
Current packaged OpenClaw releases already bundle it. Older/custom installs can add it manually with the commands above.
|
||||
</Step>
|
||||
<Step title="Create a Mattermost bot">
|
||||
Create a Mattermost bot account and copy the **bot token**.
|
||||
</Step>
|
||||
<Step title="Copy the base URL">
|
||||
Copy the Mattermost **base URL** (e.g., `https://chat.example.com`).
|
||||
</Step>
|
||||
<Step title="Configure OpenClaw and start the gateway">
|
||||
Minimal config:
|
||||
|
||||
Minimal config:
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
mattermost: {
|
||||
enabled: true,
|
||||
botToken: "mm-token",
|
||||
baseUrl: "https://chat.example.com",
|
||||
dmPolicy: "pairing",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
mattermost: {
|
||||
enabled: true,
|
||||
botToken: "mm-token",
|
||||
baseUrl: "https://chat.example.com",
|
||||
dmPolicy: "pairing",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## Native slash commands
|
||||
|
||||
Native slash commands are opt-in. When enabled, OpenClaw registers `oc_*` slash commands via
|
||||
the Mattermost API and receives callback POSTs on the gateway HTTP server.
|
||||
Native slash commands are opt-in. When enabled, OpenClaw registers `oc_*` slash commands via the Mattermost API and receives callback POSTs on the gateway HTTP server.
|
||||
|
||||
```json5
|
||||
{
|
||||
@@ -77,27 +83,33 @@ the Mattermost API and receives callback POSTs on the gateway HTTP server.
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
<AccordionGroup>
|
||||
<Accordion title="Behavior notes">
|
||||
- `native: "auto"` defaults to disabled for Mattermost. Set `native: true` to enable.
|
||||
- If `callbackUrl` is omitted, OpenClaw derives one from gateway host/port + `callbackPath`.
|
||||
- For multi-account setups, `commands` can be set at the top level or under `channels.mattermost.accounts.<id>.commands` (account values override top-level fields).
|
||||
- Command callbacks are validated with the per-command tokens returned by Mattermost when OpenClaw registers `oc_*` commands.
|
||||
- Slash callbacks fail closed when registration failed, startup was partial, or the callback token does not match one of the registered commands.
|
||||
</Accordion>
|
||||
<Accordion title="Reachability requirement">
|
||||
The callback endpoint must be reachable from the Mattermost server.
|
||||
|
||||
- Do not set `callbackUrl` to `localhost` unless Mattermost runs on the same host/network namespace as OpenClaw.
|
||||
- Do not set `callbackUrl` to your Mattermost base URL unless that URL reverse-proxies `/api/channels/mattermost/command` to OpenClaw.
|
||||
- A quick check is `curl https://<gateway-host>/api/channels/mattermost/command`; a GET should return `405 Method Not Allowed` from OpenClaw, not `404`.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Mattermost egress allowlist">
|
||||
If your callback targets private/tailnet/internal addresses, set Mattermost `ServiceSettings.AllowedUntrustedInternalConnections` to include the callback host/domain.
|
||||
|
||||
Use host/domain entries, not full URLs.
|
||||
|
||||
- `native: "auto"` defaults to disabled for Mattermost. Set `native: true` to enable.
|
||||
- If `callbackUrl` is omitted, OpenClaw derives one from gateway host/port + `callbackPath`.
|
||||
- For multi-account setups, `commands` can be set at the top level or under
|
||||
`channels.mattermost.accounts.<id>.commands` (account values override top-level fields).
|
||||
- Command callbacks are validated with the per-command tokens returned by
|
||||
Mattermost when OpenClaw registers `oc_*` commands.
|
||||
- Slash callbacks fail closed when registration failed, startup was partial, or
|
||||
the callback token does not match one of the registered commands.
|
||||
- Reachability requirement: the callback endpoint must be reachable from the Mattermost server.
|
||||
- Do not set `callbackUrl` to `localhost` unless Mattermost runs on the same host/network namespace as OpenClaw.
|
||||
- Do not set `callbackUrl` to your Mattermost base URL unless that URL reverse-proxies `/api/channels/mattermost/command` to OpenClaw.
|
||||
- A quick check is `curl https://<gateway-host>/api/channels/mattermost/command`; a GET should return `405 Method Not Allowed` from OpenClaw, not `404`.
|
||||
- Mattermost egress allowlist requirement:
|
||||
- If your callback targets private/tailnet/internal addresses, set Mattermost
|
||||
`ServiceSettings.AllowedUntrustedInternalConnections` to include the callback host/domain.
|
||||
- Use host/domain entries, not full URLs.
|
||||
- Good: `gateway.tailnet-name.ts.net`
|
||||
- Bad: `https://gateway.tailnet-name.ts.net`
|
||||
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Environment variables (default account)
|
||||
|
||||
Set these on the gateway host if you prefer env vars:
|
||||
@@ -105,17 +117,27 @@ Set these on the gateway host if you prefer env vars:
|
||||
- `MATTERMOST_BOT_TOKEN=...`
|
||||
- `MATTERMOST_URL=https://chat.example.com`
|
||||
|
||||
<Note>
|
||||
Env vars apply only to the **default** account (`default`). Other accounts must use config values.
|
||||
|
||||
`MATTERMOST_URL` cannot be set from a workspace `.env`; see [Workspace `.env` files](/gateway/security).
|
||||
</Note>
|
||||
|
||||
## Chat modes
|
||||
|
||||
Mattermost responds to DMs automatically. Channel behavior is controlled by `chatmode`:
|
||||
|
||||
- `oncall` (default): respond only when @mentioned in channels.
|
||||
- `onmessage`: respond to every channel message.
|
||||
- `onchar`: respond when a message starts with a trigger prefix.
|
||||
<Tabs>
|
||||
<Tab title="oncall (default)">
|
||||
Respond only when @mentioned in channels.
|
||||
</Tab>
|
||||
<Tab title="onmessage">
|
||||
Respond to every channel message.
|
||||
</Tab>
|
||||
<Tab title="onchar">
|
||||
Respond when a message starts with a trigger prefix.
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
Config example:
|
||||
|
||||
@@ -137,12 +159,10 @@ Notes:
|
||||
|
||||
## Threading and sessions
|
||||
|
||||
Use `channels.mattermost.replyToMode` to control whether channel and group replies stay in the
|
||||
main channel or start a thread under the triggering post.
|
||||
Use `channels.mattermost.replyToMode` to control whether channel and group replies stay in the main channel or start a thread under the triggering post.
|
||||
|
||||
- `off` (default): only reply in a thread when the inbound post is already in one.
|
||||
- `first`: for top-level channel/group posts, start a thread under that post and route the
|
||||
conversation to a thread-scoped session.
|
||||
- `first`: for top-level channel/group posts, start a thread under that post and route the conversation to a thread-scoped session.
|
||||
- `all`: same behavior as `first` for Mattermost today.
|
||||
- Direct messages ignore this setting and stay non-threaded.
|
||||
|
||||
@@ -161,8 +181,7 @@ Config example:
|
||||
Notes:
|
||||
|
||||
- Thread-scoped sessions use the triggering post id as the thread root.
|
||||
- `first` and `all` are currently equivalent because once Mattermost has a thread root,
|
||||
follow-up chunks and media continue in that same thread.
|
||||
- `first` and `all` are currently equivalent because once Mattermost has a thread root, follow-up chunks and media continue in that same thread.
|
||||
|
||||
## Access control (DMs)
|
||||
|
||||
@@ -176,8 +195,7 @@ Notes:
|
||||
|
||||
- Default: `channels.mattermost.groupPolicy = "allowlist"` (mention-gated).
|
||||
- Allowlist senders with `channels.mattermost.groupAllowFrom` (user IDs recommended).
|
||||
- Per-channel mention overrides live under `channels.mattermost.groups.<channelId>.requireMention`
|
||||
or `channels.mattermost.groups["*"].requireMention` for a default.
|
||||
- Per-channel mention overrides live under `channels.mattermost.groups.<channelId>.requireMention` or `channels.mattermost.groups["*"].requireMention` for a default.
|
||||
- `@username` matching is mutable and only enabled when `channels.mattermost.dangerouslyAllowNameMatching: true`.
|
||||
- Open channels: `channels.mattermost.groupPolicy="open"` (mention-gated).
|
||||
- Runtime note: if `channels.mattermost` is completely missing, runtime falls back to `groupPolicy="allowlist"` for group checks (even if `channels.defaults.groupPolicy` is set).
|
||||
@@ -206,6 +224,7 @@ Use these target formats with `openclaw message send` or cron/webhooks:
|
||||
- `user:<id>` for a DM
|
||||
- `@username` for a DM (resolved via the Mattermost API)
|
||||
|
||||
<Warning>
|
||||
Bare opaque IDs (like `64ifufp...`) are **ambiguous** in Mattermost (user ID vs channel ID).
|
||||
|
||||
OpenClaw resolves them **user-first**:
|
||||
@@ -214,14 +233,13 @@ OpenClaw resolves them **user-first**:
|
||||
- Otherwise the ID is treated as a **channel ID**.
|
||||
|
||||
If you need deterministic behavior, always use the explicit prefixes (`user:<id>` / `channel:<id>`).
|
||||
</Warning>
|
||||
|
||||
## DM channel retry
|
||||
|
||||
When OpenClaw sends to a Mattermost DM target and needs to resolve the direct channel first, it
|
||||
retries transient direct-channel creation failures by default.
|
||||
When OpenClaw sends to a Mattermost DM target and needs to resolve the direct channel first, it retries transient direct-channel creation failures by default.
|
||||
|
||||
Use `channels.mattermost.dmChannelRetry` to tune that behavior globally for the Mattermost plugin,
|
||||
or `channels.mattermost.accounts.<id>.dmChannelRetry` for one account.
|
||||
Use `channels.mattermost.dmChannelRetry` to tune that behavior globally for the Mattermost plugin, or `channels.mattermost.accounts.<id>.dmChannelRetry` for one account.
|
||||
|
||||
```json5
|
||||
{
|
||||
@@ -260,15 +278,19 @@ Enable via `channels.mattermost.streaming`:
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- `partial` is the usual choice: one preview post that is edited as the reply grows, then finalized with the complete answer.
|
||||
- `block` uses append-style draft chunks inside the preview post.
|
||||
- `progress` shows a status preview while generating and only posts the final answer at completion.
|
||||
- `off` disables preview streaming.
|
||||
- If the stream cannot be finalized in place (for example the post was deleted mid-stream), OpenClaw falls back to sending a fresh final post so the reply is never lost.
|
||||
- Reasoning-only payloads are suppressed from channel posts, including text that arrives as a `> Reasoning:` blockquote. Set `/reasoning on` to see thinking in other surfaces; the Mattermost final post keeps the answer only.
|
||||
- See [Streaming](/concepts/streaming#preview-streaming-modes) for the channel-mapping matrix.
|
||||
<AccordionGroup>
|
||||
<Accordion title="Streaming modes">
|
||||
- `partial` is the usual choice: one preview post that is edited as the reply grows, then finalized with the complete answer.
|
||||
- `block` uses append-style draft chunks inside the preview post.
|
||||
- `progress` shows a status preview while generating and only posts the final answer at completion.
|
||||
- `off` disables preview streaming.
|
||||
</Accordion>
|
||||
<Accordion title="Streaming behavior notes">
|
||||
- If the stream cannot be finalized in place (for example the post was deleted mid-stream), OpenClaw falls back to sending a fresh final post so the reply is never lost.
|
||||
- Reasoning-only payloads are suppressed from channel posts, including text that arrives as a `> Reasoning:` blockquote. Set `/reasoning on` to see thinking in other surfaces; the Mattermost final post keeps the answer only.
|
||||
- See [Streaming](/concepts/streaming#preview-streaming-modes) for the channel-mapping matrix.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Reactions (message tool)
|
||||
|
||||
@@ -292,8 +314,7 @@ Config:
|
||||
|
||||
## Interactive buttons (message tool)
|
||||
|
||||
Send messages with clickable buttons. When a user clicks a button, the agent receives the
|
||||
selection and can respond.
|
||||
Send messages with clickable buttons. When a user clicks a button, the agent receives the selection and can respond.
|
||||
|
||||
Enable buttons by adding `inlineButtons` to the channel capabilities:
|
||||
|
||||
@@ -315,44 +336,46 @@ message action=send channel=mattermost target=channel:<channelId> buttons=[[{"te
|
||||
|
||||
Button fields:
|
||||
|
||||
- `text` (required): display label.
|
||||
- `callback_data` (required): value sent back on click (used as the action ID).
|
||||
- `style` (optional): `"default"`, `"primary"`, or `"danger"`.
|
||||
<ParamField path="text" type="string" required>
|
||||
Display label.
|
||||
</ParamField>
|
||||
<ParamField path="callback_data" type="string" required>
|
||||
Value sent back on click (used as the action ID).
|
||||
</ParamField>
|
||||
<ParamField path="style" type='"default" | "primary" | "danger"'>
|
||||
Button style.
|
||||
</ParamField>
|
||||
|
||||
When a user clicks a button:
|
||||
|
||||
1. All buttons are replaced with a confirmation line (e.g., "✓ **Yes** selected by @user").
|
||||
2. The agent receives the selection as an inbound message and responds.
|
||||
<Steps>
|
||||
<Step title="Buttons replaced with confirmation">
|
||||
All buttons are replaced with a confirmation line (e.g., "✓ **Yes** selected by @user").
|
||||
</Step>
|
||||
<Step title="Agent receives the selection">
|
||||
The agent receives the selection as an inbound message and responds.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
Notes:
|
||||
|
||||
- Button callbacks use HMAC-SHA256 verification (automatic, no config needed).
|
||||
- Mattermost strips callback data from its API responses (security feature), so all buttons
|
||||
are removed on click — partial removal is not possible.
|
||||
- Action IDs containing hyphens or underscores are sanitized automatically
|
||||
(Mattermost routing limitation).
|
||||
|
||||
Config:
|
||||
|
||||
- `channels.mattermost.capabilities`: array of capability strings. Add `"inlineButtons"` to
|
||||
enable the buttons tool description in the agent system prompt.
|
||||
- `channels.mattermost.interactions.callbackBaseUrl`: optional external base URL for button
|
||||
callbacks (for example `https://gateway.example.com`). Use this when Mattermost cannot
|
||||
reach the gateway at its bind host directly.
|
||||
- In multi-account setups, you can also set the same field under
|
||||
`channels.mattermost.accounts.<id>.interactions.callbackBaseUrl`.
|
||||
- If `interactions.callbackBaseUrl` is omitted, OpenClaw derives the callback URL from
|
||||
`gateway.customBindHost` + `gateway.port`, then falls back to `http://localhost:<port>`.
|
||||
- Reachability rule: the button callback URL must be reachable from the Mattermost server.
|
||||
`localhost` only works when Mattermost and OpenClaw run on the same host/network namespace.
|
||||
- If your callback target is private/tailnet/internal, add its host/domain to Mattermost
|
||||
`ServiceSettings.AllowedUntrustedInternalConnections`.
|
||||
<AccordionGroup>
|
||||
<Accordion title="Implementation notes">
|
||||
- Button callbacks use HMAC-SHA256 verification (automatic, no config needed).
|
||||
- Mattermost strips callback data from its API responses (security feature), so all buttons are removed on click — partial removal is not possible.
|
||||
- Action IDs containing hyphens or underscores are sanitized automatically (Mattermost routing limitation).
|
||||
</Accordion>
|
||||
<Accordion title="Config and reachability">
|
||||
- `channels.mattermost.capabilities`: array of capability strings. Add `"inlineButtons"` to enable the buttons tool description in the agent system prompt.
|
||||
- `channels.mattermost.interactions.callbackBaseUrl`: optional external base URL for button callbacks (for example `https://gateway.example.com`). Use this when Mattermost cannot reach the gateway at its bind host directly.
|
||||
- In multi-account setups, you can also set the same field under `channels.mattermost.accounts.<id>.interactions.callbackBaseUrl`.
|
||||
- If `interactions.callbackBaseUrl` is omitted, OpenClaw derives the callback URL from `gateway.customBindHost` + `gateway.port`, then falls back to `http://localhost:<port>`.
|
||||
- Reachability rule: the button callback URL must be reachable from the Mattermost server. `localhost` only works when Mattermost and OpenClaw run on the same host/network namespace.
|
||||
- If your callback target is private/tailnet/internal, add its host/domain to Mattermost `ServiceSettings.AllowedUntrustedInternalConnections`.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
### Direct API integration (external scripts)
|
||||
|
||||
External scripts and webhooks can post buttons directly via the Mattermost REST API
|
||||
instead of going through the agent's `message` tool. Use `buildButtonAttachments()` from
|
||||
the plugin when possible; if posting raw JSON, follow these rules:
|
||||
External scripts and webhooks can post buttons directly via the Mattermost REST API instead of going through the agent's `message` tool. Use `buildButtonAttachments()` from the plugin when possible; if posting raw JSON, follow these rules:
|
||||
|
||||
**Payload structure:**
|
||||
|
||||
@@ -386,29 +409,38 @@ the plugin when possible; if posting raw JSON, follow these rules:
|
||||
}
|
||||
```
|
||||
|
||||
**Critical rules:**
|
||||
<Warning>
|
||||
**Critical rules**
|
||||
|
||||
1. Attachments go in `props.attachments`, not top-level `attachments` (silently ignored).
|
||||
2. Every action needs `type: "button"` — without it, clicks are swallowed silently.
|
||||
3. Every action needs an `id` field — Mattermost ignores actions without IDs.
|
||||
4. Action `id` must be **alphanumeric only** (`[a-zA-Z0-9]`). Hyphens and underscores break
|
||||
Mattermost's server-side action routing (returns 404). Strip them before use.
|
||||
5. `context.action_id` must match the button's `id` so the confirmation message shows the
|
||||
button name (e.g., "Approve") instead of a raw ID.
|
||||
4. Action `id` must be **alphanumeric only** (`[a-zA-Z0-9]`). Hyphens and underscores break Mattermost's server-side action routing (returns 404). Strip them before use.
|
||||
5. `context.action_id` must match the button's `id` so the confirmation message shows the button name (e.g., "Approve") instead of a raw ID.
|
||||
6. `context.action_id` is required — the interaction handler returns 400 without it.
|
||||
</Warning>
|
||||
|
||||
**HMAC token generation:**
|
||||
**HMAC token generation**
|
||||
|
||||
The gateway verifies button clicks with HMAC-SHA256. External scripts must generate tokens
|
||||
that match the gateway's verification logic:
|
||||
The gateway verifies button clicks with HMAC-SHA256. External scripts must generate tokens that match the gateway's verification logic:
|
||||
|
||||
1. Derive the secret from the bot token:
|
||||
`HMAC-SHA256(key="openclaw-mattermost-interactions", data=botToken)`
|
||||
2. Build the context object with all fields **except** `_token`.
|
||||
3. Serialize with **sorted keys** and **no spaces** (the gateway uses `JSON.stringify`
|
||||
with sorted keys, which produces compact output).
|
||||
4. Sign: `HMAC-SHA256(key=secret, data=serializedContext)`
|
||||
5. Add the resulting hex digest as `_token` in the context.
|
||||
<Steps>
|
||||
<Step title="Derive the secret from the bot token">
|
||||
`HMAC-SHA256(key="openclaw-mattermost-interactions", data=botToken)`
|
||||
</Step>
|
||||
<Step title="Build the context object">
|
||||
Build the context object with all fields **except** `_token`.
|
||||
</Step>
|
||||
<Step title="Serialize with sorted keys">
|
||||
Serialize with **sorted keys** and **no spaces** (the gateway uses `JSON.stringify` with sorted keys, which produces compact output).
|
||||
</Step>
|
||||
<Step title="Sign the payload">
|
||||
`HMAC-SHA256(key=secret, data=serializedContext)`
|
||||
</Step>
|
||||
<Step title="Add the token">
|
||||
Add the resulting hex digest as `_token` in the context.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
Python example:
|
||||
|
||||
@@ -427,22 +459,18 @@ token = hmac.new(secret.encode(), payload.encode(), hashlib.sha256).hexdigest()
|
||||
context = {**ctx, "_token": token}
|
||||
```
|
||||
|
||||
Common HMAC pitfalls:
|
||||
|
||||
- Python's `json.dumps` adds spaces by default (`{"key": "val"}`). Use
|
||||
`separators=(",", ":")` to match JavaScript's compact output (`{"key":"val"}`).
|
||||
- Always sign **all** context fields (minus `_token`). The gateway strips `_token` then
|
||||
signs everything remaining. Signing a subset causes silent verification failure.
|
||||
- Use `sort_keys=True` — the gateway sorts keys before signing, and Mattermost may
|
||||
reorder context fields when storing the payload.
|
||||
- Derive the secret from the bot token (deterministic), not random bytes. The secret
|
||||
must be the same across the process that creates buttons and the gateway that verifies.
|
||||
<AccordionGroup>
|
||||
<Accordion title="Common HMAC pitfalls">
|
||||
- Python's `json.dumps` adds spaces by default (`{"key": "val"}`). Use `separators=(",", ":")` to match JavaScript's compact output (`{"key":"val"}`).
|
||||
- Always sign **all** context fields (minus `_token`). The gateway strips `_token` then signs everything remaining. Signing a subset causes silent verification failure.
|
||||
- Use `sort_keys=True` — the gateway sorts keys before signing, and Mattermost may reorder context fields when storing the payload.
|
||||
- Derive the secret from the bot token (deterministic), not random bytes. The secret must be the same across the process that creates buttons and the gateway that verifies.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Directory adapter
|
||||
|
||||
The Mattermost plugin includes a directory adapter that resolves channel and user names
|
||||
via the Mattermost API. This enables `#channel-name` and `@username` targets in
|
||||
`openclaw message send` and cron/webhook deliveries.
|
||||
The Mattermost plugin includes a directory adapter that resolves channel and user names via the Mattermost API. This enables `#channel-name` and `@username` targets in `openclaw message send` and cron/webhook deliveries.
|
||||
|
||||
No configuration is needed — the adapter uses the bot token from the account config.
|
||||
|
||||
@@ -465,34 +493,38 @@ Mattermost supports multiple accounts under `channels.mattermost.accounts`:
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- No replies in channels: ensure the bot is in the channel and mention it (oncall), use a trigger prefix (onchar), or set `chatmode: "onmessage"`.
|
||||
- Auth errors: check the bot token, base URL, and whether the account is enabled.
|
||||
- Multi-account issues: env vars only apply to the `default` account.
|
||||
- Native slash commands return `Unauthorized: invalid command token.`: OpenClaw
|
||||
did not accept the callback token. Typical causes:
|
||||
- slash command registration failed or only partially completed at startup
|
||||
- the callback is hitting the wrong gateway/account
|
||||
- Mattermost still has old commands pointing at a previous callback target
|
||||
- the gateway restarted without reactivating slash commands
|
||||
- If native slash commands stop working, check logs for
|
||||
`mattermost: failed to register slash commands` or
|
||||
`mattermost: native slash commands enabled but no commands could be registered`.
|
||||
- If `callbackUrl` is omitted and logs warn that the callback resolved to
|
||||
`http://127.0.0.1:18789/...`, that URL is probably only reachable when
|
||||
Mattermost runs on the same host/network namespace as OpenClaw. Set an
|
||||
explicit externally reachable `commands.callbackUrl` instead.
|
||||
- Buttons appear as white boxes: the agent may be sending malformed button data. Check that each button has both `text` and `callback_data` fields.
|
||||
- Buttons render but clicks do nothing: verify `AllowedUntrustedInternalConnections` in Mattermost server config includes `127.0.0.1 localhost`, and that `EnablePostActionIntegration` is `true` in ServiceSettings.
|
||||
- Buttons return 404 on click: the button `id` likely contains hyphens or underscores. Mattermost's action router breaks on non-alphanumeric IDs. Use `[a-zA-Z0-9]` only.
|
||||
- Gateway logs `invalid _token`: HMAC mismatch. Check that you sign all context fields (not a subset), use sorted keys, and use compact JSON (no spaces). See the HMAC section above.
|
||||
- Gateway logs `missing _token in context`: the `_token` field is not in the button's context. Ensure it is included when building the integration payload.
|
||||
- Confirmation shows raw ID instead of button name: `context.action_id` does not match the button's `id`. Set both to the same sanitized value.
|
||||
- Agent doesn't know about buttons: add `capabilities: ["inlineButtons"]` to the Mattermost channel config.
|
||||
<AccordionGroup>
|
||||
<Accordion title="No replies in channels">
|
||||
Ensure the bot is in the channel and mention it (oncall), use a trigger prefix (onchar), or set `chatmode: "onmessage"`.
|
||||
</Accordion>
|
||||
<Accordion title="Auth or multi-account errors">
|
||||
- Check the bot token, base URL, and whether the account is enabled.
|
||||
- Multi-account issues: env vars only apply to the `default` account.
|
||||
</Accordion>
|
||||
<Accordion title="Native slash commands fail">
|
||||
- `Unauthorized: invalid command token.`: OpenClaw did not accept the callback token. Typical causes:
|
||||
- slash command registration failed or only partially completed at startup
|
||||
- the callback is hitting the wrong gateway/account
|
||||
- Mattermost still has old commands pointing at a previous callback target
|
||||
- the gateway restarted without reactivating slash commands
|
||||
- If native slash commands stop working, check logs for `mattermost: failed to register slash commands` or `mattermost: native slash commands enabled but no commands could be registered`.
|
||||
- If `callbackUrl` is omitted and logs warn that the callback resolved to `http://127.0.0.1:18789/...`, that URL is probably only reachable when Mattermost runs on the same host/network namespace as OpenClaw. Set an explicit externally reachable `commands.callbackUrl` instead.
|
||||
</Accordion>
|
||||
<Accordion title="Buttons issues">
|
||||
- Buttons appear as white boxes: the agent may be sending malformed button data. Check that each button has both `text` and `callback_data` fields.
|
||||
- Buttons render but clicks do nothing: verify `AllowedUntrustedInternalConnections` in Mattermost server config includes `127.0.0.1 localhost`, and that `EnablePostActionIntegration` is `true` in ServiceSettings.
|
||||
- Buttons return 404 on click: the button `id` likely contains hyphens or underscores. Mattermost's action router breaks on non-alphanumeric IDs. Use `[a-zA-Z0-9]` only.
|
||||
- Gateway logs `invalid _token`: HMAC mismatch. Check that you sign all context fields (not a subset), use sorted keys, and use compact JSON (no spaces). See the HMAC section above.
|
||||
- Gateway logs `missing _token in context`: the `_token` field is not in the button's context. Ensure it is included when building the integration payload.
|
||||
- Confirmation shows raw ID instead of button name: `context.action_id` does not match the button's `id`. Set both to the same sanitized value.
|
||||
- Agent doesn't know about buttons: add `capabilities: ["inlineButtons"]` to the Mattermost channel config.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Related
|
||||
|
||||
- [Channels Overview](/channels) — all supported channels
|
||||
- [Pairing](/channels/pairing) — DM authentication and pairing flow
|
||||
- [Groups](/channels/groups) — group chat behavior and mention gating
|
||||
- [Channel Routing](/channels/channel-routing) — session routing for messages
|
||||
- [Channels Overview](/channels) — all supported channels
|
||||
- [Groups](/channels/groups) — group chat behavior and mention gating
|
||||
- [Pairing](/channels/pairing) — DM authentication and pairing flow
|
||||
- [Security](/gateway/security) — access model and hardening
|
||||
|
||||
@@ -39,7 +39,9 @@ teams login
|
||||
teams status # verify you're logged in and see your tenant info
|
||||
```
|
||||
|
||||
> **Note:** The Teams CLI is currently in preview. Commands and flags may change between releases.
|
||||
<Note>
|
||||
The Teams CLI is currently in preview. Commands and flags may change between releases.
|
||||
</Note>
|
||||
|
||||
**2. Start a tunnel** (Teams can't reach localhost)
|
||||
|
||||
@@ -55,7 +57,9 @@ devtunnel host my-openclaw-bot
|
||||
# Your endpoint: https://<tunnel-id>.devtunnels.ms/api/messages
|
||||
```
|
||||
|
||||
> **Note:** `--allow-anonymous` is required because Teams can't authenticate with devtunnels. Each incoming bot request is still validated by the Teams SDK automatically.
|
||||
<Note>
|
||||
`--allow-anonymous` is required because Teams cannot authenticate with devtunnels. Each incoming bot request is still validated by the Teams SDK automatically.
|
||||
</Note>
|
||||
|
||||
Alternatives: `ngrok http 3978` or `tailscale funnel 3978` (but these may change URLs each session).
|
||||
|
||||
@@ -112,7 +116,9 @@ This runs diagnostics across bot registration, AAD app config, manifest validity
|
||||
|
||||
For production deployments, consider using [federated authentication](#federated-authentication-certificate--managed-identity) (certificate or managed identity) instead of client secrets.
|
||||
|
||||
Note: group chats are blocked by default (`channels.msteams.groupPolicy: "allowlist"`). To allow group replies, set `channels.msteams.groupAllowFrom` (or use `groupPolicy: "open"` to allow any member, mention-gated).
|
||||
<Note>
|
||||
Group chats are blocked by default (`channels.msteams.groupPolicy: "allowlist"`). To allow group replies, set `channels.msteams.groupAllowFrom`, or use `groupPolicy: "open"` to allow any member (mention-gated).
|
||||
</Note>
|
||||
|
||||
## Goals
|
||||
|
||||
@@ -217,7 +223,9 @@ If you can't use the Teams CLI, you can set up the bot manually through the Azur
|
||||
| **Type of App** | **Single Tenant** (recommended - see note below) |
|
||||
| **Creation type** | **Create new Microsoft App ID** |
|
||||
|
||||
> **Deprecation notice:** Creation of new multi-tenant bots was deprecated after 2025-07-31. Use **Single Tenant** for new bots.
|
||||
<Warning>
|
||||
Creation of new multi-tenant bots was deprecated after 2025-07-31. Use **Single Tenant** for new bots.
|
||||
</Warning>
|
||||
|
||||
3. Click **Review + create** → **Create** (wait ~1-2 minutes)
|
||||
|
||||
@@ -914,7 +922,9 @@ openclaw message send --channel msteams --target "conversation:19:abc...@thread.
|
||||
}
|
||||
```
|
||||
|
||||
Note: Without the `user:` prefix, names default to group/team resolution. Always use `user:` when targeting people by display name.
|
||||
<Note>
|
||||
Without the `user:` prefix, names default to group or team resolution. Always use `user:` when targeting people by display name.
|
||||
</Note>
|
||||
|
||||
## Proactive messaging
|
||||
|
||||
|
||||
@@ -52,8 +52,9 @@ Account scoping behavior:
|
||||
|
||||
Treat these as sensitive (they gate access to your assistant).
|
||||
|
||||
Important: this store is for DM access. Group authorization is separate.
|
||||
Approving a DM pairing code does not automatically allow that sender to run group commands or control the bot in groups. For group access, configure the channel's explicit group allowlists (for example `groupAllowFrom`, `groups`, or per-group/per-topic overrides depending on the channel).
|
||||
<Note>
|
||||
This store is for DM access. Group authorization is separate. Approving a DM pairing code does not automatically allow that sender to run group commands or control the bot in groups. For group access, configure the channel's explicit group allowlists (for example `groupAllowFrom`, `groups`, or per-group or per-topic overrides depending on the channel).
|
||||
</Note>
|
||||
|
||||
## 2) Node device pairing (iOS/Android/macOS/headless nodes)
|
||||
|
||||
@@ -100,11 +101,9 @@ If the same device retries with different auth details (for example different
|
||||
role/scopes/public key), the previous pending request is superseded and a new
|
||||
`requestId` is created.
|
||||
|
||||
Important: an already paired device does not get broader access silently. If it
|
||||
reconnects asking for more scopes or a broader role, OpenClaw keeps the
|
||||
existing approval as-is and creates a fresh pending upgrade request. Use
|
||||
`openclaw devices list` to compare the currently approved access with the newly
|
||||
requested access before you approve.
|
||||
<Note>
|
||||
An already paired device does not get broader access silently. If it reconnects asking for more scopes or a broader role, OpenClaw keeps the existing approval as-is and creates a fresh pending upgrade request. Use `openclaw devices list` to compare the currently approved access with the newly requested access before you approve.
|
||||
</Note>
|
||||
|
||||
### Optional trusted-CIDR node auto-approve
|
||||
|
||||
|
||||
@@ -122,10 +122,10 @@ openclaw channels add --channel qqbot --account bot2 --token "222222222:secret-o
|
||||
|
||||
STT and TTS support two-level configuration with priority fallback:
|
||||
|
||||
| Setting | Plugin-specific | Framework fallback |
|
||||
| ------- | -------------------- | ----------------------------- |
|
||||
| STT | `channels.qqbot.stt` | `tools.media.audio.models[0]` |
|
||||
| TTS | `channels.qqbot.tts` | `messages.tts` |
|
||||
| Setting | Plugin-specific | Framework fallback |
|
||||
| ------- | -------------------------------------------------------- | ----------------------------- |
|
||||
| STT | `channels.qqbot.stt` | `tools.media.audio.models[0]` |
|
||||
| TTS | `channels.qqbot.tts`, `channels.qqbot.accounts.<id>.tts` | `messages.tts` |
|
||||
|
||||
```json5
|
||||
{
|
||||
@@ -140,12 +140,23 @@ STT and TTS support two-level configuration with priority fallback:
|
||||
model: "your-tts-model",
|
||||
voice: "your-voice",
|
||||
},
|
||||
accounts: {
|
||||
qq-main: {
|
||||
tts: {
|
||||
providers: {
|
||||
openai: { voice: "shimmer" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Set `enabled: false` on either to disable.
|
||||
Account-level TTS overrides use the same shape as `messages.tts` and deep-merge
|
||||
over the channel/global TTS config.
|
||||
|
||||
Inbound QQ voice attachments are exposed to agents as audio media metadata while
|
||||
keeping raw voice files out of generic `MediaPaths`. `[[audio_as_voice]]` plain
|
||||
|
||||
@@ -152,7 +152,9 @@ openclaw channels status --probe
|
||||
- Approve code on the server: `openclaw pairing approve signal <PAIRING_CODE>`.
|
||||
- Save the bot number as a contact on your phone to avoid "Unknown contact".
|
||||
|
||||
Important: registering a phone number account with `signal-cli` can de-authenticate the main Signal app session for that number. Prefer a dedicated bot number, or use QR link mode if you need to keep your existing phone app setup.
|
||||
<Warning>
|
||||
Registering a phone number account with `signal-cli` can de-authenticate the main Signal app session for that number. Prefer a dedicated bot number, or use QR link mode if you need to keep your existing phone app setup.
|
||||
</Warning>
|
||||
|
||||
Upstream references:
|
||||
|
||||
|
||||
@@ -530,7 +530,9 @@ Manual reply tags are supported:
|
||||
- `[[reply_to_current]]`
|
||||
- `[[reply_to:<id>]]`
|
||||
|
||||
Note: `replyToMode="off"` disables **all** reply threading in Slack, including explicit `[[reply_to_*]]` tags. This differs from Telegram, where explicit tags are still honored in `"off"` mode — Slack threads hide messages from the channel while Telegram replies stay visible inline.
|
||||
<Note>
|
||||
`replyToMode="off"` disables **all** reply threading in Slack, including explicit `[[reply_to_*]]` tags. This differs from Telegram, where explicit tags are still honored in `"off"` mode. Slack threads hide messages from the channel while Telegram replies stay visible inline.
|
||||
</Note>
|
||||
|
||||
## Ack reactions
|
||||
|
||||
|
||||
@@ -298,8 +298,8 @@ curl "https://api.telegram.org/bot<bot_token>/getUpdates"
|
||||
|
||||
For text-only replies:
|
||||
|
||||
- DM: OpenClaw keeps the same preview message and performs a final edit in place (no second message)
|
||||
- group/topic: OpenClaw keeps the same preview message and performs a final edit in place (no second message)
|
||||
- short DM/group/topic previews: OpenClaw keeps the same preview message and performs a final edit in place
|
||||
- previews older than about one minute: OpenClaw sends the completed reply as a fresh final message and then cleans up the preview, so Telegram's visible timestamp reflects completion time instead of the preview creation time
|
||||
|
||||
For complex replies (for example media payloads), OpenClaw falls back to normal final delivery and then cleans up the preview message.
|
||||
|
||||
|
||||
@@ -3,50 +3,69 @@ summary: "Twitch chat bot configuration and setup"
|
||||
read_when:
|
||||
- Setting up Twitch chat integration for OpenClaw
|
||||
title: "Twitch"
|
||||
sidebarTitle: "Twitch"
|
||||
---
|
||||
|
||||
Twitch chat support via IRC connection. OpenClaw connects as a Twitch user (bot account) to receive and send messages in channels.
|
||||
|
||||
## Bundled plugin
|
||||
|
||||
Twitch ships as a bundled plugin in current OpenClaw releases, so normal
|
||||
packaged builds do not need a separate install.
|
||||
<Note>
|
||||
Twitch ships as a bundled plugin in current OpenClaw releases, so normal packaged builds do not need a separate install.
|
||||
</Note>
|
||||
|
||||
If you are on an older build or a custom install that excludes Twitch, install
|
||||
it manually:
|
||||
If you are on an older build or a custom install that excludes Twitch, install it manually:
|
||||
|
||||
Install via CLI (npm registry):
|
||||
|
||||
```bash
|
||||
openclaw plugins install @openclaw/twitch
|
||||
```
|
||||
|
||||
Local checkout (when running from a git repo):
|
||||
|
||||
```bash
|
||||
openclaw plugins install ./path/to/local/twitch-plugin
|
||||
```
|
||||
<Tabs>
|
||||
<Tab title="npm registry">
|
||||
```bash
|
||||
openclaw plugins install @openclaw/twitch
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Local checkout">
|
||||
```bash
|
||||
openclaw plugins install ./path/to/local/twitch-plugin
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
Details: [Plugins](/tools/plugin)
|
||||
|
||||
## Quick setup (beginner)
|
||||
|
||||
1. Ensure the Twitch plugin is available.
|
||||
- Current packaged OpenClaw releases already bundle it.
|
||||
- Older/custom installs can add it manually with the commands above.
|
||||
2. Create a dedicated Twitch account for the bot (or use an existing account).
|
||||
3. Generate credentials: [Twitch Token Generator](https://twitchtokengenerator.com/)
|
||||
- Select **Bot Token**
|
||||
- Verify scopes `chat:read` and `chat:write` are selected
|
||||
- Copy the **Client ID** and **Access Token**
|
||||
4. Find your Twitch user ID: [https://www.streamweasels.com/tools/convert-twitch-username-to-user-id/](https://www.streamweasels.com/tools/convert-twitch-username-to-user-id/)
|
||||
5. Configure the token:
|
||||
- Env: `OPENCLAW_TWITCH_ACCESS_TOKEN=...` (default account only)
|
||||
- Or config: `channels.twitch.accessToken`
|
||||
- If both are set, config takes precedence (env fallback is default-account only).
|
||||
6. Start the gateway.
|
||||
<Steps>
|
||||
<Step title="Ensure plugin is available">
|
||||
Current packaged OpenClaw releases already bundle it. Older/custom installs can add it manually with the commands above.
|
||||
</Step>
|
||||
<Step title="Create a Twitch bot account">
|
||||
Create a dedicated Twitch account for the bot (or use an existing account).
|
||||
</Step>
|
||||
<Step title="Generate credentials">
|
||||
Use [Twitch Token Generator](https://twitchtokengenerator.com/):
|
||||
|
||||
**⚠️ Important:** Add access control (`allowFrom` or `allowedRoles`) to prevent unauthorized users from triggering the bot. `requireMention` defaults to `true`.
|
||||
- Select **Bot Token**
|
||||
- Verify scopes `chat:read` and `chat:write` are selected
|
||||
- Copy the **Client ID** and **Access Token**
|
||||
|
||||
</Step>
|
||||
<Step title="Find your Twitch user ID">
|
||||
Use [https://www.streamweasels.com/tools/convert-twitch-username-to-user-id/](https://www.streamweasels.com/tools/convert-twitch-username-to-user-id/) to convert a username to a Twitch user ID.
|
||||
</Step>
|
||||
<Step title="Configure the token">
|
||||
- Env: `OPENCLAW_TWITCH_ACCESS_TOKEN=...` (default account only)
|
||||
- Or config: `channels.twitch.accessToken`
|
||||
|
||||
If both are set, config takes precedence (env fallback is default-account only).
|
||||
|
||||
</Step>
|
||||
<Step title="Start the gateway">
|
||||
Start the gateway with the configured channel.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
<Warning>
|
||||
Add access control (`allowFrom` or `allowedRoles`) to prevent unauthorized users from triggering the bot. `requireMention` defaults to `true`.
|
||||
</Warning>
|
||||
|
||||
Minimal config:
|
||||
|
||||
@@ -82,31 +101,34 @@ Use [Twitch Token Generator](https://twitchtokengenerator.com/):
|
||||
- Verify scopes `chat:read` and `chat:write` are selected
|
||||
- Copy the **Client ID** and **Access Token**
|
||||
|
||||
<Note>
|
||||
No manual app registration needed. Tokens expire after several hours.
|
||||
</Note>
|
||||
|
||||
### Configure the bot
|
||||
|
||||
**Env var (default account only):**
|
||||
|
||||
```bash
|
||||
OPENCLAW_TWITCH_ACCESS_TOKEN=oauth:abc123...
|
||||
```
|
||||
|
||||
**Or config:**
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
twitch: {
|
||||
enabled: true,
|
||||
username: "openclaw",
|
||||
accessToken: "oauth:abc123...",
|
||||
clientId: "xyz789...",
|
||||
channel: "vevisk",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
<Tabs>
|
||||
<Tab title="Env var (default account only)">
|
||||
```bash
|
||||
OPENCLAW_TWITCH_ACCESS_TOKEN=oauth:abc123...
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Config">
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
twitch: {
|
||||
enabled: true,
|
||||
username: "openclaw",
|
||||
accessToken: "oauth:abc123...",
|
||||
clientId: "xyz789...",
|
||||
channel: "vevisk",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
If both env and config are set, config takes precedence.
|
||||
|
||||
@@ -126,9 +148,11 @@ Prefer `allowFrom` for a hard allowlist. Use `allowedRoles` instead if you want
|
||||
|
||||
**Available roles:** `"moderator"`, `"owner"`, `"vip"`, `"subscriber"`, `"all"`.
|
||||
|
||||
<Note>
|
||||
**Why user IDs?** Usernames can change, allowing impersonation. User IDs are permanent.
|
||||
|
||||
Find your Twitch user ID: [https://www.streamweasels.com/tools/convert-twitch-username-to-user-id/](https://www.streamweasels.com/tools/convert-twitch-username-to-user-id/) (Convert your Twitch username to ID)
|
||||
</Note>
|
||||
|
||||
## Token refresh (optional)
|
||||
|
||||
@@ -151,7 +175,7 @@ The bot automatically refreshes tokens before expiration and logs refresh events
|
||||
|
||||
## Multi-account support
|
||||
|
||||
Use `channels.twitch.accounts` with per-account tokens. See [`gateway/configuration`](/gateway/configuration) for the shared pattern.
|
||||
Use `channels.twitch.accounts` with per-account tokens. See [Configuration](/gateway/configuration) for the shared pattern.
|
||||
|
||||
Example (one bot account in two channels):
|
||||
|
||||
@@ -178,78 +202,65 @@ Example (one bot account in two channels):
|
||||
}
|
||||
```
|
||||
|
||||
**Note:** Each account needs its own token (one token per channel).
|
||||
<Note>
|
||||
Each account needs its own token (one token per channel).
|
||||
</Note>
|
||||
|
||||
## Access control
|
||||
|
||||
### Role-based restrictions
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
twitch: {
|
||||
accounts: {
|
||||
default: {
|
||||
allowedRoles: ["moderator", "vip"],
|
||||
<Tabs>
|
||||
<Tab title="User ID allowlist (most secure)">
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
twitch: {
|
||||
accounts: {
|
||||
default: {
|
||||
allowFrom: ["123456789", "987654321"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Allowlist by User ID (most secure)
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
twitch: {
|
||||
accounts: {
|
||||
default: {
|
||||
allowFrom: ["123456789", "987654321"],
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Role-based">
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
twitch: {
|
||||
accounts: {
|
||||
default: {
|
||||
allowedRoles: ["moderator", "vip"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
}
|
||||
```
|
||||
|
||||
### Role-based access (alternative)
|
||||
`allowFrom` is a hard allowlist. When set, only those user IDs are allowed. If you want role-based access, leave `allowFrom` unset and configure `allowedRoles` instead.
|
||||
|
||||
`allowFrom` is a hard allowlist. When set, only those user IDs are allowed.
|
||||
If you want role-based access, leave `allowFrom` unset and configure `allowedRoles` instead:
|
||||
</Tab>
|
||||
<Tab title="Disable @mention requirement">
|
||||
By default, `requireMention` is `true`. To disable and respond to all messages:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
twitch: {
|
||||
accounts: {
|
||||
default: {
|
||||
allowedRoles: ["moderator"],
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
twitch: {
|
||||
accounts: {
|
||||
default: {
|
||||
requireMention: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
}
|
||||
```
|
||||
|
||||
### Disable @mention requirement
|
||||
|
||||
By default, `requireMention` is `true`. To disable and respond to all messages:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
twitch: {
|
||||
accounts: {
|
||||
default: {
|
||||
requireMention: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
@@ -260,53 +271,77 @@ openclaw doctor
|
||||
openclaw channels status --probe
|
||||
```
|
||||
|
||||
### Bot does not respond to messages
|
||||
<AccordionGroup>
|
||||
<Accordion title="Bot does not respond to messages">
|
||||
- **Check access control:** Ensure your user ID is in `allowFrom`, or temporarily remove `allowFrom` and set `allowedRoles: ["all"]` to test.
|
||||
- **Check the bot is in the channel:** The bot must join the channel specified in `channel`.
|
||||
</Accordion>
|
||||
<Accordion title="Token issues">
|
||||
"Failed to connect" or authentication errors:
|
||||
|
||||
**Check access control:** Ensure your user ID is in `allowFrom`, or temporarily remove
|
||||
`allowFrom` and set `allowedRoles: ["all"]` to test.
|
||||
- Verify `accessToken` is the OAuth access token value (typically starts with `oauth:` prefix)
|
||||
- Check token has `chat:read` and `chat:write` scopes
|
||||
- If using token refresh, verify `clientSecret` and `refreshToken` are set
|
||||
|
||||
**Check the bot is in the channel:** The bot must join the channel specified in `channel`.
|
||||
</Accordion>
|
||||
<Accordion title="Token refresh not working">
|
||||
Check logs for refresh events:
|
||||
|
||||
### Token issues
|
||||
```
|
||||
Using env token source for mybot
|
||||
Access token refreshed for user 123456 (expires in 14400s)
|
||||
```
|
||||
|
||||
**"Failed to connect" or authentication errors:**
|
||||
If you see "token refresh disabled (no refresh token)":
|
||||
|
||||
- Verify `accessToken` is the OAuth access token value (typically starts with `oauth:` prefix)
|
||||
- Check token has `chat:read` and `chat:write` scopes
|
||||
- If using token refresh, verify `clientSecret` and `refreshToken` are set
|
||||
- Ensure `clientSecret` is provided
|
||||
- Ensure `refreshToken` is provided
|
||||
|
||||
### Token refresh not working
|
||||
|
||||
**Check logs for refresh events:**
|
||||
|
||||
```
|
||||
Using env token source for mybot
|
||||
Access token refreshed for user 123456 (expires in 14400s)
|
||||
```
|
||||
|
||||
If you see "token refresh disabled (no refresh token)":
|
||||
|
||||
- Ensure `clientSecret` is provided
|
||||
- Ensure `refreshToken` is provided
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Config
|
||||
|
||||
**Account config:**
|
||||
### Account config
|
||||
|
||||
- `username` - Bot username
|
||||
- `accessToken` - OAuth access token with `chat:read` and `chat:write`
|
||||
- `clientId` - Twitch Client ID (from Token Generator or your app)
|
||||
- `channel` - Channel to join (required)
|
||||
- `enabled` - Enable this account (default: `true`)
|
||||
- `clientSecret` - Optional: For automatic token refresh
|
||||
- `refreshToken` - Optional: For automatic token refresh
|
||||
- `expiresIn` - Token expiry in seconds
|
||||
- `obtainmentTimestamp` - Token obtained timestamp
|
||||
- `allowFrom` - User ID allowlist
|
||||
- `allowedRoles` - Role-based access control (`"moderator" | "owner" | "vip" | "subscriber" | "all"`)
|
||||
- `requireMention` - Require @mention (default: `true`)
|
||||
<ParamField path="username" type="string">
|
||||
Bot username.
|
||||
</ParamField>
|
||||
<ParamField path="accessToken" type="string">
|
||||
OAuth access token with `chat:read` and `chat:write`.
|
||||
</ParamField>
|
||||
<ParamField path="clientId" type="string">
|
||||
Twitch Client ID (from Token Generator or your app).
|
||||
</ParamField>
|
||||
<ParamField path="channel" type="string" required>
|
||||
Channel to join.
|
||||
</ParamField>
|
||||
<ParamField path="enabled" type="boolean" default="true">
|
||||
Enable this account.
|
||||
</ParamField>
|
||||
<ParamField path="clientSecret" type="string">
|
||||
Optional: for automatic token refresh.
|
||||
</ParamField>
|
||||
<ParamField path="refreshToken" type="string">
|
||||
Optional: for automatic token refresh.
|
||||
</ParamField>
|
||||
<ParamField path="expiresIn" type="number">
|
||||
Token expiry in seconds.
|
||||
</ParamField>
|
||||
<ParamField path="obtainmentTimestamp" type="number">
|
||||
Token obtained timestamp.
|
||||
</ParamField>
|
||||
<ParamField path="allowFrom" type="string[]">
|
||||
User ID allowlist.
|
||||
</ParamField>
|
||||
<ParamField path="allowedRoles" type='Array<"moderator" | "owner" | "vip" | "subscriber" | "all">'>
|
||||
Role-based access control.
|
||||
</ParamField>
|
||||
<ParamField path="requireMention" type="boolean" default="true">
|
||||
Require @mention.
|
||||
</ParamField>
|
||||
|
||||
**Provider options:**
|
||||
### Provider options
|
||||
|
||||
- `channels.twitch.enabled` - Enable/disable channel startup
|
||||
- `channels.twitch.username` - Bot username (simplified single-account config)
|
||||
@@ -368,25 +403,25 @@ Example:
|
||||
}
|
||||
```
|
||||
|
||||
## Safety & ops
|
||||
## Safety and ops
|
||||
|
||||
- **Treat tokens like passwords** - Never commit tokens to git
|
||||
- **Use automatic token refresh** for long-running bots
|
||||
- **Use user ID allowlists** instead of usernames for access control
|
||||
- **Monitor logs** for token refresh events and connection status
|
||||
- **Scope tokens minimally** - Only request `chat:read` and `chat:write`
|
||||
- **If stuck**: Restart the gateway after confirming no other process owns the session
|
||||
- **Treat tokens like passwords** — Never commit tokens to git.
|
||||
- **Use automatic token refresh** for long-running bots.
|
||||
- **Use user ID allowlists** instead of usernames for access control.
|
||||
- **Monitor logs** for token refresh events and connection status.
|
||||
- **Scope tokens minimally** — Only request `chat:read` and `chat:write`.
|
||||
- **If stuck**: Restart the gateway after confirming no other process owns the session.
|
||||
|
||||
## Limits
|
||||
|
||||
- **500 characters** per message (auto-chunked at word boundaries)
|
||||
- Markdown is stripped before chunking
|
||||
- No rate limiting (uses Twitch's built-in rate limits)
|
||||
- **500 characters** per message (auto-chunked at word boundaries).
|
||||
- Markdown is stripped before chunking.
|
||||
- No rate limiting (uses Twitch's built-in rate limits).
|
||||
|
||||
## Related
|
||||
|
||||
- [Channels Overview](/channels) — all supported channels
|
||||
- [Pairing](/channels/pairing) — DM authentication and pairing flow
|
||||
- [Groups](/channels/groups) — group chat behavior and mention gating
|
||||
- [Channel Routing](/channels/channel-routing) — session routing for messages
|
||||
- [Channels Overview](/channels) — all supported channels
|
||||
- [Groups](/channels/groups) — group chat behavior and mention gating
|
||||
- [Pairing](/channels/pairing) — DM authentication and pairing flow
|
||||
- [Security](/gateway/security) — access model and hardening
|
||||
|
||||
@@ -146,6 +146,7 @@ OpenClaw recommends running WhatsApp on a separate number when possible. (The ch
|
||||
## Runtime model
|
||||
|
||||
- Gateway owns the WhatsApp socket and reconnect loop.
|
||||
- The reconnect watchdog uses WhatsApp Web transport activity, not only inbound app-message volume, so a quiet linked-device session is not restarted solely because nobody has sent a message recently. A longer application-silence cap still forces a reconnect if transport frames keep arriving but no application messages are handled for the watchdog window.
|
||||
- Outbound sends require an active WhatsApp listener for the target account.
|
||||
- Status and broadcast chats are ignored (`@status`, `@broadcast`).
|
||||
- Direct chats use DM session rules (`session.dmScope`; default `main` collapses DMs to the agent main session).
|
||||
@@ -510,6 +511,10 @@ Behavior notes:
|
||||
<Accordion title="Linked but disconnected / reconnect loop">
|
||||
Symptom: linked account with repeated disconnects or reconnect attempts.
|
||||
|
||||
Quiet accounts can stay connected past the normal message timeout; the watchdog
|
||||
restarts when WhatsApp Web transport activity stops, the socket closes, or
|
||||
application-level activity stays silent beyond the longer safety window.
|
||||
|
||||
Fix:
|
||||
|
||||
```bash
|
||||
@@ -562,7 +567,9 @@ The effective `direct` map is determined first: if the account defines its own `
|
||||
1. **Direct-specific system prompt** (`direct["<peerId>"].systemPrompt`): used when the specific peer entry exists in the map **and** its `systemPrompt` key is defined. If `systemPrompt` is an empty string (`""`), the wildcard is suppressed and no system prompt is applied.
|
||||
2. **Direct wildcard system prompt** (`direct["*"].systemPrompt`): used when the specific peer entry is absent from the map entirely, or when it exists but defines no `systemPrompt` key.
|
||||
|
||||
Note: `dms` remains the lightweight per-DM history override bucket (`dms.<id>.historyLimit`); prompt overrides live under `direct`.
|
||||
<Note>
|
||||
`dms` remains the lightweight per-DM history override bucket (`dms.<id>.historyLimit`). Prompt overrides live under `direct`.
|
||||
</Note>
|
||||
|
||||
**Difference from Telegram multi-account behavior:** In Telegram, root `groups` is intentionally suppressed for all accounts in a multi-account setup — even accounts that define no `groups` of their own — to prevent a bot from receiving group messages for groups it does not belong to. WhatsApp does not apply this guard: root `groups` and root `direct` are always inherited by accounts that define no account-level override, regardless of how many accounts are configured. In a multi-account WhatsApp setup, if you want per-account group or direct prompts, define the full map under each account explicitly rather than relying on root-level defaults.
|
||||
|
||||
|
||||
@@ -8,7 +8,9 @@ title: "Zalo personal"
|
||||
|
||||
Status: experimental. This integration automates a **personal Zalo account** via native `zca-js` inside OpenClaw.
|
||||
|
||||
> **Warning:** This is an unofficial integration and may result in account suspension/ban. Use at your own risk.
|
||||
<Warning>
|
||||
This is an unofficial integration and may result in account suspension or ban. Use at your own risk.
|
||||
</Warning>
|
||||
|
||||
## Bundled plugin
|
||||
|
||||
|
||||
225
docs/ci.md
225
docs/ci.md
File diff suppressed because one or more lines are too long
@@ -11,9 +11,9 @@ Manage isolated agents (workspaces + auth + routing).
|
||||
|
||||
Related:
|
||||
|
||||
- Multi-agent routing: [Multi-Agent Routing](/concepts/multi-agent)
|
||||
- Agent workspace: [Agent workspace](/concepts/agent-workspace)
|
||||
- Skill visibility config: [Skills config](/tools/skills-config)
|
||||
- [Multi-agent routing](/concepts/multi-agent)
|
||||
- [Agent workspace](/concepts/agent-workspace)
|
||||
- [Skills config](/tools/skills-config): skill visibility configuration.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -34,10 +34,7 @@ openclaw agents delete work
|
||||
|
||||
Use routing bindings to pin inbound channel traffic to a specific agent.
|
||||
|
||||
If you also want different visible skills per agent, configure
|
||||
`agents.defaults.skills` and `agents.list[].skills` in `openclaw.json`. See
|
||||
[Skills config](/tools/skills-config) and
|
||||
[Configuration Reference](/gateway/config-agents#agents-defaults-skills).
|
||||
If you also want different visible skills per agent, configure `agents.defaults.skills` and `agents.list[].skills` in `openclaw.json`. See [Skills config](/tools/skills-config) and [Configuration reference](/gateway/config-agents#agents-defaults-skills).
|
||||
|
||||
List bindings:
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ Manage chat channel accounts and their runtime status on the Gateway.
|
||||
|
||||
Related docs:
|
||||
|
||||
- Channel guides: [Channels](/channels/index)
|
||||
- Channel guides: [Channels](/channels)
|
||||
- Gateway configuration: [Configuration](/gateway/configuration)
|
||||
|
||||
## Common commands
|
||||
@@ -47,7 +47,9 @@ openclaw channels add --channel nostr --private-key "$NOSTR_PRIVATE_KEY"
|
||||
openclaw channels remove --channel telegram --delete
|
||||
```
|
||||
|
||||
Tip: `openclaw channels add --help` shows per-channel flags (token, private key, app token, signal-cli paths, etc).
|
||||
<Tip>
|
||||
`openclaw channels add --help` shows per-channel flags (token, private key, app token, signal-cli paths, etc).
|
||||
</Tip>
|
||||
|
||||
Common non-interactive add surfaces include:
|
||||
|
||||
@@ -59,6 +61,8 @@ Common non-interactive add surfaces include:
|
||||
- Tlon fields: `--ship`, `--url`, `--code`, `--group-channels`, `--dm-allowlist`, `--auto-discover-channels`
|
||||
- `--use-env` for default-account env-backed auth where supported
|
||||
|
||||
If a channel plugin needs to be installed during a flag-driven add command, OpenClaw uses the channel's default install source without opening the interactive plugin install prompt.
|
||||
|
||||
When you run `openclaw channels add` without flags, the interactive wizard can prompt:
|
||||
|
||||
- account ids per selected channel
|
||||
@@ -79,17 +83,15 @@ Routing behavior stays consistent:
|
||||
|
||||
If your config was already in a mixed state (named accounts present and top-level single-account values still set), run `openclaw doctor --fix` to move account-scoped values into the promoted account chosen for that channel. Most channels promote into `accounts.default`; Matrix can preserve an existing named/default target instead.
|
||||
|
||||
## Login / logout (interactive)
|
||||
## Login and logout (interactive)
|
||||
|
||||
```bash
|
||||
openclaw channels login --channel whatsapp
|
||||
openclaw channels logout --channel whatsapp
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- `channels login` supports `--verbose`.
|
||||
- `channels login` / `logout` can infer the channel when only one supported login target is configured.
|
||||
- `channels login` and `logout` can infer the channel when only one supported login target is configured.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
|
||||
@@ -3,29 +3,18 @@ summary: "CLI reference for `openclaw config` (get/set/unset/file/schema/validat
|
||||
read_when:
|
||||
- You want to read or edit config non-interactively
|
||||
title: "Config"
|
||||
sidebarTitle: "Config"
|
||||
---
|
||||
|
||||
# `openclaw config`
|
||||
Config helpers for non-interactive edits in `openclaw.json`: get/set/unset/file/schema/validate values by path and print the active config file. Run without a subcommand to open the configure wizard (same as `openclaw configure`).
|
||||
|
||||
Config helpers for non-interactive edits in `openclaw.json`: get/set/unset/file/schema/validate
|
||||
values by path and print the active config file. Run without a subcommand to
|
||||
open the configure wizard (same as `openclaw configure`).
|
||||
## Root options
|
||||
|
||||
Root options:
|
||||
<ParamField path="--section <section>" type="string">
|
||||
Repeatable guided-setup section filter when you run `openclaw config` without a subcommand.
|
||||
</ParamField>
|
||||
|
||||
- `--section <section>`: repeatable guided-setup section filter when you run `openclaw config` without a subcommand
|
||||
|
||||
Supported guided sections:
|
||||
|
||||
- `workspace`
|
||||
- `model`
|
||||
- `web`
|
||||
- `gateway`
|
||||
- `daemon`
|
||||
- `channels`
|
||||
- `plugins`
|
||||
- `skills`
|
||||
- `health`
|
||||
Supported guided sections: `workspace`, `model`, `web`, `gateway`, `daemon`, `channels`, `plugins`, `skills`, `health`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -52,21 +41,19 @@ openclaw config validate --json
|
||||
|
||||
Print the generated JSON schema for `openclaw.json` to stdout as JSON.
|
||||
|
||||
What it includes:
|
||||
|
||||
- The current root config schema, plus a root `$schema` string field for editor tooling
|
||||
- Field `title` and `description` docs metadata used by the Control UI
|
||||
- Nested object, wildcard (`*`), and array-item (`[]`) nodes inherit the same `title` / `description` metadata when matching field documentation exists
|
||||
- `anyOf` / `oneOf` / `allOf` branches inherit the same docs metadata too when matching field documentation exists
|
||||
- Best-effort live plugin + channel schema metadata when runtime manifests can be loaded
|
||||
- A clean fallback schema even when the current config is invalid
|
||||
|
||||
Related runtime RPC:
|
||||
|
||||
- `config.schema.lookup` returns one normalized config path with a shallow
|
||||
schema node (`title`, `description`, `type`, `enum`, `const`, common bounds),
|
||||
matched UI hint metadata, and immediate child summaries. Use it for
|
||||
path-scoped drill-down in Control UI or custom clients.
|
||||
<AccordionGroup>
|
||||
<Accordion title="What it includes">
|
||||
- The current root config schema, plus a root `$schema` string field for editor tooling.
|
||||
- Field `title` and `description` docs metadata used by the Control UI.
|
||||
- Nested object, wildcard (`*`), and array-item (`[]`) nodes inherit the same `title` / `description` metadata when matching field documentation exists.
|
||||
- `anyOf` / `oneOf` / `allOf` branches inherit the same docs metadata too when matching field documentation exists.
|
||||
- Best-effort live plugin + channel schema metadata when runtime manifests can be loaded.
|
||||
- A clean fallback schema even when the current config is invalid.
|
||||
</Accordion>
|
||||
<Accordion title="Related runtime RPC">
|
||||
`config.schema.lookup` returns one normalized config path with a shallow schema node (`title`, `description`, `type`, `enum`, `const`, common bounds), matched UI hint metadata, and immediate child summaries. Use it for path-scoped drill-down in Control UI or custom clients.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
```bash
|
||||
openclaw config schema
|
||||
@@ -96,8 +83,7 @@ openclaw config set agents.list[1].tools.exec.node "node-id-or-name"
|
||||
|
||||
## Values
|
||||
|
||||
Values are parsed as JSON5 when possible; otherwise they are treated as strings.
|
||||
Use `--strict-json` to require JSON5 parsing. `--json` remains supported as a legacy alias.
|
||||
Values are parsed as JSON5 when possible; otherwise they are treated as strings. Use `--strict-json` to require JSON5 parsing. `--json` remains supported as a legacy alias.
|
||||
|
||||
```bash
|
||||
openclaw config set agents.defaults.heartbeat.every "0m"
|
||||
@@ -107,11 +93,9 @@ 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`.
|
||||
<Note>
|
||||
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`.
|
||||
</Note>
|
||||
|
||||
Use `--merge` when adding entries to those maps:
|
||||
|
||||
@@ -120,59 +104,65 @@ openclaw config set agents.defaults.models '{"openai/gpt-5.4":{}}' --strict-json
|
||||
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.
|
||||
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:
|
||||
|
||||
1. Value mode: `openclaw config set <path> <value>`
|
||||
2. SecretRef builder mode:
|
||||
<Tabs>
|
||||
<Tab title="Value mode">
|
||||
```bash
|
||||
openclaw config set <path> <value>
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="SecretRef builder mode">
|
||||
```bash
|
||||
openclaw config set channels.discord.token \
|
||||
--ref-provider default \
|
||||
--ref-source env \
|
||||
--ref-id DISCORD_BOT_TOKEN
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Provider builder mode">
|
||||
Provider builder mode targets `secrets.providers.<alias>` paths only:
|
||||
|
||||
```bash
|
||||
openclaw config set channels.discord.token \
|
||||
--ref-provider default \
|
||||
--ref-source env \
|
||||
--ref-id DISCORD_BOT_TOKEN
|
||||
```
|
||||
```bash
|
||||
openclaw config set secrets.providers.vault \
|
||||
--provider-source exec \
|
||||
--provider-command /usr/local/bin/openclaw-vault \
|
||||
--provider-arg read \
|
||||
--provider-arg openai/api-key \
|
||||
--provider-timeout-ms 5000
|
||||
```
|
||||
|
||||
3. Provider builder mode (`secrets.providers.<alias>` path only):
|
||||
</Tab>
|
||||
<Tab title="Batch mode">
|
||||
```bash
|
||||
openclaw config set --batch-json '[
|
||||
{
|
||||
"path": "secrets.providers.default",
|
||||
"provider": { "source": "env" }
|
||||
},
|
||||
{
|
||||
"path": "channels.discord.token",
|
||||
"ref": { "source": "env", "provider": "default", "id": "DISCORD_BOT_TOKEN" }
|
||||
}
|
||||
]'
|
||||
```
|
||||
|
||||
```bash
|
||||
openclaw config set secrets.providers.vault \
|
||||
--provider-source exec \
|
||||
--provider-command /usr/local/bin/openclaw-vault \
|
||||
--provider-arg read \
|
||||
--provider-arg openai/api-key \
|
||||
--provider-timeout-ms 5000
|
||||
```
|
||||
```bash
|
||||
openclaw config set --batch-file ./config-set.batch.json --dry-run
|
||||
```
|
||||
|
||||
4. Batch mode (`--batch-json` or `--batch-file`):
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
```bash
|
||||
openclaw config set --batch-json '[
|
||||
{
|
||||
"path": "secrets.providers.default",
|
||||
"provider": { "source": "env" }
|
||||
},
|
||||
{
|
||||
"path": "channels.discord.token",
|
||||
"ref": { "source": "env", "provider": "default", "id": "DISCORD_BOT_TOKEN" }
|
||||
}
|
||||
]'
|
||||
```
|
||||
<Warning>
|
||||
SecretRef assignments are rejected on unsupported runtime-mutable surfaces (for example `hooks.token`, `commands.ownerDisplaySecret`, Discord thread-binding webhook tokens, and WhatsApp creds JSON). See [SecretRef Credential Surface](/reference/secretref-credential-surface).
|
||||
</Warning>
|
||||
|
||||
```bash
|
||||
openclaw config set --batch-file ./config-set.batch.json --dry-run
|
||||
```
|
||||
|
||||
Policy note:
|
||||
|
||||
- SecretRef assignments are rejected on unsupported runtime-mutable surfaces (for example `hooks.token`, `commands.ownerDisplaySecret`, Discord thread-binding webhook tokens, and WhatsApp creds JSON). See [SecretRef Credential Surface](/reference/secretref-credential-surface).
|
||||
|
||||
Batch parsing always uses the batch payload (`--batch-json`/`--batch-file`) as the source of truth.
|
||||
`--strict-json` / `--json` do not change batch parsing behavior.
|
||||
Batch parsing always uses the batch payload (`--batch-json`/`--batch-file`) as the source of truth. `--strict-json` / `--json` do not change batch parsing behavior.
|
||||
|
||||
JSON path/value mode remains supported for both SecretRefs and providers:
|
||||
|
||||
@@ -190,34 +180,33 @@ openclaw config set secrets.providers.vaultfile \
|
||||
|
||||
Provider builder targets must use `secrets.providers.<alias>` as the path.
|
||||
|
||||
Common flags:
|
||||
|
||||
- `--provider-source <env|file|exec>`
|
||||
- `--provider-timeout-ms <ms>` (`file`, `exec`)
|
||||
|
||||
Env provider (`--provider-source env`):
|
||||
|
||||
- `--provider-allowlist <ENV_VAR>` (repeatable)
|
||||
|
||||
File provider (`--provider-source file`):
|
||||
|
||||
- `--provider-path <path>` (required)
|
||||
- `--provider-mode <singleValue|json>`
|
||||
- `--provider-max-bytes <bytes>`
|
||||
- `--provider-allow-insecure-path`
|
||||
|
||||
Exec provider (`--provider-source exec`):
|
||||
|
||||
- `--provider-command <path>` (required)
|
||||
- `--provider-arg <arg>` (repeatable)
|
||||
- `--provider-no-output-timeout-ms <ms>`
|
||||
- `--provider-max-output-bytes <bytes>`
|
||||
- `--provider-json-only`
|
||||
- `--provider-env <KEY=VALUE>` (repeatable)
|
||||
- `--provider-pass-env <ENV_VAR>` (repeatable)
|
||||
- `--provider-trusted-dir <path>` (repeatable)
|
||||
- `--provider-allow-insecure-path`
|
||||
- `--provider-allow-symlink-command`
|
||||
<AccordionGroup>
|
||||
<Accordion title="Common flags">
|
||||
- `--provider-source <env|file|exec>`
|
||||
- `--provider-timeout-ms <ms>` (`file`, `exec`)
|
||||
</Accordion>
|
||||
<Accordion title="Env provider (--provider-source env)">
|
||||
- `--provider-allowlist <ENV_VAR>` (repeatable)
|
||||
</Accordion>
|
||||
<Accordion title="File provider (--provider-source file)">
|
||||
- `--provider-path <path>` (required)
|
||||
- `--provider-mode <singleValue|json>`
|
||||
- `--provider-max-bytes <bytes>`
|
||||
- `--provider-allow-insecure-path`
|
||||
</Accordion>
|
||||
<Accordion title="Exec provider (--provider-source exec)">
|
||||
- `--provider-command <path>` (required)
|
||||
- `--provider-arg <arg>` (repeatable)
|
||||
- `--provider-no-output-timeout-ms <ms>`
|
||||
- `--provider-max-output-bytes <bytes>`
|
||||
- `--provider-json-only`
|
||||
- `--provider-env <KEY=VALUE>` (repeatable)
|
||||
- `--provider-pass-env <ENV_VAR>` (repeatable)
|
||||
- `--provider-trusted-dir <path>` (repeatable)
|
||||
- `--provider-allow-insecure-path`
|
||||
- `--provider-allow-symlink-command`
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
Hardened exec provider example:
|
||||
|
||||
@@ -259,25 +248,29 @@ openclaw config set channels.discord.token \
|
||||
--allow-exec
|
||||
```
|
||||
|
||||
Dry-run behavior:
|
||||
<AccordionGroup>
|
||||
<Accordion title="Dry-run behavior">
|
||||
- Builder mode: runs SecretRef resolvability checks for changed refs/providers.
|
||||
- JSON mode (`--strict-json`, `--json`, or batch mode): runs schema validation plus SecretRef resolvability checks.
|
||||
- Policy validation also runs for known unsupported SecretRef target surfaces.
|
||||
- Policy checks evaluate the full post-change config, so parent-object writes (for example setting `hooks` as an object) cannot bypass unsupported-surface validation.
|
||||
- Exec SecretRef checks are skipped by default during dry-run to avoid command side effects.
|
||||
- Use `--allow-exec` with `--dry-run` to opt in to exec SecretRef checks (this may execute provider commands).
|
||||
- `--allow-exec` is dry-run only and errors if used without `--dry-run`.
|
||||
</Accordion>
|
||||
<Accordion title="--dry-run --json fields">
|
||||
`--dry-run --json` prints a machine-readable report:
|
||||
|
||||
- Builder mode: runs SecretRef resolvability checks for changed refs/providers.
|
||||
- JSON mode (`--strict-json`, `--json`, or batch mode): runs schema validation plus SecretRef resolvability checks.
|
||||
- Policy validation also runs for known unsupported SecretRef target surfaces.
|
||||
- Policy checks evaluate the full post-change config, so parent-object writes (for example setting `hooks` as an object) cannot bypass unsupported-surface validation.
|
||||
- Exec SecretRef checks are skipped by default during dry-run to avoid command side effects.
|
||||
- Use `--allow-exec` with `--dry-run` to opt in to exec SecretRef checks (this may execute provider commands).
|
||||
- `--allow-exec` is dry-run only and errors if used without `--dry-run`.
|
||||
- `ok`: whether dry-run passed
|
||||
- `operations`: number of assignments evaluated
|
||||
- `checks`: whether schema/resolvability checks ran
|
||||
- `checks.resolvabilityComplete`: whether resolvability checks ran to completion (false when exec refs are skipped)
|
||||
- `refsChecked`: number of refs actually resolved during dry-run
|
||||
- `skippedExecRefs`: number of exec refs skipped because `--allow-exec` was not set
|
||||
- `errors`: structured schema/resolvability failures when `ok=false`
|
||||
|
||||
`--dry-run --json` prints a machine-readable report:
|
||||
|
||||
- `ok`: whether dry-run passed
|
||||
- `operations`: number of assignments evaluated
|
||||
- `checks`: whether schema/resolvability checks ran
|
||||
- `checks.resolvabilityComplete`: whether resolvability checks ran to completion (false when exec refs are skipped)
|
||||
- `refsChecked`: number of refs actually resolved during dry-run
|
||||
- `skippedExecRefs`: number of exec refs skipped because `--allow-exec` was not set
|
||||
- `errors`: structured schema/resolvability failures when `ok=false`
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
### JSON output shape
|
||||
|
||||
@@ -304,66 +297,67 @@ Dry-run behavior:
|
||||
}
|
||||
```
|
||||
|
||||
Success example:
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"operations": 1,
|
||||
"configPath": "~/.openclaw/openclaw.json",
|
||||
"inputModes": ["builder"],
|
||||
"checks": {
|
||||
"schema": false,
|
||||
"resolvability": true,
|
||||
"resolvabilityComplete": true
|
||||
},
|
||||
"refsChecked": 1,
|
||||
"skippedExecRefs": 0
|
||||
}
|
||||
```
|
||||
|
||||
Failure example:
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": false,
|
||||
"operations": 1,
|
||||
"configPath": "~/.openclaw/openclaw.json",
|
||||
"inputModes": ["builder"],
|
||||
"checks": {
|
||||
"schema": false,
|
||||
"resolvability": true,
|
||||
"resolvabilityComplete": true
|
||||
},
|
||||
"refsChecked": 1,
|
||||
"skippedExecRefs": 0,
|
||||
"errors": [
|
||||
<Tabs>
|
||||
<Tab title="Success example">
|
||||
```json
|
||||
{
|
||||
"kind": "resolvability",
|
||||
"message": "Error: Environment variable \"MISSING_TEST_SECRET\" is not set.",
|
||||
"ref": "env:default:MISSING_TEST_SECRET"
|
||||
"ok": true,
|
||||
"operations": 1,
|
||||
"configPath": "~/.openclaw/openclaw.json",
|
||||
"inputModes": ["builder"],
|
||||
"checks": {
|
||||
"schema": false,
|
||||
"resolvability": true,
|
||||
"resolvabilityComplete": true
|
||||
},
|
||||
"refsChecked": 1,
|
||||
"skippedExecRefs": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Failure example">
|
||||
```json
|
||||
{
|
||||
"ok": false,
|
||||
"operations": 1,
|
||||
"configPath": "~/.openclaw/openclaw.json",
|
||||
"inputModes": ["builder"],
|
||||
"checks": {
|
||||
"schema": false,
|
||||
"resolvability": true,
|
||||
"resolvabilityComplete": true
|
||||
},
|
||||
"refsChecked": 1,
|
||||
"skippedExecRefs": 0,
|
||||
"errors": [
|
||||
{
|
||||
"kind": "resolvability",
|
||||
"message": "Error: Environment variable \"MISSING_TEST_SECRET\" is not set.",
|
||||
"ref": "env:default:MISSING_TEST_SECRET"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
If dry-run fails:
|
||||
|
||||
- `config schema validation failed`: your post-change config shape is invalid; fix path/value or provider/ref object shape.
|
||||
- `Config policy validation failed: unsupported SecretRef usage`: move that credential back to plaintext/string input and keep SecretRefs on supported surfaces only.
|
||||
- `SecretRef assignment(s) could not be resolved`: referenced provider/ref currently cannot resolve (missing env var, invalid file pointer, exec provider failure, or provider/source mismatch).
|
||||
- `Dry run note: skipped <n> exec SecretRef resolvability check(s)`: dry-run skipped exec refs; rerun with `--allow-exec` if you need exec resolvability validation.
|
||||
- For batch mode, fix failing entries and rerun `--dry-run` before writing.
|
||||
<AccordionGroup>
|
||||
<Accordion title="If dry-run fails">
|
||||
- `config schema validation failed`: your post-change config shape is invalid; fix path/value or provider/ref object shape.
|
||||
- `Config policy validation failed: unsupported SecretRef usage`: move that credential back to plaintext/string input and keep SecretRefs on supported surfaces only.
|
||||
- `SecretRef assignment(s) could not be resolved`: referenced provider/ref currently cannot resolve (missing env var, invalid file pointer, exec provider failure, or provider/source mismatch).
|
||||
- `Dry run note: skipped <n> exec SecretRef resolvability check(s)`: dry-run skipped exec refs; rerun with `--allow-exec` if you need exec resolvability validation.
|
||||
- For batch mode, fix failing entries and rerun `--dry-run` before writing.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Write safety
|
||||
|
||||
`openclaw config set` and other OpenClaw-owned config writers validate the full
|
||||
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.
|
||||
`openclaw config set` and other OpenClaw-owned config writers validate the full 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.*`.
|
||||
|
||||
<Warning>
|
||||
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.
|
||||
</Warning>
|
||||
|
||||
Prefer CLI writes for small edits:
|
||||
|
||||
@@ -381,19 +375,9 @@ ls -lt "$CONFIG".rejected.* 2>/dev/null | head
|
||||
openclaw config validate
|
||||
```
|
||||
|
||||
Direct editor writes are still allowed, but the running Gateway treats them as
|
||||
untrusted until they validate. Invalid direct edits can be restored from the
|
||||
last-known-good backup during startup or hot reload. See
|
||||
[Gateway troubleshooting](/gateway/troubleshooting#gateway-restored-last-known-good-config).
|
||||
Direct editor writes are still allowed, but the running Gateway treats them as untrusted until they validate. Invalid direct edits can be restored from the last-known-good backup during startup or hot reload. See [Gateway troubleshooting](/gateway/troubleshooting#gateway-restored-last-known-good-config).
|
||||
|
||||
Whole-file recovery is reserved for globally broken config, such as parse
|
||||
errors, root-level schema failures, legacy migration failures, or mixed plugin
|
||||
and root failures. If validation fails only under `plugins.entries.<id>...`,
|
||||
OpenClaw keeps the active `openclaw.json` in place and reports the plugin-local
|
||||
issue instead of restoring `.last-good`. This prevents plugin schema changes or
|
||||
`minHostVersion` skew from rolling back unrelated user settings such as models,
|
||||
providers, auth profiles, channels, gateway exposure, tools, memory, browser, or
|
||||
cron config.
|
||||
Whole-file recovery is reserved for globally broken config, such as parse errors, root-level schema failures, legacy migration failures, or mixed plugin and root failures. If validation fails only under `plugins.entries.<id>...`, OpenClaw keeps the active `openclaw.json` in place and reports the plugin-local issue instead of restoring `.last-good`. This prevents plugin schema changes or `minHostVersion` skew from rolling back unrelated user settings such as models, providers, auth profiles, channels, gateway exposure, tools, memory, browser, or cron config.
|
||||
|
||||
## Subcommands
|
||||
|
||||
@@ -403,21 +387,18 @@ Restart the gateway after edits.
|
||||
|
||||
## Validate
|
||||
|
||||
Validate the current config against the active schema without starting the
|
||||
gateway.
|
||||
Validate the current config against the active schema without starting the gateway.
|
||||
|
||||
```bash
|
||||
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:
|
||||
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.
|
||||
<Note>
|
||||
If validation is already failing, start with `openclaw configure` or `openclaw doctor --fix`. `openclaw chat` does not bypass the invalid-config guard.
|
||||
</Note>
|
||||
|
||||
```bash
|
||||
openclaw chat
|
||||
@@ -434,10 +415,20 @@ Then inside the TUI:
|
||||
|
||||
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.
|
||||
<Steps>
|
||||
<Step title="Compare with docs">
|
||||
Ask the agent to compare your current config with the relevant docs page and suggest the smallest fix.
|
||||
</Step>
|
||||
<Step title="Apply targeted edits">
|
||||
Apply targeted edits with `openclaw config set` or `openclaw configure`.
|
||||
</Step>
|
||||
<Step title="Re-validate">
|
||||
Rerun `openclaw config validate` after each change.
|
||||
</Step>
|
||||
<Step title="Doctor for runtime issues">
|
||||
If validation passes but the runtime is still unhealthy, run `openclaw doctor` or `openclaw doctor --fix` for migration and repair help.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## Related
|
||||
|
||||
|
||||
@@ -9,23 +9,15 @@ title: "Configure"
|
||||
|
||||
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.
|
||||
Re-running provider auth from configure preserves an existing
|
||||
`agents.defaults.model.primary`; use `openclaw models auth login --provider <id> --set-default`
|
||||
or `openclaw models set <model>` when you intentionally want to change the default model.
|
||||
<Note>
|
||||
The **Model** section 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. Re-running provider auth from configure preserves an existing `agents.defaults.model.primary`. Use `openclaw models auth login --provider <id> --set-default` or `openclaw models set <model>` when you intentionally want to change the default model.
|
||||
</Note>
|
||||
|
||||
When configure starts from a provider auth choice, the default-model and
|
||||
allowlist pickers prefer that provider automatically. For paired providers such
|
||||
as Volcengine/BytePlus, the same preference also matches their coding-plan
|
||||
variants (`volcengine-plan/*`, `byteplus-plan/*`). If the preferred-provider
|
||||
filter would produce an empty list, configure falls back to the unfiltered
|
||||
catalog instead of showing a blank picker.
|
||||
When configure starts from a provider auth choice, the default-model and allowlist pickers prefer that provider automatically. For paired providers such as Volcengine and BytePlus, the same preference also matches their coding-plan variants (`volcengine-plan/*`, `byteplus-plan/*`). If the preferred-provider filter would produce an empty list, configure falls back to the unfiltered catalog instead of showing a blank picker.
|
||||
|
||||
Tip: `openclaw config` without a subcommand opens the same wizard. Use
|
||||
`openclaw config get|set|unset` for non-interactive edits.
|
||||
<Tip>
|
||||
`openclaw config` without a subcommand opens the same wizard. Use `openclaw config get|set|unset` for non-interactive edits.
|
||||
</Tip>
|
||||
|
||||
For web search, `openclaw configure --section web` lets you choose a provider
|
||||
and configure its credentials. Some providers also show provider-specific
|
||||
|
||||
@@ -162,7 +162,7 @@ configured OpenClaw model. If no configured model is usable yet, it can fall
|
||||
back to local runtimes already present on the machine:
|
||||
|
||||
- Claude Code CLI: `claude-cli/claude-opus-4-7`
|
||||
- Codex app-server harness: `openai/gpt-5.5` with `embeddedHarness.runtime: "codex"`
|
||||
- Codex app-server harness: `openai/gpt-5.5` with `agentRuntime.id: "codex"`
|
||||
- Codex CLI: `codex-cli/gpt-5.5`
|
||||
|
||||
The model-assisted planner cannot mutate config directly. It must translate the
|
||||
|
||||
195
docs/cli/cron.md
195
docs/cli/cron.md
@@ -2,7 +2,7 @@
|
||||
summary: "CLI reference for `openclaw cron` (schedule and run background jobs)"
|
||||
read_when:
|
||||
- You want scheduled jobs and wakeups
|
||||
- You’re debugging cron execution and logs
|
||||
- You are debugging cron execution and logs
|
||||
title: "Cron"
|
||||
---
|
||||
|
||||
@@ -10,86 +10,138 @@ title: "Cron"
|
||||
|
||||
Manage cron jobs for the Gateway scheduler.
|
||||
|
||||
Related:
|
||||
<Tip>
|
||||
Run `openclaw cron --help` for the full command surface. See [Cron jobs](/automation/cron-jobs) for the conceptual guide.
|
||||
</Tip>
|
||||
|
||||
- Cron jobs: [Cron jobs](/automation/cron-jobs)
|
||||
## Sessions
|
||||
|
||||
Tip: run `openclaw cron --help` for the full command surface.
|
||||
`--session` accepts `main`, `isolated`, `current`, or `session:<id>`.
|
||||
|
||||
Note: `openclaw cron list` and `openclaw cron show <job-id>` preview the
|
||||
resolved delivery route. For `channel: "last"`, the preview shows whether the
|
||||
route resolved from the main/current session or will fail closed.
|
||||
<AccordionGroup>
|
||||
<Accordion title="Session keys">
|
||||
- `main` binds to the agent's main session.
|
||||
- `isolated` creates a fresh transcript and session id for each run.
|
||||
- `current` binds to the active session at creation time.
|
||||
- `session:<id>` pins to an explicit persistent session key.
|
||||
</Accordion>
|
||||
<Accordion title="Isolated session semantics">
|
||||
Isolated runs reset ambient conversation context. Channel and group routing, send/queue policy, elevation, origin, and ACP runtime binding are reset for the new run. Safe preferences and explicit user-selected model or auth overrides can carry across runs.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
Note: isolated `cron add` jobs default to `--announce` delivery. Use `--no-deliver` to keep
|
||||
output internal. `--deliver` remains as a deprecated alias for `--announce`.
|
||||
## Delivery
|
||||
|
||||
Note: isolated cron chat delivery is shared. `--announce` is runner fallback
|
||||
delivery for the final reply; `--no-deliver` disables that fallback but does
|
||||
not remove the agent's `message` tool when a chat route is available.
|
||||
`openclaw cron list` and `openclaw cron show <job-id>` preview the resolved delivery route. For `channel: "last"`, the preview shows whether the route resolved from the main or current session, or will fail closed.
|
||||
|
||||
Note: one-shot (`--at`) jobs delete after success by default. Use `--keep-after-run` to keep them.
|
||||
<Note>
|
||||
Isolated `cron add` jobs default to `--announce` delivery. Use `--no-deliver` to keep output internal. `--deliver` remains as a deprecated alias for `--announce`.
|
||||
</Note>
|
||||
|
||||
Note: `--session` supports `main`, `isolated`, `current`, and `session:<id>`.
|
||||
Use `current` to bind to the active session at creation time, or `session:<id>` for
|
||||
an explicit persistent session key.
|
||||
### Delivery ownership
|
||||
|
||||
Note: `--session isolated` creates a fresh transcript/session id for each run.
|
||||
Safe preferences and explicit user-selected model/auth overrides can carry, but
|
||||
ambient conversation context does not: channel/group routing, send/queue policy,
|
||||
elevation, origin, and ACP runtime binding are reset for the new isolated run.
|
||||
Isolated cron chat delivery is shared between the agent and the runner:
|
||||
|
||||
Note: for one-shot CLI jobs, offset-less `--at` datetimes are treated as UTC unless you also pass
|
||||
`--tz <iana>`, which interprets that local wall-clock time in the given timezone.
|
||||
- The agent can send directly using the `message` tool when a chat route is available.
|
||||
- `announce` fallback-delivers the final reply only when the agent did not send directly to the resolved target.
|
||||
- `webhook` posts the finished payload to a URL.
|
||||
- `none` disables runner fallback delivery.
|
||||
|
||||
Note: recurring jobs now use exponential retry backoff after consecutive errors (30s → 1m → 5m → 15m → 60m), then return to normal schedule after the next successful run.
|
||||
`--announce` is runner fallback delivery for the final reply. `--no-deliver` disables that fallback but does not remove the agent's `message` tool when a chat route is available.
|
||||
|
||||
Note: `openclaw cron run` now returns as soon as the manual run is queued for execution. Successful responses include `{ ok: true, enqueued: true, runId }`; use `openclaw cron runs --id <job-id>` to follow the eventual outcome.
|
||||
Reminders created from an active chat preserve the live chat delivery target for fallback announce delivery. Internal session keys may be lowercase; do not use them as a source of truth for case-sensitive provider IDs such as Matrix room IDs.
|
||||
|
||||
Note: `openclaw cron run <job-id>` force-runs by default. Use `--due` to keep the
|
||||
older "only run if due" behavior.
|
||||
### Failure delivery
|
||||
|
||||
Note: isolated cron turns suppress stale acknowledgement-only replies. If the
|
||||
first result is just an interim status update and no descendant subagent run is
|
||||
responsible for the eventual answer, cron re-prompts once for the real result
|
||||
before delivery.
|
||||
Failure notifications resolve in this order:
|
||||
|
||||
Note: if an isolated cron run returns only the silent token (`NO_REPLY` /
|
||||
`no_reply`), cron suppresses direct outbound delivery and the fallback queued
|
||||
summary path as well, so nothing is posted back to chat.
|
||||
1. `delivery.failureDestination` on the job.
|
||||
2. Global `cron.failureDestination`.
|
||||
3. The job's primary announce target (when no explicit failure destination is set).
|
||||
|
||||
Note: `cron add|edit --model ...` uses that selected allowed model for the job.
|
||||
If the model is not allowed, cron warns and falls back to the job's agent/default
|
||||
model selection instead. Configured fallback chains still apply, but a plain
|
||||
model override with no explicit per-job fallback list no longer appends the
|
||||
agent primary as a hidden extra retry target.
|
||||
<Note>
|
||||
Main-session jobs may only use `delivery.failureDestination` when primary delivery mode is `webhook`. Isolated jobs accept it in all modes.
|
||||
</Note>
|
||||
|
||||
Note: isolated cron model precedence is Gmail-hook override first, then per-job
|
||||
`--model`, then any user-selected stored cron-session model override, then the
|
||||
normal agent/default selection.
|
||||
Note: isolated cron runs treat run-level agent failures as job errors even when
|
||||
no reply payload is produced, so model/provider failures still increment error
|
||||
counters and trigger failure notifications.
|
||||
|
||||
Note: isolated cron fast mode follows the resolved live model selection. Model
|
||||
config `params.fastMode` applies by default, but a stored session `fastMode`
|
||||
override still wins over config.
|
||||
## Scheduling
|
||||
|
||||
Note: if an isolated run throws `LiveSessionModelSwitchError`, cron persists the
|
||||
switched provider/model (and switched auth profile override when present) for
|
||||
the active run before retrying. The outer retry loop is bounded to 2 switch
|
||||
retries after the initial attempt, then aborts instead of looping forever.
|
||||
### One-shot jobs
|
||||
|
||||
Note: failure notifications use `delivery.failureDestination` first, then
|
||||
global `cron.failureDestination`, and finally fall back to the job's primary
|
||||
announce target when no explicit failure destination is configured.
|
||||
`--at <datetime>` schedules a one-shot run. Offset-less datetimes are treated as UTC unless you also pass `--tz <iana>`, which interprets the wall-clock time in the given timezone.
|
||||
|
||||
Note: retention/pruning is controlled in config:
|
||||
<Note>
|
||||
One-shot jobs delete after success by default. Use `--keep-after-run` to preserve them.
|
||||
</Note>
|
||||
|
||||
### Recurring jobs
|
||||
|
||||
Recurring jobs use exponential retry backoff after consecutive errors: 30s, 1m, 5m, 15m, 60m. The schedule returns to normal after the next successful run.
|
||||
|
||||
### Manual runs
|
||||
|
||||
`openclaw cron run` returns as soon as the manual run is queued. Successful responses include `{ ok: true, enqueued: true, runId }`. Use `openclaw cron runs --id <job-id>` to follow the eventual outcome.
|
||||
|
||||
<Note>
|
||||
`openclaw cron run <job-id>` force-runs by default. Use `--due` to keep the older "only run if due" behavior.
|
||||
</Note>
|
||||
|
||||
## Models
|
||||
|
||||
`cron add|edit --model <ref>` selects an allowed model for the job.
|
||||
|
||||
<Warning>
|
||||
If the model is not allowed, cron warns and falls back to the job's agent or default model selection. Configured fallback chains still apply, but a plain model override with no explicit per-job fallback list no longer appends the agent primary as a hidden extra retry target.
|
||||
</Warning>
|
||||
|
||||
### Isolated cron model precedence
|
||||
|
||||
Isolated cron resolves the active model in this order:
|
||||
|
||||
1. Gmail-hook override.
|
||||
2. Per-job `--model`.
|
||||
3. Stored cron-session model override (when the user selected one).
|
||||
4. Agent or default model selection.
|
||||
|
||||
### Fast mode
|
||||
|
||||
Isolated cron fast mode follows the resolved live model selection. Model config `params.fastMode` applies by default, but a stored session `fastMode` override still wins over config.
|
||||
|
||||
### Live model switch retries
|
||||
|
||||
If an isolated run throws `LiveSessionModelSwitchError`, cron persists the switched provider and model (and switched auth profile override when present) for the active run before retrying. The outer retry loop is bounded to two switch retries after the initial attempt, then aborts instead of looping forever.
|
||||
|
||||
## Run output and denials
|
||||
|
||||
### Stale acknowledgement suppression
|
||||
|
||||
Isolated cron turns suppress stale acknowledgement-only replies. If the first result is just an interim status update and no descendant subagent run is responsible for the eventual answer, cron re-prompts once for the real result before delivery.
|
||||
|
||||
### Silent token suppression
|
||||
|
||||
If an isolated cron run returns only the silent token (`NO_REPLY` or `no_reply`), cron suppresses both direct outbound delivery and the fallback queued summary path, so nothing is posted back to chat.
|
||||
|
||||
### Structured denials
|
||||
|
||||
Isolated cron runs prefer structured execution-denial metadata from the embedded run, then fall back to known denial markers in final output, such as `SYSTEM_RUN_DENIED`, `INVALID_REQUEST`, and approval-binding refusal phrases.
|
||||
|
||||
`cron list` and run history surface the denial reason instead of reporting a blocked command as `ok`.
|
||||
|
||||
## Retention
|
||||
|
||||
Retention and pruning are controlled in config:
|
||||
|
||||
- `cron.sessionRetention` (default `24h`) prunes completed isolated run sessions.
|
||||
- `cron.runLog.maxBytes` + `cron.runLog.keepLines` prune `~/.openclaw/cron/runs/<jobId>.jsonl`.
|
||||
- `cron.runLog.maxBytes` and `cron.runLog.keepLines` prune `~/.openclaw/cron/runs/<jobId>.jsonl`.
|
||||
|
||||
Upgrade note: if you have older cron jobs from before the current delivery/store format, run
|
||||
`openclaw doctor --fix`. Doctor now normalizes legacy cron fields (`jobId`, `schedule.cron`,
|
||||
top-level delivery fields including legacy `threadId`, payload `provider` delivery aliases) and migrates simple
|
||||
`notify: true` webhook fallback jobs to explicit webhook delivery when `cron.webhook` is
|
||||
configured.
|
||||
## Migrating older jobs
|
||||
|
||||
<Note>
|
||||
If you have cron jobs from before the current delivery and store format, run `openclaw doctor --fix`. Doctor normalizes legacy cron fields (`jobId`, `schedule.cron`, top-level delivery fields including legacy `threadId`, payload `provider` delivery aliases) and migrates simple `notify: true` webhook fallback jobs to explicit webhook delivery when `cron.webhook` is configured.
|
||||
</Note>
|
||||
|
||||
## Common edits
|
||||
|
||||
@@ -131,21 +183,9 @@ openclaw cron add \
|
||||
|
||||
`--light-context` applies to isolated agent-turn jobs only. For cron runs, lightweight mode keeps bootstrap context empty instead of injecting the full workspace bootstrap set.
|
||||
|
||||
Delivery ownership note:
|
||||
|
||||
- Isolated cron chat delivery is shared. The agent can send directly with the
|
||||
`message` tool when a chat route is available.
|
||||
- `announce` fallback-delivers the final reply only when the agent did not send
|
||||
directly to the resolved target. `webhook` posts the finished payload to a URL.
|
||||
`none` disables runner fallback delivery.
|
||||
- Reminders created from an active chat preserve the live chat delivery target
|
||||
for fallback announce delivery. Internal session keys may be lowercase; do not
|
||||
use them as a source of truth for case-sensitive provider IDs such as Matrix
|
||||
room IDs.
|
||||
|
||||
## Common admin commands
|
||||
|
||||
Manual run:
|
||||
Manual run and inspection:
|
||||
|
||||
```bash
|
||||
openclaw cron list
|
||||
@@ -155,10 +195,9 @@ openclaw cron run <job-id> --due
|
||||
openclaw cron runs --id <job-id> --limit 50
|
||||
```
|
||||
|
||||
`cron runs` entries include delivery diagnostics with the intended cron target,
|
||||
the resolved target, message-tool sends, fallback use, and delivered state.
|
||||
`cron runs` entries include delivery diagnostics with the intended cron target, the resolved target, message-tool sends, fallback use, and delivered state.
|
||||
|
||||
Agent/session retargeting:
|
||||
Agent and session retargeting:
|
||||
|
||||
```bash
|
||||
openclaw cron edit <job-id> --agent ops
|
||||
@@ -176,14 +215,6 @@ openclaw cron edit <job-id> --no-best-effort-deliver
|
||||
openclaw cron edit <job-id> --no-deliver
|
||||
```
|
||||
|
||||
Failure-delivery note:
|
||||
|
||||
- `delivery.failureDestination` is supported for isolated jobs.
|
||||
- Main-session jobs may only use `delivery.failureDestination` when primary
|
||||
delivery mode is `webhook`.
|
||||
- If you do not set any failure destination and the job already announces to a
|
||||
channel, failure notifications reuse that same announce target.
|
||||
|
||||
## Related
|
||||
|
||||
- [CLI reference](/cli)
|
||||
|
||||
@@ -55,10 +55,9 @@ is omitted or `--latest` is passed, OpenClaw only prints the selected pending
|
||||
request and exits; rerun approval with the exact request ID after verifying
|
||||
the details.
|
||||
|
||||
Note: if a device retries pairing with changed auth details (role/scopes/public
|
||||
key), OpenClaw supersedes the previous pending entry and issues a new
|
||||
`requestId`. Run `openclaw devices list` right before approval to use the
|
||||
current ID.
|
||||
<Note>
|
||||
If a device retries pairing with changed auth details (role, scopes, or public key), OpenClaw supersedes the previous pending entry and issues a new `requestId`. Run `openclaw devices list` right before approval to use the current ID.
|
||||
</Note>
|
||||
|
||||
If the device is already paired and asks for broader scopes or a broader role,
|
||||
OpenClaw keeps the existing approval in place and creates a new pending upgrade
|
||||
@@ -128,8 +127,9 @@ Returns the revoke result as JSON.
|
||||
- `--timeout <ms>`: RPC timeout.
|
||||
- `--json`: JSON output (recommended for scripting).
|
||||
|
||||
Note: when you set `--url`, the CLI does not fall back to config or environment credentials.
|
||||
Pass `--token` or `--password` explicitly. Missing explicit credentials is an error.
|
||||
<Warning>
|
||||
When you set `--url`, the CLI does not fall back to config or environment credentials. Pass `--token` or `--password` explicitly. Missing explicit credentials is an error.
|
||||
</Warning>
|
||||
|
||||
## Notes
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ Notes:
|
||||
- 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 writing into packaged global installs. 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`.
|
||||
- Set `OPENCLAW_SERVICE_REPAIR_POLICY=external` when another supervisor owns the gateway lifecycle. Doctor still reports gateway/service health and applies non-service repairs, but skips service install/start/restart/bootstrap and legacy service cleanup.
|
||||
- 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.
|
||||
|
||||
@@ -5,19 +5,22 @@ read_when:
|
||||
- Debugging Gateway auth, bind modes, and connectivity
|
||||
- Discovering gateways via Bonjour (local + wide-area DNS-SD)
|
||||
title: "Gateway"
|
||||
sidebarTitle: "Gateway"
|
||||
---
|
||||
|
||||
# Gateway CLI
|
||||
The Gateway is OpenClaw's WebSocket server (channels, nodes, sessions, hooks). Subcommands in this page live under `openclaw gateway …`.
|
||||
|
||||
The Gateway is OpenClaw’s WebSocket server (channels, nodes, sessions, hooks).
|
||||
|
||||
Subcommands in this page live under `openclaw gateway …`.
|
||||
|
||||
Related docs:
|
||||
|
||||
- [/gateway/bonjour](/gateway/bonjour)
|
||||
- [/gateway/discovery](/gateway/discovery)
|
||||
- [/gateway/configuration](/gateway/configuration)
|
||||
<CardGroup cols={3}>
|
||||
<Card title="Bonjour discovery" href="/gateway/bonjour">
|
||||
Local mDNS + wide-area DNS-SD setup.
|
||||
</Card>
|
||||
<Card title="Discovery overview" href="/gateway/discovery">
|
||||
How OpenClaw advertises and finds gateways.
|
||||
</Card>
|
||||
<Card title="Configuration" href="/gateway/configuration">
|
||||
Top-level gateway config keys.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## Run the Gateway
|
||||
|
||||
@@ -33,37 +36,79 @@ Foreground alias:
|
||||
openclaw gateway run
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- By default, the Gateway refuses to start unless `gateway.mode=local` is set in `~/.openclaw/openclaw.json`. Use `--allow-unconfigured` for ad-hoc/dev runs.
|
||||
- `openclaw onboard --mode local` and `openclaw setup` are expected to write `gateway.mode=local`. If the file exists but `gateway.mode` is missing, treat that as a broken or clobbered config and repair it instead of assuming local mode implicitly.
|
||||
- If the file exists and `gateway.mode` is missing, the Gateway treats that as suspicious config damage and refuses to “guess local” for you.
|
||||
- Binding beyond loopback without auth is blocked (safety guardrail).
|
||||
- `SIGUSR1` triggers an in-process restart when authorized (`commands.restart` is enabled by default; set `commands.restart: false` to block manual restart, while gateway tool/config apply/update remain allowed).
|
||||
- `SIGINT`/`SIGTERM` handlers stop the gateway process, but they don’t restore any custom terminal state. If you wrap the CLI with a TUI or raw-mode input, restore the terminal before exit.
|
||||
<AccordionGroup>
|
||||
<Accordion title="Startup behavior">
|
||||
- By default, the Gateway refuses to start unless `gateway.mode=local` is set in `~/.openclaw/openclaw.json`. Use `--allow-unconfigured` for ad-hoc/dev runs.
|
||||
- `openclaw onboard --mode local` and `openclaw setup` are expected to write `gateway.mode=local`. If the file exists but `gateway.mode` is missing, treat that as a broken or clobbered config and repair it instead of assuming local mode implicitly.
|
||||
- If the file exists and `gateway.mode` is missing, the Gateway treats that as suspicious config damage and refuses to "guess local" for you.
|
||||
- Binding beyond loopback without auth is blocked (safety guardrail).
|
||||
- `SIGUSR1` triggers an in-process restart when authorized (`commands.restart` is enabled by default; set `commands.restart: false` to block manual restart, while gateway tool/config apply/update remain allowed).
|
||||
- `SIGINT`/`SIGTERM` handlers stop the gateway process, but they don't restore any custom terminal state. If you wrap the CLI with a TUI or raw-mode input, restore the terminal before exit.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
### Options
|
||||
|
||||
- `--port <port>`: WebSocket port (default comes from config/env; usually `18789`).
|
||||
- `--bind <loopback|lan|tailnet|auto|custom>`: listener bind mode.
|
||||
- `--auth <token|password>`: auth mode override.
|
||||
- `--token <token>`: token override (also sets `OPENCLAW_GATEWAY_TOKEN` for the process).
|
||||
- `--password <password>`: password override. Warning: inline passwords can be exposed in local process listings.
|
||||
- `--password-file <path>`: read the gateway password from a file.
|
||||
- `--tailscale <off|serve|funnel>`: expose the Gateway via Tailscale.
|
||||
- `--tailscale-reset-on-exit`: reset Tailscale serve/funnel config on shutdown.
|
||||
- `--allow-unconfigured`: allow gateway start without `gateway.mode=local` in config. This bypasses the startup guard for ad-hoc/dev bootstrap only; it does not write or repair the config file.
|
||||
- `--dev`: create a dev config + workspace if missing (skips BOOTSTRAP.md).
|
||||
- `--reset`: reset dev config + credentials + sessions + workspace (requires `--dev`).
|
||||
- `--force`: kill any existing listener on the selected port before starting.
|
||||
- `--verbose`: verbose logs.
|
||||
- `--cli-backend-logs`: only show CLI backend logs in the console (and enable stdout/stderr).
|
||||
- `--ws-log <auto|full|compact>`: websocket log style (default `auto`).
|
||||
- `--compact`: alias for `--ws-log compact`.
|
||||
- `--raw-stream`: log raw model stream events to jsonl.
|
||||
- `--raw-stream-path <path>`: raw stream jsonl path.
|
||||
<ParamField path="--port <port>" type="number">
|
||||
WebSocket port (default comes from config/env; usually `18789`).
|
||||
</ParamField>
|
||||
<ParamField path="--bind <loopback|lan|tailnet|auto|custom>" type="string">
|
||||
Listener bind mode.
|
||||
</ParamField>
|
||||
<ParamField path="--auth <token|password>" type="string">
|
||||
Auth mode override.
|
||||
</ParamField>
|
||||
<ParamField path="--token <token>" type="string">
|
||||
Token override (also sets `OPENCLAW_GATEWAY_TOKEN` for the process).
|
||||
</ParamField>
|
||||
<ParamField path="--password <password>" type="string">
|
||||
Password override.
|
||||
</ParamField>
|
||||
<ParamField path="--password-file <path>" type="string">
|
||||
Read the gateway password from a file.
|
||||
</ParamField>
|
||||
<ParamField path="--tailscale <off|serve|funnel>" type="string">
|
||||
Expose the Gateway via Tailscale.
|
||||
</ParamField>
|
||||
<ParamField path="--tailscale-reset-on-exit" type="boolean">
|
||||
Reset Tailscale serve/funnel config on shutdown.
|
||||
</ParamField>
|
||||
<ParamField path="--allow-unconfigured" type="boolean">
|
||||
Allow gateway start without `gateway.mode=local` in config. Bypasses the startup guard for ad-hoc/dev bootstrap only; does not write or repair the config file.
|
||||
</ParamField>
|
||||
<ParamField path="--dev" type="boolean">
|
||||
Create a dev config + workspace if missing (skips BOOTSTRAP.md).
|
||||
</ParamField>
|
||||
<ParamField path="--reset" type="boolean">
|
||||
Reset dev config + credentials + sessions + workspace (requires `--dev`).
|
||||
</ParamField>
|
||||
<ParamField path="--force" type="boolean">
|
||||
Kill any existing listener on the selected port before starting.
|
||||
</ParamField>
|
||||
<ParamField path="--verbose" type="boolean">
|
||||
Verbose logs.
|
||||
</ParamField>
|
||||
<ParamField path="--cli-backend-logs" type="boolean">
|
||||
Only show CLI backend logs in the console (and enable stdout/stderr).
|
||||
</ParamField>
|
||||
<ParamField path="--ws-log <auto|full|compact>" type="string" default="auto">
|
||||
Websocket log style.
|
||||
</ParamField>
|
||||
<ParamField path="--compact" type="boolean">
|
||||
Alias for `--ws-log compact`.
|
||||
</ParamField>
|
||||
<ParamField path="--raw-stream" type="boolean">
|
||||
Log raw model stream events to jsonl.
|
||||
</ParamField>
|
||||
<ParamField path="--raw-stream-path <path>" type="string">
|
||||
Raw stream jsonl path.
|
||||
</ParamField>
|
||||
|
||||
Startup profiling:
|
||||
<Warning>
|
||||
Inline `--password` can be exposed in local process listings. Prefer `--password-file`, env, or a SecretRef-backed `gateway.auth.password`.
|
||||
</Warning>
|
||||
|
||||
### Startup profiling
|
||||
|
||||
- Set `OPENCLAW_GATEWAY_STARTUP_TRACE=1` to log phase timings during Gateway startup.
|
||||
- Run `pnpm test:startup:gateway -- --runs 5 --warmup 1` to benchmark Gateway startup. The benchmark records first process output, `/healthz`, `/readyz`, and startup trace timings.
|
||||
@@ -72,22 +117,24 @@ Startup profiling:
|
||||
|
||||
All query commands use WebSocket RPC.
|
||||
|
||||
Output modes:
|
||||
<Tabs>
|
||||
<Tab title="Output modes">
|
||||
- Default: human-readable (colored in TTY).
|
||||
- `--json`: machine-readable JSON (no styling/spinner).
|
||||
- `--no-color` (or `NO_COLOR=1`): disable ANSI while keeping human layout.
|
||||
</Tab>
|
||||
<Tab title="Shared options">
|
||||
- `--url <url>`: Gateway WebSocket URL.
|
||||
- `--token <token>`: Gateway token.
|
||||
- `--password <password>`: Gateway password.
|
||||
- `--timeout <ms>`: timeout/budget (varies per command).
|
||||
- `--expect-final`: wait for a "final" response (agent calls).
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
- Default: human-readable (colored in TTY).
|
||||
- `--json`: machine-readable JSON (no styling/spinner).
|
||||
- `--no-color` (or `NO_COLOR=1`): disable ANSI while keeping human layout.
|
||||
|
||||
Shared options (where supported):
|
||||
|
||||
- `--url <url>`: Gateway WebSocket URL.
|
||||
- `--token <token>`: Gateway token.
|
||||
- `--password <password>`: Gateway password.
|
||||
- `--timeout <ms>`: timeout/budget (varies per command).
|
||||
- `--expect-final`: wait for a “final” response (agent calls).
|
||||
|
||||
Note: when you set `--url`, the CLI does not fall back to config or environment credentials.
|
||||
Pass `--token` or `--password` explicitly. Missing explicit credentials is an error.
|
||||
<Note>
|
||||
When you set `--url`, the CLI does not fall back to config or environment credentials. Pass `--token` or `--password` explicitly. Missing explicit credentials is an error.
|
||||
</Note>
|
||||
|
||||
### `gateway health`
|
||||
|
||||
@@ -107,9 +154,9 @@ openclaw gateway usage-cost --days 7
|
||||
openclaw gateway usage-cost --json
|
||||
```
|
||||
|
||||
Options:
|
||||
|
||||
- `--days <days>`: number of days to include (default `30`).
|
||||
<ParamField path="--days <days>" type="number" default="30">
|
||||
Number of days to include.
|
||||
</ParamField>
|
||||
|
||||
### `gateway stability`
|
||||
|
||||
@@ -123,24 +170,35 @@ openclaw gateway stability --bundle latest --export
|
||||
openclaw gateway stability --json
|
||||
```
|
||||
|
||||
Options:
|
||||
<ParamField path="--limit <limit>" type="number" default="25">
|
||||
Maximum number of recent events to include (max `1000`).
|
||||
</ParamField>
|
||||
<ParamField path="--type <type>" type="string">
|
||||
Filter by diagnostic event type, such as `payload.large` or `diagnostic.memory.pressure`.
|
||||
</ParamField>
|
||||
<ParamField path="--since-seq <seq>" type="number">
|
||||
Include only events after a diagnostic sequence number.
|
||||
</ParamField>
|
||||
<ParamField path="--bundle [path]" type="string">
|
||||
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.
|
||||
</ParamField>
|
||||
<ParamField path="--export" type="boolean">
|
||||
Write a shareable support diagnostics zip instead of printing stability details.
|
||||
</ParamField>
|
||||
<ParamField path="--output <path>" type="string">
|
||||
Output path for `--export`.
|
||||
</ParamField>
|
||||
|
||||
- `--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:
|
||||
|
||||
- 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. Set `diagnostics.enabled: false` to disable the recorder entirely.
|
||||
- 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.
|
||||
<AccordionGroup>
|
||||
<Accordion title="Privacy and bundle behavior">
|
||||
- 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. Set `diagnostics.enabled: false` to disable the recorder entirely.
|
||||
- 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.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
### `gateway diagnostics export`
|
||||
|
||||
Write a local diagnostics zip that is designed to attach to bug reports.
|
||||
For the privacy model and bundle contents, see [Diagnostics Export](/gateway/diagnostics).
|
||||
Write a local diagnostics zip that is designed to attach to bug reports. For the privacy model and bundle contents, see [Diagnostics Export](/gateway/diagnostics).
|
||||
|
||||
```bash
|
||||
openclaw gateway diagnostics export
|
||||
@@ -148,17 +206,33 @@ 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.
|
||||
<ParamField path="--output <path>" type="string">
|
||||
Output zip path. Defaults to a support export under the state directory.
|
||||
</ParamField>
|
||||
<ParamField path="--log-lines <count>" type="number" default="5000">
|
||||
Maximum sanitized log lines to include.
|
||||
</ParamField>
|
||||
<ParamField path="--log-bytes <bytes>" type="number" default="1000000">
|
||||
Maximum log bytes to inspect.
|
||||
</ParamField>
|
||||
<ParamField path="--url <url>" type="string">
|
||||
Gateway WebSocket URL for the health snapshot.
|
||||
</ParamField>
|
||||
<ParamField path="--token <token>" type="string">
|
||||
Gateway token for the health snapshot.
|
||||
</ParamField>
|
||||
<ParamField path="--password <password>" type="string">
|
||||
Gateway password for the health snapshot.
|
||||
</ParamField>
|
||||
<ParamField path="--timeout <ms>" type="number" default="3000">
|
||||
Status/health snapshot timeout.
|
||||
</ParamField>
|
||||
<ParamField path="--no-stability-bundle" type="boolean">
|
||||
Skip persisted stability bundle lookup.
|
||||
</ParamField>
|
||||
<ParamField path="--json" type="boolean">
|
||||
Print the written path, size, and manifest as JSON.
|
||||
</ParamField>
|
||||
|
||||
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.
|
||||
|
||||
@@ -174,93 +248,113 @@ openclaw gateway status --json
|
||||
openclaw gateway status --require-rpc
|
||||
```
|
||||
|
||||
Options:
|
||||
<ParamField path="--url <url>" type="string">
|
||||
Add an explicit probe target. Configured remote + localhost are still probed.
|
||||
</ParamField>
|
||||
<ParamField path="--token <token>" type="string">
|
||||
Token auth for the probe.
|
||||
</ParamField>
|
||||
<ParamField path="--password <password>" type="string">
|
||||
Password auth for the probe.
|
||||
</ParamField>
|
||||
<ParamField path="--timeout <ms>" type="number" default="10000">
|
||||
Probe timeout.
|
||||
</ParamField>
|
||||
<ParamField path="--no-probe" type="boolean">
|
||||
Skip the connectivity probe (service-only view).
|
||||
</ParamField>
|
||||
<ParamField path="--deep" type="boolean">
|
||||
Scan system-level services too.
|
||||
</ParamField>
|
||||
<ParamField path="--require-rpc" type="boolean">
|
||||
Upgrade the default connectivity probe to a read probe and exit non-zero when that read probe fails. Cannot be combined with `--no-probe`.
|
||||
</ParamField>
|
||||
|
||||
- `--url <url>`: add an explicit probe target. Configured remote + localhost are still probed.
|
||||
- `--token <token>`: token auth for the probe.
|
||||
- `--password <password>`: password auth for the probe.
|
||||
- `--timeout <ms>`: probe timeout (default `10000`).
|
||||
- `--no-probe`: skip the connectivity probe (service-only view).
|
||||
- `--deep`: scan system-level services too.
|
||||
- `--require-rpc`: upgrade the default connectivity probe to a read probe and exit non-zero when that read probe fails. Cannot be combined with `--no-probe`.
|
||||
|
||||
Notes:
|
||||
|
||||
- `gateway status` stays available for diagnostics even when the local CLI config is missing or invalid.
|
||||
- Default `gateway status` proves service state, WebSocket connect, and the auth capability visible at handshake time. It does not prove read/write/admin operations.
|
||||
- Diagnostic probes are non-mutating for first-time device auth: they reuse an
|
||||
existing cached device token when one exists, but they do not create a new CLI
|
||||
device identity or read-only device pairing record just to check status.
|
||||
- `gateway status` resolves configured auth SecretRefs for probe auth when possible.
|
||||
- If a required auth SecretRef is unresolved in this command path, `gateway status --json` reports `rpc.authWarning` when probe connectivity/auth fails; pass `--token`/`--password` explicitly or resolve the secret source first.
|
||||
- If the probe succeeds, unresolved auth-ref warnings are suppressed to avoid false positives.
|
||||
- Use `--require-rpc` in scripts and automation when a listening service is not enough and you need read-scope RPC calls to be healthy too.
|
||||
- `--deep` adds a best-effort scan for extra launchd/systemd/schtasks installs. When multiple gateway-like services are detected, human output prints cleanup hints and warns that most setups should run one gateway per machine.
|
||||
- Human output includes the resolved file log path plus the CLI-vs-service config paths/validity snapshot to help diagnose profile or state-dir drift.
|
||||
- On Linux systemd installs, service auth drift checks read both `Environment=` and `EnvironmentFile=` values from the unit (including `%h`, quoted paths, multiple files, and optional `-` files).
|
||||
- Drift checks resolve `gateway.auth.token` SecretRefs using merged runtime env (service command env first, then process env fallback).
|
||||
- If token auth is not effectively active (explicit `gateway.auth.mode` of `password`/`none`/`trusted-proxy`, or mode unset where password can win and no token candidate can win), token-drift checks skip config token resolution.
|
||||
<AccordionGroup>
|
||||
<Accordion title="Status semantics">
|
||||
- `gateway status` stays available for diagnostics even when the local CLI config is missing or invalid.
|
||||
- Default `gateway status` proves service state, WebSocket connect, and the auth capability visible at handshake time. It does not prove read/write/admin operations.
|
||||
- Diagnostic probes are non-mutating for first-time device auth: they reuse an existing cached device token when one exists, but they do not create a new CLI device identity or read-only device pairing record just to check status.
|
||||
- `gateway status` resolves configured auth SecretRefs for probe auth when possible.
|
||||
- If a required auth SecretRef is unresolved in this command path, `gateway status --json` reports `rpc.authWarning` when probe connectivity/auth fails; pass `--token`/`--password` explicitly or resolve the secret source first.
|
||||
- If the probe succeeds, unresolved auth-ref warnings are suppressed to avoid false positives.
|
||||
- Use `--require-rpc` in scripts and automation when a listening service is not enough and you need read-scope RPC calls to be healthy too.
|
||||
- `--deep` adds a best-effort scan for extra launchd/systemd/schtasks installs. When multiple gateway-like services are detected, human output prints cleanup hints and warns that most setups should run one gateway per machine.
|
||||
- Human output includes the resolved file log path plus the CLI-vs-service config paths/validity snapshot to help diagnose profile or state-dir drift.
|
||||
</Accordion>
|
||||
<Accordion title="Linux systemd auth-drift checks">
|
||||
- On Linux systemd installs, service auth drift checks read both `Environment=` and `EnvironmentFile=` values from the unit (including `%h`, quoted paths, multiple files, and optional `-` files).
|
||||
- Drift checks resolve `gateway.auth.token` SecretRefs using merged runtime env (service command env first, then process env fallback).
|
||||
- If token auth is not effectively active (explicit `gateway.auth.mode` of `password`/`none`/`trusted-proxy`, or mode unset where password can win and no token candidate can win), token-drift checks skip config token resolution.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
### `gateway probe`
|
||||
|
||||
`gateway probe` is the “debug everything” command. It always probes:
|
||||
`gateway probe` is the "debug everything" command. It always probes:
|
||||
|
||||
- your configured remote gateway (if set), and
|
||||
- localhost (loopback) **even if remote is configured**.
|
||||
|
||||
If you pass `--url`, that explicit target is added ahead of both. Human output labels the
|
||||
targets as:
|
||||
If you pass `--url`, that explicit target is added ahead of both. Human output labels the targets as:
|
||||
|
||||
- `URL (explicit)`
|
||||
- `Remote (configured)` or `Remote (configured, inactive)`
|
||||
- `Local loopback`
|
||||
|
||||
<Note>
|
||||
If multiple gateways are reachable, it prints all of them. Multiple gateways are supported when you use isolated profiles/ports (e.g., a rescue bot), but most installs still run a single gateway.
|
||||
</Note>
|
||||
|
||||
```bash
|
||||
openclaw gateway probe
|
||||
openclaw gateway probe --json
|
||||
```
|
||||
|
||||
Interpretation:
|
||||
<AccordionGroup>
|
||||
<Accordion title="Interpretation">
|
||||
- `Reachable: yes` means at least one target accepted a WebSocket connect.
|
||||
- `Capability: read-only|write-capable|admin-capable|pairing-pending|connect-only` reports what the probe could prove about auth. It is separate from reachability.
|
||||
- `Read probe: ok` means read-scope detail RPC calls (`health`/`status`/`system-presence`/`config.get`) also succeeded.
|
||||
- `Read probe: limited - missing scope: operator.read` means connect succeeded but read-scope RPC is limited. This is reported as **degraded** reachability, not full failure.
|
||||
- Like `gateway status`, probe reuses existing cached device auth but does not create first-time device identity or pairing state.
|
||||
- Exit code is non-zero only when no probed target is reachable.
|
||||
</Accordion>
|
||||
<Accordion title="JSON output">
|
||||
Top level:
|
||||
|
||||
- `Reachable: yes` means at least one target accepted a WebSocket connect.
|
||||
- `Capability: read-only|write-capable|admin-capable|pairing-pending|connect-only` reports what the probe could prove about auth. It is separate from reachability.
|
||||
- `Read probe: ok` means read-scope detail RPC calls (`health`/`status`/`system-presence`/`config.get`) also succeeded.
|
||||
- `Read probe: limited - missing scope: operator.read` means connect succeeded but read-scope RPC is limited. This is reported as **degraded** reachability, not full failure.
|
||||
- Like `gateway status`, probe reuses existing cached device auth but does not
|
||||
create first-time device identity or pairing state.
|
||||
- Exit code is non-zero only when no probed target is reachable.
|
||||
- `ok`: at least one target is reachable.
|
||||
- `degraded`: at least one target had scope-limited detail RPC.
|
||||
- `capability`: best capability seen across reachable targets (`read_only`, `write_capable`, `admin_capable`, `pairing_pending`, `connected_no_operator_scope`, or `unknown`).
|
||||
- `primaryTargetId`: best target to treat as the active winner in this order: explicit URL, SSH tunnel, configured remote, then local loopback.
|
||||
- `warnings[]`: best-effort warning records with `code`, `message`, and optional `targetIds`.
|
||||
- `network`: local loopback/tailnet URL hints derived from current config and host networking.
|
||||
- `discovery.timeoutMs` and `discovery.count`: the actual discovery budget/result count used for this probe pass.
|
||||
|
||||
JSON notes (`--json`):
|
||||
Per target (`targets[].connect`):
|
||||
|
||||
- Top level:
|
||||
- `ok`: at least one target is reachable.
|
||||
- `degraded`: at least one target had scope-limited detail RPC.
|
||||
- `capability`: best capability seen across reachable targets (`read_only`, `write_capable`, `admin_capable`, `pairing_pending`, `connected_no_operator_scope`, or `unknown`).
|
||||
- `primaryTargetId`: best target to treat as the active winner in this order: explicit URL, SSH tunnel, configured remote, then local loopback.
|
||||
- `warnings[]`: best-effort warning records with `code`, `message`, and optional `targetIds`.
|
||||
- `network`: local loopback/tailnet URL hints derived from current config and host networking.
|
||||
- `discovery.timeoutMs` and `discovery.count`: the actual discovery budget/result count used for this probe pass.
|
||||
- Per target (`targets[].connect`):
|
||||
- `ok`: reachability after connect + degraded classification.
|
||||
- `rpcOk`: full detail RPC success.
|
||||
- `scopeLimited`: detail RPC failed due to missing operator scope.
|
||||
- Per target (`targets[].auth`):
|
||||
- `role`: auth role reported in `hello-ok` when available.
|
||||
- `scopes`: granted scopes reported in `hello-ok` when available.
|
||||
- `capability`: the surfaced auth capability classification for that target.
|
||||
- `ok`: reachability after connect + degraded classification.
|
||||
- `rpcOk`: full detail RPC success.
|
||||
- `scopeLimited`: detail RPC failed due to missing operator scope.
|
||||
|
||||
Common warning codes:
|
||||
Per target (`targets[].auth`):
|
||||
|
||||
- `ssh_tunnel_failed`: SSH tunnel setup failed; the command fell back to direct probes.
|
||||
- `multiple_gateways`: more than one target was reachable; this is unusual unless you intentionally run isolated profiles, such as a rescue bot.
|
||||
- `auth_secretref_unresolved`: a configured auth SecretRef could not be resolved for a failed target.
|
||||
- `probe_scope_limited`: WebSocket connect succeeded, but the read probe was limited by missing `operator.read`.
|
||||
- `role`: auth role reported in `hello-ok` when available.
|
||||
- `scopes`: granted scopes reported in `hello-ok` when available.
|
||||
- `capability`: the surfaced auth capability classification for that target.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Common warning codes">
|
||||
- `ssh_tunnel_failed`: SSH tunnel setup failed; the command fell back to direct probes.
|
||||
- `multiple_gateways`: more than one target was reachable; this is unusual unless you intentionally run isolated profiles, such as a rescue bot.
|
||||
- `auth_secretref_unresolved`: a configured auth SecretRef could not be resolved for a failed target.
|
||||
- `probe_scope_limited`: WebSocket connect succeeded, but the read probe was limited by missing `operator.read`.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
#### Remote over SSH (Mac app parity)
|
||||
|
||||
The macOS app “Remote over SSH” mode uses a local port-forward so the remote gateway (which may be bound to loopback only) becomes reachable at `ws://127.0.0.1:<port>`.
|
||||
The macOS app "Remote over SSH" mode uses a local port-forward so the remote gateway (which may be bound to loopback only) becomes reachable at `ws://127.0.0.1:<port>`.
|
||||
|
||||
CLI equivalent:
|
||||
|
||||
@@ -268,13 +362,15 @@ CLI equivalent:
|
||||
openclaw gateway probe --ssh user@gateway-host
|
||||
```
|
||||
|
||||
Options:
|
||||
|
||||
- `--ssh <target>`: `user@host` or `user@host:port` (port defaults to `22`).
|
||||
- `--ssh-identity <path>`: identity file.
|
||||
- `--ssh-auto`: pick the first discovered gateway host as SSH target from the resolved
|
||||
discovery endpoint (`local.` plus the configured wide-area domain, if any). TXT-only
|
||||
hints are ignored.
|
||||
<ParamField path="--ssh <target>" type="string">
|
||||
`user@host` or `user@host:port` (port defaults to `22`).
|
||||
</ParamField>
|
||||
<ParamField path="--ssh-identity <path>" type="string">
|
||||
Identity file.
|
||||
</ParamField>
|
||||
<ParamField path="--ssh-auto" type="boolean">
|
||||
Pick the first discovered gateway host as SSH target from the resolved discovery endpoint (`local.` plus the configured wide-area domain, if any). TXT-only hints are ignored.
|
||||
</ParamField>
|
||||
|
||||
Config (optional, used as defaults):
|
||||
|
||||
@@ -290,20 +386,31 @@ openclaw gateway call status
|
||||
openclaw gateway call logs.tail --params '{"sinceMs": 60000}'
|
||||
```
|
||||
|
||||
Options:
|
||||
<ParamField path="--params <json>" type="string" default="{}">
|
||||
JSON object string for params.
|
||||
</ParamField>
|
||||
<ParamField path="--url <url>" type="string">
|
||||
Gateway WebSocket URL.
|
||||
</ParamField>
|
||||
<ParamField path="--token <token>" type="string">
|
||||
Gateway token.
|
||||
</ParamField>
|
||||
<ParamField path="--password <password>" type="string">
|
||||
Gateway password.
|
||||
</ParamField>
|
||||
<ParamField path="--timeout <ms>" type="number">
|
||||
Timeout budget.
|
||||
</ParamField>
|
||||
<ParamField path="--expect-final" type="boolean">
|
||||
Mainly for agent-style RPCs that stream intermediate events before a final payload.
|
||||
</ParamField>
|
||||
<ParamField path="--json" type="boolean">
|
||||
Machine-readable JSON output.
|
||||
</ParamField>
|
||||
|
||||
- `--params <json>`: JSON object string for params (default `{}`)
|
||||
- `--url <url>`
|
||||
- `--token <token>`
|
||||
- `--password <password>`
|
||||
- `--timeout <ms>`
|
||||
- `--expect-final`
|
||||
- `--json`
|
||||
|
||||
Notes:
|
||||
|
||||
- `--params` must be valid JSON.
|
||||
- `--expect-final` is mainly for agent-style RPCs that stream intermediate events before a final payload.
|
||||
<Note>
|
||||
`--params` must be valid JSON.
|
||||
</Note>
|
||||
|
||||
## Manage the Gateway service
|
||||
|
||||
@@ -315,29 +422,66 @@ openclaw gateway restart
|
||||
openclaw gateway uninstall
|
||||
```
|
||||
|
||||
Command options:
|
||||
### Install with a wrapper
|
||||
|
||||
- `gateway status`: `--url`, `--token`, `--password`, `--timeout`, `--no-probe`, `--require-rpc`, `--deep`, `--json`
|
||||
- `gateway install`: `--port`, `--runtime <node|bun>`, `--token`, `--force`, `--json`
|
||||
- `gateway uninstall|start|stop|restart`: `--json`
|
||||
Use `--wrapper` when the managed service must start through another executable, for example a
|
||||
secrets manager shim or a run-as helper. The wrapper receives the normal Gateway args and is
|
||||
responsible for eventually exec'ing `openclaw` or Node with those args.
|
||||
|
||||
Notes:
|
||||
```bash
|
||||
cat > ~/.local/bin/openclaw-doppler <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
exec doppler run --project my-project --config production -- openclaw "$@"
|
||||
EOF
|
||||
chmod +x ~/.local/bin/openclaw-doppler
|
||||
|
||||
- `gateway install` supports `--port`, `--runtime`, `--token`, `--force`, `--json`.
|
||||
- Use `gateway restart` to restart a managed service. Do not chain `gateway stop` and `gateway start` as a restart substitute; on macOS, `gateway stop` intentionally disables the LaunchAgent before stopping it.
|
||||
- When token auth requires a token and `gateway.auth.token` is SecretRef-managed, `gateway install` validates that the SecretRef is resolvable but does not persist the resolved token into service environment metadata.
|
||||
- If token auth requires a token and the configured token SecretRef is unresolved, install fails closed instead of persisting fallback plaintext.
|
||||
- For password auth on `gateway run`, prefer `OPENCLAW_GATEWAY_PASSWORD`, `--password-file`, or a SecretRef-backed `gateway.auth.password` over inline `--password`.
|
||||
- In inferred auth mode, shell-only `OPENCLAW_GATEWAY_PASSWORD` does not relax install token requirements; use durable config (`gateway.auth.password` or config `env`) when installing a managed service.
|
||||
- If both `gateway.auth.token` and `gateway.auth.password` are configured and `gateway.auth.mode` is unset, install is blocked until mode is set explicitly.
|
||||
- Lifecycle commands accept `--json` for scripting.
|
||||
openclaw gateway install --wrapper ~/.local/bin/openclaw-doppler --force
|
||||
openclaw gateway restart
|
||||
```
|
||||
|
||||
You can also set the wrapper through the environment. `gateway install` validates that the path is
|
||||
an executable file, writes the wrapper into service `ProgramArguments`, and persists
|
||||
`OPENCLAW_WRAPPER` in the service environment for later forced reinstalls, updates, and doctor
|
||||
repairs.
|
||||
|
||||
```bash
|
||||
OPENCLAW_WRAPPER="$HOME/.local/bin/openclaw-doppler" openclaw gateway install --force
|
||||
openclaw doctor
|
||||
```
|
||||
|
||||
To remove a persisted wrapper, clear `OPENCLAW_WRAPPER` while reinstalling:
|
||||
|
||||
```bash
|
||||
OPENCLAW_WRAPPER= openclaw gateway install --force
|
||||
openclaw gateway restart
|
||||
```
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Command options">
|
||||
- `gateway status`: `--url`, `--token`, `--password`, `--timeout`, `--no-probe`, `--require-rpc`, `--deep`, `--json`
|
||||
- `gateway install`: `--port`, `--runtime <node|bun>`, `--token`, `--wrapper <path>`, `--force`, `--json`
|
||||
- `gateway uninstall|start|stop|restart`: `--json`
|
||||
</Accordion>
|
||||
<Accordion title="Lifecycle behavior">
|
||||
- Use `gateway restart` to restart a managed service. Do not chain `gateway stop` and `gateway start` as a restart substitute; on macOS, `gateway stop` intentionally disables the LaunchAgent before stopping it.
|
||||
- Lifecycle commands accept `--json` for scripting.
|
||||
</Accordion>
|
||||
<Accordion title="Auth and SecretRefs at install time">
|
||||
- When token auth requires a token and `gateway.auth.token` is SecretRef-managed, `gateway install` validates that the SecretRef is resolvable but does not persist the resolved token into service environment metadata.
|
||||
- If token auth requires a token and the configured token SecretRef is unresolved, install fails closed instead of persisting fallback plaintext.
|
||||
- For password auth on `gateway run`, prefer `OPENCLAW_GATEWAY_PASSWORD`, `--password-file`, or a SecretRef-backed `gateway.auth.password` over inline `--password`.
|
||||
- In inferred auth mode, shell-only `OPENCLAW_GATEWAY_PASSWORD` does not relax install token requirements; use durable config (`gateway.auth.password` or config `env`) when installing a managed service.
|
||||
- If both `gateway.auth.token` and `gateway.auth.password` are configured and `gateway.auth.mode` is unset, install is blocked until mode is set explicitly.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Discover gateways (Bonjour)
|
||||
|
||||
`gateway discover` scans for Gateway beacons (`_openclaw-gw._tcp`).
|
||||
|
||||
- Multicast DNS-SD: `local.`
|
||||
- Unicast DNS-SD (Wide-Area Bonjour): choose a domain (example: `openclaw.internal.`) and set up split DNS + a DNS server; see [/gateway/bonjour](/gateway/bonjour)
|
||||
- Unicast DNS-SD (Wide-Area Bonjour): choose a domain (example: `openclaw.internal.`) and set up split DNS + a DNS server; see [Bonjour](/gateway/bonjour).
|
||||
|
||||
Only gateways with Bonjour discovery enabled (default) advertise the beacon.
|
||||
|
||||
@@ -357,10 +501,12 @@ Wide-Area discovery records include (TXT):
|
||||
openclaw gateway discover
|
||||
```
|
||||
|
||||
Options:
|
||||
|
||||
- `--timeout <ms>`: per-command timeout (browse/resolve); default `2000`.
|
||||
- `--json`: machine-readable output (also disables styling/spinner).
|
||||
<ParamField path="--timeout <ms>" type="number" default="2000">
|
||||
Per-command timeout (browse/resolve).
|
||||
</ParamField>
|
||||
<ParamField path="--json" type="boolean">
|
||||
Machine-readable output (also disables styling/spinner).
|
||||
</ParamField>
|
||||
|
||||
Examples:
|
||||
|
||||
@@ -369,14 +515,11 @@ openclaw gateway discover --timeout 4000
|
||||
openclaw gateway discover --json | jq '.beacons[].wsUrl'
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
<Note>
|
||||
- The CLI scans `local.` plus the configured wide-area domain when one is enabled.
|
||||
- `wsUrl` in JSON output is derived from the resolved service endpoint, not from TXT-only
|
||||
hints such as `lanHost` or `tailnetDns`.
|
||||
- On `local.` mDNS, `sshPort` and `cliPath` are only broadcast when
|
||||
`discovery.mdns.mode` is `full`. Wide-area DNS-SD still writes `cliPath`; `sshPort`
|
||||
stays optional there too.
|
||||
- `wsUrl` in JSON output is derived from the resolved service endpoint, not from TXT-only hints such as `lanHost` or `tailnetDns`.
|
||||
- On `local.` mDNS, `sshPort` and `cliPath` are only broadcast when `discovery.mdns.mode` is `full`. Wide-area DNS-SD still writes `cliPath`; `sshPort` stays optional there too.
|
||||
</Note>
|
||||
|
||||
## Related
|
||||
|
||||
|
||||
462
docs/cli/mcp.md
462
docs/cli/mcp.md
@@ -5,91 +5,89 @@ read_when:
|
||||
- Running `openclaw mcp serve`
|
||||
- Managing OpenClaw-saved MCP server definitions
|
||||
title: "MCP"
|
||||
sidebarTitle: "MCP"
|
||||
---
|
||||
|
||||
`openclaw mcp` has two jobs:
|
||||
|
||||
- run OpenClaw as an MCP server with `openclaw mcp serve`
|
||||
- manage OpenClaw-owned outbound MCP server definitions with `list`, `show`,
|
||||
`set`, and `unset`
|
||||
- manage OpenClaw-owned outbound MCP server definitions with `list`, `show`, `set`, and `unset`
|
||||
|
||||
In other words:
|
||||
|
||||
- `serve` is OpenClaw acting as an MCP server
|
||||
- `list` / `show` / `set` / `unset` is OpenClaw acting as an MCP client-side
|
||||
registry for other MCP servers its runtimes may consume later
|
||||
- `list` / `show` / `set` / `unset` is OpenClaw acting as an MCP client-side registry for other MCP servers its runtimes may consume later
|
||||
|
||||
Use [`openclaw acp`](/cli/acp) when OpenClaw should host a coding harness
|
||||
session itself and route that runtime through ACP.
|
||||
Use [`openclaw acp`](/cli/acp) when OpenClaw should host a coding harness session itself and route that runtime through ACP.
|
||||
|
||||
## OpenClaw as an MCP server
|
||||
|
||||
This is the `openclaw mcp serve` path.
|
||||
|
||||
## When to use `serve`
|
||||
### When to use `serve`
|
||||
|
||||
Use `openclaw mcp serve` when:
|
||||
|
||||
- Codex, Claude Code, or another MCP client should talk directly to
|
||||
OpenClaw-backed channel conversations
|
||||
- Codex, Claude Code, or another MCP client should talk directly to OpenClaw-backed channel conversations
|
||||
- you already have a local or remote OpenClaw Gateway with routed sessions
|
||||
- you want one MCP server that works across OpenClaw's channel backends instead
|
||||
of running separate per-channel bridges
|
||||
- you want one MCP server that works across OpenClaw's channel backends instead of running separate per-channel bridges
|
||||
|
||||
Use [`openclaw acp`](/cli/acp) instead when OpenClaw should host the coding
|
||||
runtime itself and keep the agent session inside OpenClaw.
|
||||
Use [`openclaw acp`](/cli/acp) instead when OpenClaw should host the coding runtime itself and keep the agent session inside OpenClaw.
|
||||
|
||||
## How it works
|
||||
### How it works
|
||||
|
||||
`openclaw mcp serve` starts a stdio MCP server. The MCP client owns that
|
||||
process. While the client keeps the stdio session open, the bridge connects to a
|
||||
local or remote OpenClaw Gateway over WebSocket and exposes routed channel
|
||||
conversations over MCP.
|
||||
`openclaw mcp serve` starts a stdio MCP server. The MCP client owns that process. While the client keeps the stdio session open, the bridge connects to a local or remote OpenClaw Gateway over WebSocket and exposes routed channel conversations over MCP.
|
||||
|
||||
Lifecycle:
|
||||
<Steps>
|
||||
<Step title="Client spawns the bridge">
|
||||
The MCP client spawns `openclaw mcp serve`.
|
||||
</Step>
|
||||
<Step title="Bridge connects to Gateway">
|
||||
The bridge connects to the OpenClaw Gateway over WebSocket.
|
||||
</Step>
|
||||
<Step title="Sessions become MCP conversations">
|
||||
Routed sessions become MCP conversations and transcript/history tools.
|
||||
</Step>
|
||||
<Step title="Live events queue">
|
||||
Live events are queued in memory while the bridge is connected.
|
||||
</Step>
|
||||
<Step title="Optional Claude push">
|
||||
If Claude channel mode is enabled, the same session can also receive Claude-specific push notifications.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
1. the MCP client spawns `openclaw mcp serve`
|
||||
2. the bridge connects to Gateway
|
||||
3. routed sessions become MCP conversations and transcript/history tools
|
||||
4. live events are queued in memory while the bridge is connected
|
||||
5. if Claude channel mode is enabled, the same session can also receive
|
||||
Claude-specific push notifications
|
||||
<AccordionGroup>
|
||||
<Accordion title="Important behavior">
|
||||
- live queue state starts when the bridge connects
|
||||
- older transcript history is read with `messages_read`
|
||||
- Claude push notifications only exist while the MCP session is alive
|
||||
- when the client disconnects, the bridge exits and the live queue is gone
|
||||
- one-shot agent entry points such as `openclaw agent` and `openclaw infer model run` retire any bundled MCP runtimes they open when the reply completes, so repeated scripted runs do not accumulate stdio MCP child processes
|
||||
- stdio MCP servers launched by OpenClaw (bundled or user-configured) are torn down as a process tree on shutdown, so child subprocesses started by the server do not survive after the parent stdio client exits
|
||||
- deleting or resetting a session disposes that session's MCP clients through the shared runtime cleanup path, so there are no lingering stdio connections tied to a removed session
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
Important behavior:
|
||||
|
||||
- live queue state starts when the bridge connects
|
||||
- older transcript history is read with `messages_read`
|
||||
- Claude push notifications only exist while the MCP session is alive
|
||||
- when the client disconnects, the bridge exits and the live queue is gone
|
||||
- one-shot agent entry points such as `openclaw agent` and
|
||||
`openclaw infer model run` retire any bundled MCP runtimes they open when the
|
||||
reply completes, so repeated scripted runs do not accumulate stdio MCP child
|
||||
processes
|
||||
- stdio MCP servers launched by OpenClaw (bundled or user-configured) are torn
|
||||
down as a process tree on shutdown, so child subprocesses started by the
|
||||
server do not survive after the parent stdio client exits
|
||||
- deleting or resetting a session disposes that session's MCP clients through
|
||||
the shared runtime cleanup path, so there are no lingering stdio connections
|
||||
tied to a removed session
|
||||
|
||||
## Choose a client mode
|
||||
### Choose a client mode
|
||||
|
||||
Use the same bridge in two different ways:
|
||||
|
||||
- Generic MCP clients: standard MCP tools only. Use `conversations_list`,
|
||||
`messages_read`, `events_poll`, `events_wait`, `messages_send`, and the
|
||||
approval tools.
|
||||
- Claude Code: standard MCP tools plus the Claude-specific channel adapter.
|
||||
Enable `--claude-channel-mode on` or leave the default `auto`.
|
||||
<Tabs>
|
||||
<Tab title="Generic MCP clients">
|
||||
Standard MCP tools only. Use `conversations_list`, `messages_read`, `events_poll`, `events_wait`, `messages_send`, and the approval tools.
|
||||
</Tab>
|
||||
<Tab title="Claude Code">
|
||||
Standard MCP tools plus the Claude-specific channel adapter. Enable `--claude-channel-mode on` or leave the default `auto`.
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
Today, `auto` behaves the same as `on`. There is no client capability detection
|
||||
yet.
|
||||
<Note>
|
||||
Today, `auto` behaves the same as `on`. There is no client capability detection yet.
|
||||
</Note>
|
||||
|
||||
## What `serve` exposes
|
||||
### What `serve` exposes
|
||||
|
||||
The bridge uses existing Gateway session route metadata to expose channel-backed
|
||||
conversations. A conversation appears when OpenClaw already has session state
|
||||
with a known route such as:
|
||||
The bridge uses existing Gateway session route metadata to expose channel-backed conversations. A conversation appears when OpenClaw already has session state with a known route such as:
|
||||
|
||||
- `channel`
|
||||
- recipient or destination metadata
|
||||
@@ -104,101 +102,91 @@ This gives MCP clients one place to:
|
||||
- send a reply back through the same route
|
||||
- see approval requests that arrive while the bridge is connected
|
||||
|
||||
## Usage
|
||||
### Usage
|
||||
|
||||
```bash
|
||||
# Local Gateway
|
||||
openclaw mcp serve
|
||||
<Tabs>
|
||||
<Tab title="Local Gateway">
|
||||
```bash
|
||||
openclaw mcp serve
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Remote Gateway (token)">
|
||||
```bash
|
||||
openclaw mcp serve --url wss://gateway-host:18789 --token-file ~/.openclaw/gateway.token
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Remote Gateway (password)">
|
||||
```bash
|
||||
openclaw mcp serve --url wss://gateway-host:18789 --password-file ~/.openclaw/gateway.password
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Verbose / Claude off">
|
||||
```bash
|
||||
openclaw mcp serve --verbose
|
||||
openclaw mcp serve --claude-channel-mode off
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
# Remote Gateway
|
||||
openclaw mcp serve --url wss://gateway-host:18789 --token-file ~/.openclaw/gateway.token
|
||||
|
||||
# Remote Gateway with password auth
|
||||
openclaw mcp serve --url wss://gateway-host:18789 --password-file ~/.openclaw/gateway.password
|
||||
|
||||
# Enable verbose bridge logs
|
||||
openclaw mcp serve --verbose
|
||||
|
||||
# Disable Claude-specific push notifications
|
||||
openclaw mcp serve --claude-channel-mode off
|
||||
```
|
||||
|
||||
## Bridge tools
|
||||
### Bridge tools
|
||||
|
||||
The current bridge exposes these MCP tools:
|
||||
|
||||
- `conversations_list`
|
||||
- `conversation_get`
|
||||
- `messages_read`
|
||||
- `attachments_fetch`
|
||||
- `events_poll`
|
||||
- `events_wait`
|
||||
- `messages_send`
|
||||
- `permissions_list_open`
|
||||
- `permissions_respond`
|
||||
<AccordionGroup>
|
||||
<Accordion title="conversations_list">
|
||||
Lists recent session-backed conversations that already have route metadata in Gateway session state.
|
||||
|
||||
### `conversations_list`
|
||||
Useful filters:
|
||||
|
||||
Lists recent session-backed conversations that already have route metadata in
|
||||
Gateway session state.
|
||||
- `limit`
|
||||
- `search`
|
||||
- `channel`
|
||||
- `includeDerivedTitles`
|
||||
- `includeLastMessage`
|
||||
|
||||
Useful filters:
|
||||
</Accordion>
|
||||
<Accordion title="conversation_get">
|
||||
Returns one conversation by `session_key`.
|
||||
</Accordion>
|
||||
<Accordion title="messages_read">
|
||||
Reads recent transcript messages for one session-backed conversation.
|
||||
</Accordion>
|
||||
<Accordion title="attachments_fetch">
|
||||
Extracts non-text message content blocks from one transcript message. This is a metadata view over transcript content, not a standalone durable attachment blob store.
|
||||
</Accordion>
|
||||
<Accordion title="events_poll">
|
||||
Reads queued live events since a numeric cursor.
|
||||
</Accordion>
|
||||
<Accordion title="events_wait">
|
||||
Long-polls until the next matching queued event arrives or a timeout expires.
|
||||
|
||||
- `limit`
|
||||
- `search`
|
||||
- `channel`
|
||||
- `includeDerivedTitles`
|
||||
- `includeLastMessage`
|
||||
Use this when a generic MCP client needs near-real-time delivery without a Claude-specific push protocol.
|
||||
|
||||
### `conversation_get`
|
||||
</Accordion>
|
||||
<Accordion title="messages_send">
|
||||
Sends text back through the same route already recorded on the session.
|
||||
|
||||
Returns one conversation by `session_key`.
|
||||
Current behavior:
|
||||
|
||||
### `messages_read`
|
||||
- requires an existing conversation route
|
||||
- uses the session's channel, recipient, account id, and thread id
|
||||
- sends text only
|
||||
|
||||
Reads recent transcript messages for one session-backed conversation.
|
||||
</Accordion>
|
||||
<Accordion title="permissions_list_open">
|
||||
Lists pending exec/plugin approval requests the bridge has observed since it connected to the Gateway.
|
||||
</Accordion>
|
||||
<Accordion title="permissions_respond">
|
||||
Resolves one pending exec/plugin approval request with:
|
||||
|
||||
### `attachments_fetch`
|
||||
- `allow-once`
|
||||
- `allow-always`
|
||||
- `deny`
|
||||
|
||||
Extracts non-text message content blocks from one transcript message. This is a
|
||||
metadata view over transcript content, not a standalone durable attachment blob
|
||||
store.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
### `events_poll`
|
||||
|
||||
Reads queued live events since a numeric cursor.
|
||||
|
||||
### `events_wait`
|
||||
|
||||
Long-polls until the next matching queued event arrives or a timeout expires.
|
||||
|
||||
Use this when a generic MCP client needs near-real-time delivery without a
|
||||
Claude-specific push protocol.
|
||||
|
||||
### `messages_send`
|
||||
|
||||
Sends text back through the same route already recorded on the session.
|
||||
|
||||
Current behavior:
|
||||
|
||||
- requires an existing conversation route
|
||||
- uses the session's channel, recipient, account id, and thread id
|
||||
- sends text only
|
||||
|
||||
### `permissions_list_open`
|
||||
|
||||
Lists pending exec/plugin approval requests the bridge has observed since it
|
||||
connected to the Gateway.
|
||||
|
||||
### `permissions_respond`
|
||||
|
||||
Resolves one pending exec/plugin approval request with:
|
||||
|
||||
- `allow-once`
|
||||
- `allow-always`
|
||||
- `deny`
|
||||
|
||||
## Event model
|
||||
### Event model
|
||||
|
||||
The bridge keeps an in-memory event queue while it is connected.
|
||||
|
||||
@@ -211,46 +199,43 @@ Current event types:
|
||||
- `plugin_approval_resolved`
|
||||
- `claude_permission_request`
|
||||
|
||||
Important limits:
|
||||
|
||||
<Warning>
|
||||
- the queue is live-only; it starts when the MCP bridge starts
|
||||
- `events_poll` and `events_wait` do not replay older Gateway history by
|
||||
themselves
|
||||
- `events_poll` and `events_wait` do not replay older Gateway history by themselves
|
||||
- durable backlog should be read with `messages_read`
|
||||
</Warning>
|
||||
|
||||
## Claude channel notifications
|
||||
### Claude channel notifications
|
||||
|
||||
The bridge can also expose Claude-specific channel notifications. This is the
|
||||
OpenClaw equivalent of a Claude Code channel adapter: standard MCP tools remain
|
||||
available, but live inbound messages can also arrive as Claude-specific MCP
|
||||
notifications.
|
||||
The bridge can also expose Claude-specific channel notifications. This is the OpenClaw equivalent of a Claude Code channel adapter: standard MCP tools remain available, but live inbound messages can also arrive as Claude-specific MCP notifications.
|
||||
|
||||
Flags:
|
||||
<Tabs>
|
||||
<Tab title="off">
|
||||
`--claude-channel-mode off`: standard MCP tools only.
|
||||
</Tab>
|
||||
<Tab title="on">
|
||||
`--claude-channel-mode on`: enable Claude channel notifications.
|
||||
</Tab>
|
||||
<Tab title="auto (default)">
|
||||
`--claude-channel-mode auto`: current default; same bridge behavior as `on`.
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
- `--claude-channel-mode off`: standard MCP tools only
|
||||
- `--claude-channel-mode on`: enable Claude channel notifications
|
||||
- `--claude-channel-mode auto`: current default; same bridge behavior as `on`
|
||||
|
||||
When Claude channel mode is enabled, the server advertises Claude experimental
|
||||
capabilities and can emit:
|
||||
When Claude channel mode is enabled, the server advertises Claude experimental capabilities and can emit:
|
||||
|
||||
- `notifications/claude/channel`
|
||||
- `notifications/claude/channel/permission`
|
||||
|
||||
Current bridge behavior:
|
||||
|
||||
- inbound `user` transcript messages are forwarded as
|
||||
`notifications/claude/channel`
|
||||
- inbound `user` transcript messages are forwarded as `notifications/claude/channel`
|
||||
- Claude permission requests received over MCP are tracked in-memory
|
||||
- if the linked conversation later sends `yes abcde` or `no abcde`, the bridge
|
||||
converts that to `notifications/claude/channel/permission`
|
||||
- these notifications are live-session only; if the MCP client disconnects,
|
||||
there is no push target
|
||||
- if the linked conversation later sends `yes abcde` or `no abcde`, the bridge converts that to `notifications/claude/channel/permission`
|
||||
- these notifications are live-session only; if the MCP client disconnects, there is no push target
|
||||
|
||||
This is intentionally client-specific. Generic MCP clients should rely on the
|
||||
standard polling tools.
|
||||
This is intentionally client-specific. Generic MCP clients should rely on the standard polling tools.
|
||||
|
||||
## MCP client config
|
||||
### MCP client config
|
||||
|
||||
Example stdio client config:
|
||||
|
||||
@@ -272,43 +257,52 @@ Example stdio client config:
|
||||
}
|
||||
```
|
||||
|
||||
For most generic MCP clients, start with the standard tool surface and ignore
|
||||
Claude mode. Turn Claude mode on only for clients that actually understand the
|
||||
Claude-specific notification methods.
|
||||
For most generic MCP clients, start with the standard tool surface and ignore Claude mode. Turn Claude mode on only for clients that actually understand the Claude-specific notification methods.
|
||||
|
||||
## Options
|
||||
### Options
|
||||
|
||||
`openclaw mcp serve` supports:
|
||||
|
||||
- `--url <url>`: Gateway WebSocket URL
|
||||
- `--token <token>`: Gateway token
|
||||
- `--token-file <path>`: read token from file
|
||||
- `--password <password>`: Gateway password
|
||||
- `--password-file <path>`: read password from file
|
||||
- `--claude-channel-mode <auto|on|off>`: Claude notification mode
|
||||
- `-v`, `--verbose`: verbose logs on stderr
|
||||
<ParamField path="--url" type="string">
|
||||
Gateway WebSocket URL.
|
||||
</ParamField>
|
||||
<ParamField path="--token" type="string">
|
||||
Gateway token.
|
||||
</ParamField>
|
||||
<ParamField path="--token-file" type="string">
|
||||
Read token from file.
|
||||
</ParamField>
|
||||
<ParamField path="--password" type="string">
|
||||
Gateway password.
|
||||
</ParamField>
|
||||
<ParamField path="--password-file" type="string">
|
||||
Read password from file.
|
||||
</ParamField>
|
||||
<ParamField path="--claude-channel-mode" type='"auto" | "on" | "off"'>
|
||||
Claude notification mode.
|
||||
</ParamField>
|
||||
<ParamField path="-v, --verbose" type="boolean">
|
||||
Verbose logs on stderr.
|
||||
</ParamField>
|
||||
|
||||
<Tip>
|
||||
Prefer `--token-file` or `--password-file` over inline secrets when possible.
|
||||
</Tip>
|
||||
|
||||
## Security and trust boundary
|
||||
### Security and trust boundary
|
||||
|
||||
The bridge does not invent routing. It only exposes conversations that Gateway
|
||||
already knows how to route.
|
||||
The bridge does not invent routing. It only exposes conversations that Gateway already knows how to route.
|
||||
|
||||
That means:
|
||||
|
||||
- sender allowlists, pairing, and channel-level trust still belong to the
|
||||
underlying OpenClaw channel configuration
|
||||
- sender allowlists, pairing, and channel-level trust still belong to the underlying OpenClaw channel configuration
|
||||
- `messages_send` can only reply through an existing stored route
|
||||
- approval state is live/in-memory only for the current bridge session
|
||||
- bridge auth should use the same Gateway token or password controls you would
|
||||
trust for any other remote Gateway client
|
||||
- bridge auth should use the same Gateway token or password controls you would trust for any other remote Gateway client
|
||||
|
||||
If a conversation is missing from `conversations_list`, the usual cause is not
|
||||
MCP configuration. It is missing or incomplete route metadata in the underlying
|
||||
Gateway session.
|
||||
If a conversation is missing from `conversations_list`, the usual cause is not MCP configuration. It is missing or incomplete route metadata in the underlying Gateway session.
|
||||
|
||||
## Testing
|
||||
### Testing
|
||||
|
||||
OpenClaw ships a deterministic Docker smoke for this bridge:
|
||||
|
||||
@@ -320,79 +314,60 @@ That smoke:
|
||||
|
||||
- starts a seeded Gateway container
|
||||
- starts a second container that spawns `openclaw mcp serve`
|
||||
- verifies conversation discovery, transcript reads, attachment metadata reads,
|
||||
live event queue behavior, and outbound send routing
|
||||
- validates Claude-style channel and permission notifications over the real
|
||||
stdio MCP bridge
|
||||
- verifies conversation discovery, transcript reads, attachment metadata reads, live event queue behavior, and outbound send routing
|
||||
- validates Claude-style channel and permission notifications over the real stdio MCP bridge
|
||||
|
||||
This is the fastest way to prove the bridge works without wiring a real
|
||||
Telegram, Discord, or iMessage account into the test run.
|
||||
This is the fastest way to prove the bridge works without wiring a real Telegram, Discord, or iMessage account into the test run.
|
||||
|
||||
For broader testing context, see [Testing](/help/testing).
|
||||
|
||||
## Troubleshooting
|
||||
### Troubleshooting
|
||||
|
||||
### No conversations returned
|
||||
<AccordionGroup>
|
||||
<Accordion title="No conversations returned">
|
||||
Usually means the Gateway session is not already routable. Confirm that the underlying session has stored channel/provider, recipient, and optional account/thread route metadata.
|
||||
</Accordion>
|
||||
<Accordion title="events_poll or events_wait misses older messages">
|
||||
Expected. The live queue starts when the bridge connects. Read older transcript history with `messages_read`.
|
||||
</Accordion>
|
||||
<Accordion title="Claude notifications do not show up">
|
||||
Check all of these:
|
||||
|
||||
Usually means the Gateway session is not already routable. Confirm that the
|
||||
underlying session has stored channel/provider, recipient, and optional
|
||||
account/thread route metadata.
|
||||
- the client kept the stdio MCP session open
|
||||
- `--claude-channel-mode` is `on` or `auto`
|
||||
- the client actually understands the Claude-specific notification methods
|
||||
- the inbound message happened after the bridge connected
|
||||
|
||||
### `events_poll` or `events_wait` misses older messages
|
||||
|
||||
Expected. The live queue starts when the bridge connects. Read older transcript
|
||||
history with `messages_read`.
|
||||
|
||||
### Claude notifications do not show up
|
||||
|
||||
Check all of these:
|
||||
|
||||
- the client kept the stdio MCP session open
|
||||
- `--claude-channel-mode` is `on` or `auto`
|
||||
- the client actually understands the Claude-specific notification methods
|
||||
- the inbound message happened after the bridge connected
|
||||
|
||||
### Approvals are missing
|
||||
|
||||
`permissions_list_open` only shows approval requests observed while the bridge
|
||||
was connected. It is not a durable approval history API.
|
||||
</Accordion>
|
||||
<Accordion title="Approvals are missing">
|
||||
`permissions_list_open` only shows approval requests observed while the bridge was connected. It is not a durable approval history API.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## OpenClaw as an MCP client registry
|
||||
|
||||
This is the `openclaw mcp list`, `show`, `set`, and `unset` path.
|
||||
|
||||
These commands do not expose OpenClaw over MCP. They manage OpenClaw-owned MCP
|
||||
server definitions under `mcp.servers` in OpenClaw config.
|
||||
These commands do not expose OpenClaw over MCP. They manage OpenClaw-owned MCP server definitions under `mcp.servers` in OpenClaw config.
|
||||
|
||||
Those saved definitions are for runtimes that OpenClaw launches or configures
|
||||
later, such as embedded Pi and other runtime adapters. OpenClaw stores the
|
||||
definitions centrally so those runtimes do not need to keep their own duplicate
|
||||
MCP server lists.
|
||||
Those saved definitions are for runtimes that OpenClaw launches or configures later, such as embedded Pi and other runtime adapters. OpenClaw stores the definitions centrally so those runtimes do not need to keep their own duplicate MCP server lists.
|
||||
|
||||
Important behavior:
|
||||
<AccordionGroup>
|
||||
<Accordion title="Important behavior">
|
||||
- these commands only read or write OpenClaw config
|
||||
- they do not connect to the target MCP server
|
||||
- they do not validate whether the command, URL, or remote transport is 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
|
||||
- session-scoped bundled MCP runtimes are reaped after `mcp.sessionIdleTtlMs` milliseconds of idle time (default 10 minutes; set `0` to disable) and one-shot embedded runs clean them up at run end
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
- these commands only read or write OpenClaw config
|
||||
- they do not connect to the target MCP server
|
||||
- they do not validate whether the command, URL, or remote transport is
|
||||
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
|
||||
- session-scoped bundled MCP runtimes are reaped after `mcp.sessionIdleTtlMs`
|
||||
milliseconds of idle time (default 10 minutes; set `0` to disable) and
|
||||
one-shot embedded runs clean them up at run end
|
||||
Runtime adapters may normalize this shared registry into the shape their downstream client expects. For example, embedded Pi consumes OpenClaw `transport` values directly, while Claude Code and Gemini receive CLI-native `type` values such as `http`, `sse`, or `stdio`.
|
||||
|
||||
Runtime adapters may normalize this shared registry into the shape their
|
||||
downstream client expects. For example, embedded Pi consumes OpenClaw
|
||||
`transport` values directly, while Claude Code and Gemini receive CLI-native
|
||||
`type` values such as `http`, `sse`, or `stdio`.
|
||||
### Saved MCP server definitions
|
||||
|
||||
## Saved MCP server definitions
|
||||
|
||||
OpenClaw also stores a lightweight MCP server registry in config for surfaces
|
||||
that want OpenClaw-managed MCP definitions.
|
||||
OpenClaw also stores a lightweight MCP server registry in config for surfaces that want OpenClaw-managed MCP definitions.
|
||||
|
||||
Commands:
|
||||
|
||||
@@ -447,11 +422,13 @@ Launches a local child process and communicates over stdin/stdout.
|
||||
| `env` | Extra environment variables |
|
||||
| `cwd` / `workingDirectory` | Working directory for the process |
|
||||
|
||||
#### Stdio env safety filter
|
||||
<Warning>
|
||||
**Stdio env safety filter**
|
||||
|
||||
OpenClaw rejects interpreter-startup env keys that can alter how a stdio MCP server starts up before the first RPC, even if they appear in a server's `env` block. Blocked keys include `NODE_OPTIONS`, `PYTHONSTARTUP`, `PYTHONPATH`, `PERL5OPT`, `RUBYOPT`, `SHELLOPTS`, `PS4`, and similar runtime-control variables. Startup rejects these with a configuration error so they cannot inject an implicit prelude, swap the interpreter, or enable a debugger against the stdio process. Ordinary credential, proxy, and server-specific env vars (`GITHUB_TOKEN`, `HTTP_PROXY`, custom `*_API_KEY`, etc.) are unaffected.
|
||||
|
||||
If your MCP server genuinely needs one of the blocked variables, set it on the gateway host process instead of under the stdio server's `env`.
|
||||
</Warning>
|
||||
|
||||
### SSE / HTTP transport
|
||||
|
||||
@@ -480,8 +457,7 @@ Example:
|
||||
}
|
||||
```
|
||||
|
||||
Sensitive values in `url` (userinfo) and `headers` are redacted in logs and
|
||||
status output.
|
||||
Sensitive values in `url` (userinfo) and `headers` are redacted in logs and status output.
|
||||
|
||||
### Streamable HTTP transport
|
||||
|
||||
@@ -513,8 +489,9 @@ Example:
|
||||
}
|
||||
```
|
||||
|
||||
These commands manage saved config only. They do not start the channel bridge,
|
||||
open a live MCP client session, or prove the target server is reachable.
|
||||
<Note>
|
||||
These commands manage saved config only. They do not start the channel bridge, open a live MCP client session, or prove the target server is reachable.
|
||||
</Note>
|
||||
|
||||
## Current limits
|
||||
|
||||
@@ -526,8 +503,7 @@ Current limits:
|
||||
- no generic push protocol beyond the Claude-specific adapter
|
||||
- no message edit or react tools yet
|
||||
- HTTP/SSE/streamable-http transport connects to a single remote server; no multiplexed upstream yet
|
||||
- `permissions_list_open` only includes approvals observed while the bridge is
|
||||
connected
|
||||
- `permissions_list_open` only includes approvals observed while the bridge is connected
|
||||
|
||||
## Related
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ Notes:
|
||||
stale removed-provider default.
|
||||
- `models status` may show `marker(<value>)` in auth output for non-secret placeholders (for example `OPENAI_API_KEY`, `secretref-managed`, `minimax-oauth`, `oauth:chutes`, `ollama-local`) instead of masking them as secrets.
|
||||
|
||||
### `models scan`
|
||||
### Models scan
|
||||
|
||||
`models scan` reads OpenRouter's public `:free` catalog and ranks candidates for
|
||||
fallback use. The catalog itself is public, so metadata-only scans do not need
|
||||
@@ -96,7 +96,7 @@ Options:
|
||||
`--set-default` and `--set-image` require live probes; metadata-only scan
|
||||
results are informational and are not applied to config.
|
||||
|
||||
### `models status`
|
||||
### Models status
|
||||
|
||||
Options:
|
||||
|
||||
|
||||
@@ -114,6 +114,12 @@ Use `openclaw node run` for a foreground node host (no service).
|
||||
|
||||
Service commands accept `--json` for machine-readable output.
|
||||
|
||||
The node host retries Gateway restart and network closes in-process. If the
|
||||
Gateway reports a terminal token/password/bootstrap auth pause, the node host
|
||||
logs the close detail and exits non-zero so launchd/systemd can restart it with
|
||||
fresh config and credentials. Pairing-required pauses stay in the foreground
|
||||
flow so the pending request can be approved.
|
||||
|
||||
## Pairing
|
||||
|
||||
The first connection creates a pending device pairing request (`role: node`) on the Gateway.
|
||||
|
||||
@@ -11,11 +11,23 @@ Interactive onboarding for local or remote Gateway setup.
|
||||
|
||||
## Related guides
|
||||
|
||||
- CLI onboarding hub: [Onboarding (CLI)](/start/wizard)
|
||||
- Onboarding overview: [Onboarding Overview](/start/onboarding-overview)
|
||||
- CLI onboarding reference: [CLI Setup Reference](/start/wizard-cli-reference)
|
||||
- CLI automation: [CLI Automation](/start/wizard-cli-automation)
|
||||
- macOS onboarding: [Onboarding (macOS App)](/start/onboarding)
|
||||
<CardGroup cols={2}>
|
||||
<Card title="CLI onboarding hub" href="/start/wizard" icon="rocket">
|
||||
Walkthrough of the interactive CLI flow.
|
||||
</Card>
|
||||
<Card title="Onboarding overview" href="/start/onboarding-overview" icon="map">
|
||||
How OpenClaw onboarding fits together.
|
||||
</Card>
|
||||
<Card title="CLI setup reference" href="/start/wizard-cli-reference" icon="book">
|
||||
Outputs, internals, and per-step behavior.
|
||||
</Card>
|
||||
<Card title="CLI automation" href="/start/wizard-cli-automation" icon="terminal">
|
||||
Non-interactive flags and scripted setups.
|
||||
</Card>
|
||||
<Card title="macOS app onboarding" href="/start/onboarding" icon="apple">
|
||||
Onboarding flow for the macOS menu bar app.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -132,10 +144,11 @@ Interactive onboarding behavior with reference mode:
|
||||
- Onboarding performs a fast preflight validation before saving the ref.
|
||||
- If validation fails, onboarding shows the error and lets you retry.
|
||||
|
||||
Non-interactive Z.AI endpoint choices:
|
||||
### Non-interactive Z.AI endpoint choices
|
||||
|
||||
Note: `--auth-choice zai-api-key` now auto-detects the best Z.AI endpoint for your key (prefers the general API with `zai/glm-5.1`).
|
||||
If you specifically want the GLM Coding Plan endpoints, pick `zai-coding-global` or `zai-coding-cn`.
|
||||
<Note>
|
||||
`--auth-choice zai-api-key` auto-detects the best Z.AI endpoint for your key (prefers the general API with `zai/glm-5.1`). If you specifically want the GLM Coding Plan endpoints, pick `zai-coding-global` or `zai-coding-cn`.
|
||||
</Note>
|
||||
|
||||
```bash
|
||||
# Promptless endpoint selection
|
||||
@@ -157,26 +170,32 @@ openclaw onboard --non-interactive \
|
||||
--mistral-api-key "$MISTRAL_API_KEY"
|
||||
```
|
||||
|
||||
Flow notes:
|
||||
## Flow notes
|
||||
|
||||
- `quickstart`: minimal prompts, auto-generates a gateway token.
|
||||
- `manual`: full prompts for port/bind/auth (alias of `advanced`).
|
||||
- When an auth choice implies a preferred provider, onboarding prefilters the
|
||||
default-model and allowlist pickers to that provider. For Volcengine and
|
||||
BytePlus, this also matches the coding-plan variants
|
||||
(`volcengine-plan/*`, `byteplus-plan/*`).
|
||||
- If the preferred-provider filter yields no loaded models yet, onboarding
|
||||
falls back to the unfiltered catalog instead of leaving the picker empty.
|
||||
- In the web-search step, some providers can trigger provider-specific
|
||||
follow-up prompts:
|
||||
- **Grok** can offer optional `x_search` setup with the same `XAI_API_KEY`
|
||||
and an `x_search` model choice.
|
||||
- **Kimi** can ask for the Moonshot API region (`api.moonshot.ai` vs
|
||||
`api.moonshot.cn`) and the default Kimi web-search model.
|
||||
- Local onboarding DM scope behavior: [CLI Setup Reference](/start/wizard-cli-reference#outputs-and-internals).
|
||||
- Fastest first chat: `openclaw dashboard` (Control UI, no channel setup).
|
||||
- Custom Provider: connect any OpenAI or Anthropic compatible endpoint,
|
||||
including hosted providers not listed. Use Unknown to auto-detect.
|
||||
<AccordionGroup>
|
||||
<Accordion title="Flow types">
|
||||
- `quickstart`: minimal prompts, auto-generates a gateway token.
|
||||
- `manual`: full prompts for port, bind, and auth (alias of `advanced`).
|
||||
</Accordion>
|
||||
<Accordion title="Provider prefiltering">
|
||||
When an auth choice implies a preferred provider, onboarding prefilters the default-model and allowlist pickers to that provider. For Volcengine and BytePlus, this also matches the coding-plan variants (`volcengine-plan/*`, `byteplus-plan/*`).
|
||||
|
||||
If the preferred-provider filter yields no loaded models yet, onboarding falls back to the unfiltered catalog instead of leaving the picker empty.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Web-search follow-ups">
|
||||
Some web-search providers trigger provider-specific follow-up prompts:
|
||||
|
||||
- **Grok** can offer optional `x_search` setup with the same `XAI_API_KEY` and an `x_search` model choice.
|
||||
- **Kimi** can ask for the Moonshot API region (`api.moonshot.ai` vs `api.moonshot.cn`) and the default Kimi web-search model.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Other behaviors">
|
||||
- Local onboarding DM scope behavior: [CLI setup reference](/start/wizard-cli-reference#outputs-and-internals).
|
||||
- Fastest first chat: `openclaw dashboard` (Control UI, no channel setup).
|
||||
- Custom provider: connect any OpenAI or Anthropic compatible endpoint, including hosted providers not listed. Use Unknown to auto-detect.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Common follow-up commands
|
||||
|
||||
|
||||
@@ -4,18 +4,25 @@ read_when:
|
||||
- You want to install or manage Gateway plugins or compatible bundles
|
||||
- You want to debug plugin load failures
|
||||
title: "Plugins"
|
||||
sidebarTitle: "Plugins"
|
||||
---
|
||||
|
||||
# `openclaw plugins`
|
||||
|
||||
Manage Gateway plugins, hook packs, and compatible bundles.
|
||||
|
||||
Related:
|
||||
|
||||
- Plugin system: [Plugins](/tools/plugin)
|
||||
- Bundle compatibility: [Plugin bundles](/plugins/bundles)
|
||||
- Plugin manifest + schema: [Plugin manifest](/plugins/manifest)
|
||||
- Security hardening: [Security](/gateway/security)
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Plugin system" href="/tools/plugin">
|
||||
End-user guide for installing, enabling, and troubleshooting plugins.
|
||||
</Card>
|
||||
<Card title="Plugin bundles" href="/plugins/bundles">
|
||||
Bundle compatibility model.
|
||||
</Card>
|
||||
<Card title="Plugin manifest" href="/plugins/manifest">
|
||||
Manifest fields and config schema.
|
||||
</Card>
|
||||
<Card title="Security" href="/gateway/security">
|
||||
Security hardening for plugin installs.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## Commands
|
||||
|
||||
@@ -41,17 +48,13 @@ openclaw plugins marketplace list <marketplace>
|
||||
openclaw plugins marketplace list <marketplace> --json
|
||||
```
|
||||
|
||||
Bundled plugins ship with OpenClaw. Some are enabled by default (for example
|
||||
bundled model providers, bundled speech providers, and the bundled browser
|
||||
plugin); others require `plugins enable`.
|
||||
<Note>
|
||||
Bundled plugins ship with OpenClaw. Some are enabled by default (for example bundled model providers, bundled speech providers, and the bundled browser plugin); others require `plugins enable`.
|
||||
|
||||
Native OpenClaw plugins must ship `openclaw.plugin.json` with an inline JSON
|
||||
Schema (`configSchema`, even if empty). Compatible bundles use their own bundle
|
||||
manifests instead.
|
||||
Native OpenClaw plugins must ship `openclaw.plugin.json` with an inline JSON Schema (`configSchema`, even if empty). Compatible bundles use their own bundle manifests instead.
|
||||
|
||||
`plugins list` shows `Format: openclaw` or `Format: bundle`. Verbose list/info
|
||||
output also shows the bundle subtype (`codex`, `claude`, or `cursor`) plus detected bundle
|
||||
capabilities.
|
||||
`plugins list` shows `Format: openclaw` or `Format: bundle`. Verbose list/info output also shows the bundle subtype (`codex`, `claude`, or `cursor`) plus detected bundle capabilities.
|
||||
</Note>
|
||||
|
||||
### Install
|
||||
|
||||
@@ -67,66 +70,49 @@ openclaw plugins install <plugin> --marketplace <name> # marketplace (explicit)
|
||||
openclaw plugins install <plugin> --marketplace https://github.com/<owner>/<repo>
|
||||
```
|
||||
|
||||
Bare package names are checked against ClawHub first, then npm. Security note:
|
||||
treat plugin installs like running code. Prefer pinned versions.
|
||||
<Warning>
|
||||
Bare package names are checked against ClawHub first, then npm. Treat plugin installs like running code. Prefer pinned versions.
|
||||
</Warning>
|
||||
|
||||
If your `plugins` section is backed by a single-file `$include`, `plugins install/update/enable/disable/uninstall` write through to that included file and leave `openclaw.json` untouched. Root includes, include arrays, and includes with sibling overrides fail closed instead of flattening. See [Config includes](/gateway/configuration) for the supported shapes.
|
||||
<AccordionGroup>
|
||||
<Accordion title="Config includes and invalid-config recovery">
|
||||
If your `plugins` section is backed by a single-file `$include`, `plugins install/update/enable/disable/uninstall` write through to that included file and leave `openclaw.json` untouched. Root includes, include arrays, and includes with sibling overrides fail closed instead of flattening. See [Config includes](/gateway/configuration) for the supported shapes.
|
||||
|
||||
If config is invalid, `plugins install` normally fails closed and tells you to
|
||||
run `openclaw doctor --fix` first. The only documented exception is a narrow
|
||||
bundled-plugin recovery path for plugins that explicitly opt into
|
||||
`openclaw.install.allowInvalidConfigRecovery`.
|
||||
If config is invalid, `plugins install` normally fails closed and tells you to run `openclaw doctor --fix` first. The only documented exception is a narrow bundled-plugin recovery path for plugins that explicitly opt into `openclaw.install.allowInvalidConfigRecovery`.
|
||||
|
||||
`--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>`.
|
||||
</Accordion>
|
||||
<Accordion title="--force and reinstall vs update">
|
||||
`--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>`.
|
||||
|
||||
If you run `plugins install` for a plugin id that is already installed, OpenClaw
|
||||
stops and points you at `plugins update <id-or-npm-spec>` for a normal upgrade,
|
||||
or at `plugins install <package> --force` when you genuinely want to overwrite
|
||||
the current install from a different source.
|
||||
If you run `plugins install` for a plugin id that is already installed, OpenClaw stops and points you at `plugins update <id-or-npm-spec>` for a normal upgrade, or at `plugins install <package> --force` when you genuinely want to overwrite the current install from a different source.
|
||||
|
||||
`--pin` applies to npm installs only. It is not supported with `--marketplace`,
|
||||
because marketplace installs persist marketplace source metadata instead of an
|
||||
npm spec.
|
||||
</Accordion>
|
||||
<Accordion title="--pin scope">
|
||||
`--pin` applies to npm installs only. It is not supported with `--marketplace`, because marketplace installs persist marketplace source metadata instead of an npm spec.
|
||||
</Accordion>
|
||||
<Accordion title="--dangerously-force-unsafe-install">
|
||||
`--dangerously-force-unsafe-install` is a break-glass option for false positives in the built-in dangerous-code scanner. It allows the install to continue even when the built-in scanner reports `critical` findings, but it does **not** bypass plugin `before_install` hook policy blocks and does **not** bypass scan failures.
|
||||
|
||||
`--dangerously-force-unsafe-install` is a break-glass option for false positives
|
||||
in the built-in dangerous-code scanner. It allows the install to continue even
|
||||
when the built-in scanner reports `critical` findings, but it does **not**
|
||||
bypass plugin `before_install` hook policy blocks and does **not** bypass scan
|
||||
failures.
|
||||
This CLI flag applies to plugin install/update flows. Gateway-backed skill dependency installs use the matching `dangerouslyForceUnsafeInstall` request override, while `openclaw skills install` remains a separate ClawHub skill download/install flow.
|
||||
|
||||
This CLI flag applies to plugin install/update flows. Gateway-backed skill
|
||||
dependency installs use the matching `dangerouslyForceUnsafeInstall` request
|
||||
override, while `openclaw skills install` remains a separate ClawHub skill
|
||||
download/install flow.
|
||||
</Accordion>
|
||||
<Accordion title="Hook packs and npm specs">
|
||||
`plugins install` is also the install surface for hook packs that expose `openclaw.hooks` in `package.json`. Use `openclaw hooks` for filtered hook visibility and per-hook enablement, not package installation.
|
||||
|
||||
`plugins install` is also the install surface for hook packs that expose
|
||||
`openclaw.hooks` in `package.json`. Use `openclaw hooks` for filtered hook
|
||||
visibility and per-hook enablement, not package installation.
|
||||
Npm specs are **registry-only** (package name + optional **exact version** or **dist-tag**). Git/URL/file specs and semver ranges are rejected. Dependency installs run project-local with `--ignore-scripts` for safety, even when your shell has global npm install settings.
|
||||
|
||||
Npm specs are **registry-only** (package name + optional **exact version** or
|
||||
**dist-tag**). Git/URL/file specs and semver ranges are rejected. Dependency
|
||||
installs run project-local with `--ignore-scripts` for safety, even when your
|
||||
shell has global npm install settings.
|
||||
Bare specs and `@latest` stay on the stable track. If npm resolves either of those to a prerelease, OpenClaw stops and asks you to opt in explicitly with a prerelease tag such as `@beta`/`@rc` or an exact prerelease version such as `@1.2.3-beta.4`.
|
||||
|
||||
Bare specs and `@latest` stay on the stable track. If npm resolves either of
|
||||
those to a prerelease, OpenClaw stops and asks you to opt in explicitly with a
|
||||
prerelease tag such as `@beta`/`@rc` or an exact prerelease version such as
|
||||
`@1.2.3-beta.4`.
|
||||
If a bare install spec matches a bundled plugin id (for example `diffs`), OpenClaw installs the bundled plugin directly. To install an npm package with the same name, use an explicit scoped spec (for example `@scope/diffs`).
|
||||
|
||||
If a bare install spec matches a bundled plugin id (for example `diffs`), OpenClaw
|
||||
installs the bundled plugin directly. To install an npm package with the same
|
||||
name, use an explicit scoped spec (for example `@scope/diffs`).
|
||||
</Accordion>
|
||||
<Accordion title="Archives">
|
||||
Supported archives: `.zip`, `.tgz`, `.tar.gz`, `.tar`. Native OpenClaw plugin archives must contain a valid `openclaw.plugin.json` at the extracted plugin root; archives that only contain `package.json` are rejected before OpenClaw writes install records.
|
||||
|
||||
Supported archives: `.zip`, `.tgz`, `.tar.gz`, `.tar`.
|
||||
Native OpenClaw plugin archives must contain a valid `openclaw.plugin.json` at
|
||||
the extracted plugin root; archives that only contain `package.json` are
|
||||
rejected before OpenClaw writes install records.
|
||||
Claude marketplace installs are also supported.
|
||||
|
||||
Claude marketplace installs are also supported.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
ClawHub installs use an explicit `clawhub:<package>` locator:
|
||||
|
||||
@@ -135,20 +121,17 @@ openclaw plugins install clawhub:openclaw-codex-app-server
|
||||
openclaw plugins install clawhub:openclaw-codex-app-server@1.2.3
|
||||
```
|
||||
|
||||
OpenClaw now also prefers ClawHub for bare npm-safe plugin specs. It only falls
|
||||
back to npm if ClawHub does not have that package or version:
|
||||
OpenClaw now also prefers ClawHub for bare npm-safe plugin specs. It only falls back to npm if ClawHub does not have that package or version:
|
||||
|
||||
```bash
|
||||
openclaw plugins install openclaw-codex-app-server
|
||||
```
|
||||
|
||||
OpenClaw downloads the package archive from ClawHub, checks the advertised
|
||||
plugin API / minimum gateway compatibility, then installs it through the normal
|
||||
archive path. Recorded installs keep their ClawHub source metadata for later
|
||||
updates.
|
||||
OpenClaw downloads the package archive from ClawHub, checks the advertised plugin API / minimum gateway compatibility, then installs it through the normal archive path. Recorded installs keep their ClawHub source metadata for later updates.
|
||||
|
||||
Use `plugin@marketplace` shorthand when the marketplace name exists in Claude's
|
||||
local registry cache at `~/.claude/plugins/known_marketplaces.json`:
|
||||
#### Marketplace shorthand
|
||||
|
||||
Use `plugin@marketplace` shorthand when the marketplace name exists in Claude's local registry cache at `~/.claude/plugins/known_marketplaces.json`:
|
||||
|
||||
```bash
|
||||
openclaw plugins marketplace list <marketplace-name>
|
||||
@@ -164,33 +147,29 @@ openclaw plugins install <plugin-name> --marketplace https://github.com/<owner>/
|
||||
openclaw plugins install <plugin-name> --marketplace ./my-marketplace
|
||||
```
|
||||
|
||||
Marketplace sources can be:
|
||||
|
||||
- a Claude known-marketplace name from `~/.claude/plugins/known_marketplaces.json`
|
||||
- a local marketplace root or `marketplace.json` path
|
||||
- a GitHub repo shorthand such as `owner/repo`
|
||||
- a GitHub repo URL such as `https://github.com/owner/repo`
|
||||
- a git URL
|
||||
|
||||
For remote marketplaces loaded from GitHub or git, plugin entries must stay
|
||||
inside the cloned marketplace repo. OpenClaw accepts relative path sources from
|
||||
that repo and rejects HTTP(S), absolute-path, git, GitHub, and other non-path
|
||||
plugin sources from remote manifests.
|
||||
<Tabs>
|
||||
<Tab title="Marketplace sources">
|
||||
- a Claude known-marketplace name from `~/.claude/plugins/known_marketplaces.json`
|
||||
- a local marketplace root or `marketplace.json` path
|
||||
- a GitHub repo shorthand such as `owner/repo`
|
||||
- a GitHub repo URL such as `https://github.com/owner/repo`
|
||||
- a git URL
|
||||
</Tab>
|
||||
<Tab title="Remote marketplace rules">
|
||||
For remote marketplaces loaded from GitHub or git, plugin entries must stay inside the cloned marketplace repo. OpenClaw accepts relative path sources from that repo and rejects HTTP(S), absolute-path, git, GitHub, and other non-path plugin sources from remote manifests.
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
For local paths and archives, OpenClaw auto-detects:
|
||||
|
||||
- native OpenClaw plugins (`openclaw.plugin.json`)
|
||||
- Codex-compatible bundles (`.codex-plugin/plugin.json`)
|
||||
- Claude-compatible bundles (`.claude-plugin/plugin.json` or the default Claude
|
||||
component layout)
|
||||
- Claude-compatible bundles (`.claude-plugin/plugin.json` or the default Claude component layout)
|
||||
- Cursor-compatible bundles (`.cursor-plugin/plugin.json`)
|
||||
|
||||
Compatible bundles install into the normal plugin root and participate in
|
||||
the same list/info/enable/disable flow. Today, bundle skills, Claude
|
||||
command-skills, Claude `settings.json` defaults, Claude `.lsp.json` /
|
||||
manifest-declared `lspServers` defaults, Cursor command-skills, and compatible
|
||||
Codex hook directories are supported; other detected bundle capabilities are
|
||||
shown in diagnostics/info but are not yet wired into runtime execution.
|
||||
<Note>
|
||||
Compatible bundles install into the normal plugin root and participate in the same list/info/enable/disable flow. Today, bundle skills, Claude command-skills, Claude `settings.json` defaults, Claude `.lsp.json` / manifest-declared `lspServers` defaults, Cursor command-skills, and compatible Codex hook directories are supported; other detected bundle capabilities are shown in diagnostics/info but are not yet wired into runtime execution.
|
||||
</Note>
|
||||
|
||||
### List
|
||||
|
||||
@@ -201,30 +180,31 @@ openclaw plugins list --verbose
|
||||
openclaw plugins list --json
|
||||
```
|
||||
|
||||
Use `--enabled` to show only enabled plugins. Use `--verbose` to switch from the
|
||||
table view to per-plugin detail lines with source/origin/version/activation
|
||||
metadata. Use `--json` for machine-readable inventory plus registry
|
||||
diagnostics.
|
||||
<ParamField path="--enabled" type="boolean">
|
||||
Show only enabled plugins.
|
||||
</ParamField>
|
||||
<ParamField path="--verbose" type="boolean">
|
||||
Switch from the table view to per-plugin detail lines with source/origin/version/activation metadata.
|
||||
</ParamField>
|
||||
<ParamField path="--json" type="boolean">
|
||||
Machine-readable inventory plus registry diagnostics.
|
||||
</ParamField>
|
||||
|
||||
`plugins list` reads the persisted local plugin registry first, with a
|
||||
manifest-only derived fallback when the registry is missing or invalid. It is
|
||||
useful for checking whether a plugin is installed, enabled, and visible to cold
|
||||
startup planning, but it is not a live runtime probe of an already-running
|
||||
Gateway process. After changing plugin code, enablement, hook policy, or
|
||||
`plugins.load.paths`, restart the Gateway that serves the channel before
|
||||
expecting new `register(api)` code or hooks to run. For remote/container
|
||||
deployments, verify you are restarting the actual `openclaw gateway run` child,
|
||||
not only a wrapper process.
|
||||
<Note>
|
||||
`plugins list` reads the persisted local plugin registry first, with a manifest-only derived fallback when the registry is missing or invalid. It is useful for checking whether a plugin is installed, enabled, and visible to cold startup planning, but it is not a live runtime probe of an already-running Gateway process. After changing plugin code, enablement, hook policy, or `plugins.load.paths`, restart the Gateway that serves the channel before expecting new `register(api)` code or hooks to run. For remote/container deployments, verify you are restarting the actual `openclaw gateway run` child, not only a wrapper process.
|
||||
</Note>
|
||||
|
||||
For bundled plugin work inside a packaged Docker image, bind-mount the plugin
|
||||
source directory over the matching packaged source path, such as
|
||||
`/app/extensions/synology-chat`. OpenClaw will discover that mounted source
|
||||
overlay before `/app/dist/extensions/synology-chat`; a plain copied source
|
||||
directory remains inert so normal packaged installs still use compiled dist.
|
||||
|
||||
For runtime hook debugging:
|
||||
|
||||
- `openclaw plugins inspect <id> --json` shows registered hooks and diagnostics
|
||||
from a module-loaded inspection pass.
|
||||
- `openclaw gateway status --deep --require-rpc` confirms the reachable Gateway,
|
||||
service/process hints, config path, and RPC health.
|
||||
- Non-bundled conversation hooks (`llm_input`, `llm_output`,
|
||||
`before_agent_finalize`, `agent_end`) require
|
||||
`plugins.entries.<id>.hooks.allowConversationAccess=true`.
|
||||
- `openclaw plugins inspect <id> --json` shows registered hooks and diagnostics from a module-loaded inspection pass.
|
||||
- `openclaw gateway status --deep --require-rpc` confirms the reachable Gateway, service/process hints, config path, and RPC health.
|
||||
- Non-bundled conversation hooks (`llm_input`, `llm_output`, `before_agent_finalize`, `agent_end`) require `plugins.entries.<id>.hooks.allowConversationAccess=true`.
|
||||
|
||||
Use `--link` to avoid copying a local directory (adds to `plugins.load.paths`):
|
||||
|
||||
@@ -232,24 +212,17 @@ Use `--link` to avoid copying a local directory (adds to `plugins.load.paths`):
|
||||
openclaw plugins install -l ./my-plugin
|
||||
```
|
||||
|
||||
`--force` is not supported with `--link` because linked installs reuse the
|
||||
source path instead of copying over a managed install target.
|
||||
<Note>
|
||||
`--force` is not supported with `--link` because linked installs reuse the source path instead of copying over a managed install target.
|
||||
|
||||
Use `--pin` on npm installs to save the resolved exact spec (`name@version`) in
|
||||
the managed plugin index while keeping the default behavior unpinned.
|
||||
Use `--pin` on npm installs to save the resolved exact spec (`name@version`) in the managed plugin index while keeping the default behavior unpinned.
|
||||
</Note>
|
||||
|
||||
### Plugin Index
|
||||
### Plugin index
|
||||
|
||||
Plugin install metadata is machine-managed state, not user config. Installs
|
||||
and updates write it to `plugins/installs.json` under the active OpenClaw state
|
||||
directory. Its top-level `installRecords` map is the durable source of install
|
||||
metadata, including records for broken or missing plugin manifests. The
|
||||
`plugins` array is the manifest-derived cold registry cache. The file includes a
|
||||
do-not-edit warning and is used by `openclaw plugins update`, uninstall,
|
||||
diagnostics, and the cold plugin registry.
|
||||
When OpenClaw sees shipped legacy `plugins.installs` records in config, it moves
|
||||
them into the plugin index and removes the config key; if either write fails,
|
||||
the config records are kept so the install metadata is not lost.
|
||||
Plugin install metadata is machine-managed state, not user config. Installs and updates write it to `plugins/installs.json` under the active OpenClaw state directory. Its top-level `installRecords` map is the durable source of install metadata, including records for broken or missing plugin manifests. The `plugins` array is the manifest-derived cold registry cache. The file includes a do-not-edit warning and is used by `openclaw plugins update`, uninstall, diagnostics, and the cold plugin registry.
|
||||
|
||||
When OpenClaw sees shipped legacy `plugins.installs` records in config, it moves them into the plugin index and removes the config key; if either write fails, the config records are kept so the install metadata is not lost.
|
||||
|
||||
### Uninstall
|
||||
|
||||
@@ -259,13 +232,11 @@ openclaw plugins uninstall <id> --dry-run
|
||||
openclaw plugins uninstall <id> --keep-files
|
||||
```
|
||||
|
||||
`uninstall` removes plugin records from `plugins.entries`, the persisted plugin
|
||||
index, the plugin allowlist, and linked `plugins.load.paths` entries when
|
||||
applicable. Unless `--keep-files` is set, uninstall also removes the tracked
|
||||
managed install directory when it is inside OpenClaw's plugin extensions root.
|
||||
For active memory plugins, the memory slot resets to `memory-core`.
|
||||
`uninstall` removes plugin records from `plugins.entries`, the persisted plugin index, plugin allow/deny list entries, and linked `plugins.load.paths` entries when applicable. Unless `--keep-files` is set, uninstall also removes the tracked managed install directory when it is inside OpenClaw's plugin extensions root. For active memory plugins, the memory slot resets to `memory-core`.
|
||||
|
||||
<Note>
|
||||
`--keep-config` is supported as a deprecated alias for `--keep-files`.
|
||||
</Note>
|
||||
|
||||
### Update
|
||||
|
||||
@@ -277,38 +248,27 @@ openclaw plugins update @openclaw/voice-call@beta
|
||||
openclaw plugins update openclaw-codex-app-server --dangerously-force-unsafe-install
|
||||
```
|
||||
|
||||
Updates apply to tracked plugin installs in the managed plugin index and
|
||||
tracked hook-pack installs in `hooks.internal.installs`.
|
||||
Updates apply to tracked plugin installs in the managed plugin index and tracked hook-pack installs in `hooks.internal.installs`.
|
||||
|
||||
When you pass a plugin id, OpenClaw reuses the recorded install spec for that
|
||||
plugin. That means previously stored dist-tags such as `@beta` and exact pinned
|
||||
versions continue to be used on later `update <id>` runs.
|
||||
<AccordionGroup>
|
||||
<Accordion title="Resolving plugin id vs npm spec">
|
||||
When you pass a plugin id, OpenClaw reuses the recorded install spec for that plugin. That means previously stored dist-tags such as `@beta` and exact pinned versions continue to be used on later `update <id>` runs.
|
||||
|
||||
For npm installs, you can also pass an explicit npm package spec with a dist-tag
|
||||
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.
|
||||
For npm installs, you can also pass an explicit npm package spec with a dist-tag 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.
|
||||
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`.
|
||||
</Accordion>
|
||||
<Accordion title="Version checks and integrity drift">
|
||||
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 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.
|
||||
When a stored integrity hash exists and the fetched artifact hash changes, 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
|
||||
plugin updates. It still does not bypass plugin `before_install` policy blocks
|
||||
or scan-failure blocking, and it only applies to plugin updates, not hook-pack
|
||||
updates.
|
||||
</Accordion>
|
||||
<Accordion title="--dangerously-force-unsafe-install on update">
|
||||
`--dangerously-force-unsafe-install` is also available on `plugins update` as a break-glass override for built-in dangerous-code scan false positives during plugin updates. It still does not bypass plugin `before_install` policy blocks or scan-failure blocking, and it only applies to plugin updates, not hook-pack updates.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
### Inspect
|
||||
|
||||
@@ -317,10 +277,7 @@ openclaw plugins inspect <id>
|
||||
openclaw plugins inspect <id> --json
|
||||
```
|
||||
|
||||
Deep introspection for a single plugin. Shows identity, load status, source,
|
||||
registered capabilities, hooks, tools, commands, services, gateway methods,
|
||||
HTTP routes, policy flags, diagnostics, install metadata, bundle capabilities,
|
||||
and any detected MCP or LSP server support.
|
||||
Deep introspection for a single plugin. Shows identity, load status, source, registered capabilities, hooks, tools, commands, services, gateway methods, HTTP routes, policy flags, diagnostics, install metadata, bundle capabilities, and any detected MCP or LSP server support.
|
||||
|
||||
Each plugin is classified by what it actually registers at runtime:
|
||||
|
||||
@@ -331,13 +288,9 @@ Each plugin is classified by what it actually registers at runtime:
|
||||
|
||||
See [Plugin shapes](/plugins/architecture#plugin-shapes) for more on the capability model.
|
||||
|
||||
The `--json` flag outputs a machine-readable report suitable for scripting and
|
||||
auditing.
|
||||
|
||||
`inspect --all` renders a fleet-wide table with shape, capability kinds,
|
||||
compatibility notices, bundle capabilities, and hook summary columns.
|
||||
|
||||
`info` is an alias for `inspect`.
|
||||
<Note>
|
||||
The `--json` flag outputs a machine-readable report suitable for scripting and auditing. `inspect --all` renders a fleet-wide table with shape, capability kinds, compatibility notices, bundle capabilities, and hook summary columns. `info` is an alias for `inspect`.
|
||||
</Note>
|
||||
|
||||
### Doctor
|
||||
|
||||
@@ -345,13 +298,9 @@ compatibility notices, bundle capabilities, and hook summary columns.
|
||||
openclaw plugins doctor
|
||||
```
|
||||
|
||||
`doctor` reports plugin load errors, manifest/discovery diagnostics, and
|
||||
compatibility notices. When everything is clean it prints `No plugin issues
|
||||
detected.`
|
||||
`doctor` reports plugin load errors, manifest/discovery diagnostics, and 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.
|
||||
For module-shape failures such as missing `register`/`activate` exports, rerun with `OPENCLAW_PLUGIN_LOAD_DEBUG=1` to include a compact export-shape summary in the diagnostic output.
|
||||
|
||||
### Registry
|
||||
|
||||
@@ -361,20 +310,13 @@ openclaw plugins registry --refresh
|
||||
openclaw plugins registry --json
|
||||
```
|
||||
|
||||
The local plugin registry is OpenClaw's persisted cold read model for installed
|
||||
plugin identity, enablement, source metadata, and contribution ownership.
|
||||
Normal startup, provider owner lookup, channel setup classification, and plugin
|
||||
inventory can read it without importing plugin runtime modules.
|
||||
The local plugin registry is OpenClaw's persisted cold read model for installed plugin identity, enablement, source metadata, and contribution ownership. Normal startup, provider owner lookup, channel setup classification, and plugin inventory can read it without importing plugin runtime modules.
|
||||
|
||||
Use `plugins registry` to inspect whether the persisted registry is present,
|
||||
current, or stale. Use `--refresh` to rebuild it from the persisted plugin
|
||||
index, config policy, and manifest/package metadata. This is a repair path, not
|
||||
a runtime activation path.
|
||||
Use `plugins registry` to inspect whether the persisted registry is present, current, or stale. Use `--refresh` to rebuild it from the persisted plugin index, config policy, and manifest/package metadata. This is a repair path, not a runtime activation path.
|
||||
|
||||
`OPENCLAW_DISABLE_PERSISTED_PLUGIN_REGISTRY=1` is a deprecated break-glass
|
||||
compatibility switch for registry read failures. Prefer `plugins registry
|
||||
--refresh` or `openclaw doctor --fix`; the env fallback is only for emergency
|
||||
startup recovery while the migration rolls out.
|
||||
<Warning>
|
||||
`OPENCLAW_DISABLE_PERSISTED_PLUGIN_REGISTRY=1` is a deprecated break-glass compatibility switch for registry read failures. Prefer `plugins registry --refresh` or `openclaw doctor --fix`; the env fallback is only for emergency startup recovery while the migration rolls out.
|
||||
</Warning>
|
||||
|
||||
### Marketplace
|
||||
|
||||
@@ -383,13 +325,10 @@ openclaw plugins marketplace list <source>
|
||||
openclaw plugins marketplace list <source> --json
|
||||
```
|
||||
|
||||
Marketplace list accepts a local marketplace path, a `marketplace.json` path, a
|
||||
GitHub shorthand like `owner/repo`, a GitHub repo URL, or a git URL. `--json`
|
||||
prints the resolved source label plus the parsed marketplace manifest and
|
||||
plugin entries.
|
||||
Marketplace list accepts a local marketplace path, a `marketplace.json` path, a GitHub shorthand like `owner/repo`, a GitHub repo URL, or a git URL. `--json` prints the resolved source label plus the parsed marketplace manifest and plugin entries.
|
||||
|
||||
## Related
|
||||
|
||||
- [CLI reference](/cli)
|
||||
- [Building plugins](/plugins/building-plugins)
|
||||
- [CLI reference](/cli)
|
||||
- [Community plugins](/plugins/community)
|
||||
|
||||
@@ -75,9 +75,11 @@ openclaw sandbox recreate --all --force # Skip confirmation
|
||||
- `--browser`: Only recreate browser containers
|
||||
- `--force`: Skip confirmation prompt
|
||||
|
||||
**Important:** Runtimes are automatically recreated when the agent is next used.
|
||||
<Note>
|
||||
Runtimes are automatically recreated when the agent is next used.
|
||||
</Note>
|
||||
|
||||
## Use Cases
|
||||
## Use cases
|
||||
|
||||
### After updating a Docker image
|
||||
|
||||
@@ -148,18 +150,19 @@ openclaw sandbox recreate --agent family
|
||||
openclaw sandbox recreate --agent alfred
|
||||
```
|
||||
|
||||
## Why is this needed?
|
||||
## Why this is needed
|
||||
|
||||
**Problem:** When you update sandbox configuration:
|
||||
When you update sandbox configuration:
|
||||
|
||||
- Existing runtimes continue running with old settings
|
||||
- Runtimes are only pruned after 24h of inactivity
|
||||
- Regularly-used agents keep old runtimes alive indefinitely
|
||||
- Existing runtimes continue running with old settings.
|
||||
- Runtimes are only pruned after 24h of inactivity.
|
||||
- Regularly-used agents keep old runtimes alive indefinitely.
|
||||
|
||||
**Solution:** Use `openclaw sandbox recreate` to force removal of old runtimes. They'll be recreated automatically with current settings when next needed.
|
||||
Use `openclaw sandbox recreate` to force removal of old runtimes. They are recreated automatically with current settings when next needed.
|
||||
|
||||
Tip: prefer `openclaw sandbox recreate` over manual backend-specific cleanup.
|
||||
It uses the Gateway’s runtime registry and avoids mismatches when scope/session keys change.
|
||||
<Tip>
|
||||
Prefer `openclaw sandbox recreate` over manual backend-specific cleanup. It uses the Gateway's runtime registry and avoids mismatches when scope or session keys change.
|
||||
</Tip>
|
||||
|
||||
## Configuration
|
||||
|
||||
@@ -193,4 +196,4 @@ Sandbox settings live in `~/.openclaw/openclaw.json` under `agents.defaults.sand
|
||||
- [CLI reference](/cli)
|
||||
- [Sandboxing](/gateway/sandboxing)
|
||||
- [Agent workspace](/concepts/agent-workspace)
|
||||
- [Doctor](/gateway/doctor) — checks sandbox setup
|
||||
- [Doctor](/gateway/doctor): checks sandbox setup.
|
||||
|
||||
@@ -39,10 +39,12 @@ openclaw --update
|
||||
- `--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)
|
||||
- `--timeout <seconds>`: per-step timeout (default is 1800s).
|
||||
- `--yes`: skip confirmation prompts (for example downgrade confirmation).
|
||||
|
||||
Note: downgrades require confirmation because older versions can break configuration.
|
||||
<Warning>
|
||||
Downgrades require confirmation because older versions can break configuration.
|
||||
</Warning>
|
||||
|
||||
## `update status`
|
||||
|
||||
@@ -67,7 +69,7 @@ offers to create one.
|
||||
|
||||
Options:
|
||||
|
||||
- `--timeout <seconds>`: timeout for each update step (default `1200`)
|
||||
- `--timeout <seconds>`: timeout for each update step (default `1800`)
|
||||
|
||||
## What it does
|
||||
|
||||
@@ -83,38 +85,63 @@ 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.
|
||||
version before invoking the package manager. Even when the installed version
|
||||
already matches the target, the command refreshes the global package install,
|
||||
then runs plugin sync, completion refresh, and restart work. This keeps packaged
|
||||
sidecars and channel-owned plugin records aligned with the installed OpenClaw
|
||||
build.
|
||||
|
||||
## Git checkout flow
|
||||
|
||||
Channels:
|
||||
### Channel selection
|
||||
|
||||
- `stable`: checkout the latest non-beta tag, then build + doctor.
|
||||
- `beta`: prefer the latest `-beta` tag, but fall back to the latest stable tag
|
||||
when beta is missing or older.
|
||||
- `dev`: checkout `main`, then fetch + rebase.
|
||||
- `stable`: checkout the latest non-beta tag, then build and doctor.
|
||||
- `beta`: prefer the latest `-beta` tag, but fall back to the latest stable tag when beta is missing or older.
|
||||
- `dev`: checkout `main`, then fetch and rebase.
|
||||
|
||||
High-level:
|
||||
### Update steps
|
||||
|
||||
1. Requires a clean worktree (no uncommitted changes).
|
||||
2. Switches to the selected channel (tag or branch).
|
||||
3. Fetches upstream (dev only).
|
||||
4. Dev only: preflight lint + TypeScript build in a temp worktree; if the tip fails, walks back up to 10 commits to find the newest clean build.
|
||||
5. Rebases onto the selected commit (dev only).
|
||||
6. Installs deps with the repo package manager. For pnpm checkouts, the updater bootstraps `pnpm` on demand (via `corepack` first, then a temporary `npm install pnpm@10` fallback) instead of running `npm run build` inside a pnpm workspace.
|
||||
7. Builds + builds the Control UI.
|
||||
8. Runs `openclaw doctor` as the final “safe update” check.
|
||||
9. Syncs plugins to the active channel (dev uses bundled plugins; stable/beta uses npm) and updates npm-installed plugins.
|
||||
<Steps>
|
||||
<Step title="Verify clean worktree">
|
||||
Requires no uncommitted changes.
|
||||
</Step>
|
||||
<Step title="Switch channel">
|
||||
Switches to the selected channel (tag or branch).
|
||||
</Step>
|
||||
<Step title="Fetch upstream">
|
||||
Dev only.
|
||||
</Step>
|
||||
<Step title="Preflight build (dev only)">
|
||||
Runs lint and TypeScript build in a temp worktree. If the tip fails, walks back up to 10 commits to find the newest clean build.
|
||||
</Step>
|
||||
<Step title="Rebase">
|
||||
Rebases onto the selected commit (dev only).
|
||||
</Step>
|
||||
<Step title="Install dependencies">
|
||||
Uses the repo package manager. For pnpm checkouts, the updater bootstraps `pnpm` on demand (via `corepack` first, then a temporary `npm install pnpm@10` fallback) instead of running `npm run build` inside a pnpm workspace.
|
||||
</Step>
|
||||
<Step title="Build Control UI">
|
||||
Builds the gateway and the Control UI.
|
||||
</Step>
|
||||
<Step title="Run doctor">
|
||||
`openclaw doctor` runs as the final safe-update check.
|
||||
</Step>
|
||||
<Step title="Sync plugins">
|
||||
Syncs plugins to the active channel. Dev uses bundled plugins; stable and beta use npm. Updates npm-installed plugins.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
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.
|
||||
<Warning>
|
||||
If an exact pinned npm plugin update resolves to an artifact whose integrity differs from the stored install record, `openclaw update` aborts that plugin artifact update instead of installing it. Reinstall or update the plugin explicitly only after verifying that you trust the new artifact.
|
||||
</Warning>
|
||||
|
||||
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.
|
||||
<Note>
|
||||
Post-update plugin sync failures fail the update result and stop restart follow-up work. Fix the plugin install or update error, then rerun `openclaw update`.
|
||||
|
||||
When the updated Gateway starts, enabled bundled plugin runtime dependencies are staged before plugin activation. Update-triggered restarts drain any active runtime-dependency staging before closing the Gateway, so service-manager restarts do not interrupt an in-flight npm install.
|
||||
|
||||
If pnpm bootstrap still fails, the updater stops early with a package-manager-specific error instead of trying `npm run build` inside the checkout.
|
||||
</Note>
|
||||
|
||||
## `--update` shorthand
|
||||
|
||||
|
||||
@@ -163,6 +163,7 @@ surfaces, while Codex native hooks remain a separate lower-level Codex mechanism
|
||||
- `agent.wait` default: 30s (just the wait). `timeoutMs` param overrides.
|
||||
- Agent runtime: `agents.defaults.timeoutSeconds` default 172800s (48 hours); enforced in `runEmbeddedPiAgent` abort timer.
|
||||
- LLM idle timeout: `agents.defaults.llm.idleTimeoutSeconds` aborts a model request when no response chunks arrive before the idle window. Set it explicitly for slow local models or reasoning/tool-call providers; set it to 0 to disable. If it is not set, OpenClaw uses `agents.defaults.timeoutSeconds` when configured, otherwise 120s. Cron-triggered runs with no explicit LLM or agent timeout disable the idle watchdog and rely on the cron outer timeout.
|
||||
- Provider HTTP request timeout: `models.providers.<id>.timeoutSeconds` applies only to that provider's model HTTP fetches, including connect, headers, body, and total guarded-fetch abort handling. Use this for slow local/self-hosted providers such as Ollama before raising the whole agent runtime timeout.
|
||||
|
||||
## Where things can end early
|
||||
|
||||
|
||||
@@ -18,14 +18,24 @@ configuration. They are different layers:
|
||||
| ------------- | ------------------------------------- | ------------------------------------------------------------------- |
|
||||
| Provider | `openai`, `anthropic`, `openai-codex` | How OpenClaw authenticates, discovers models, and names model refs. |
|
||||
| Model | `gpt-5.5`, `claude-opus-4-6` | The model selected for the agent turn. |
|
||||
| Agent runtime | `pi`, `codex`, ACP-backed runtimes | The low level loop that executes the prepared turn. |
|
||||
| Agent runtime | `pi`, `codex`, `claude-cli` | The low level loop or backend that executes the prepared turn. |
|
||||
| Channel | Telegram, Discord, Slack, WhatsApp | Where messages enter and leave OpenClaw. |
|
||||
|
||||
You will also see the word **harness** in code and config. A harness is the
|
||||
implementation that provides an agent runtime. For example, the bundled Codex
|
||||
harness implements the `codex` runtime. The config key is still named
|
||||
`embeddedHarness` for compatibility, but user-facing docs and status output
|
||||
should generally say runtime.
|
||||
You will also see the word **harness** in code. A harness is the implementation
|
||||
that provides an agent runtime. For example, the bundled Codex harness
|
||||
implements the `codex` runtime. Public config uses `agentRuntime.id`; `openclaw
|
||||
doctor --fix` rewrites older runtime-policy keys to that shape.
|
||||
|
||||
There are two runtime families:
|
||||
|
||||
- **Embedded harnesses** run inside OpenClaw's prepared agent loop. Today this
|
||||
is the built-in `pi` runtime plus registered plugin harnesses such as
|
||||
`codex`.
|
||||
- **CLI backends** run a local CLI process while keeping the model ref
|
||||
canonical. For example, `anthropic/claude-opus-4-7` with
|
||||
`agentRuntime.id: "claude-cli"` means "select the Anthropic model, execute
|
||||
through Claude CLI." `claude-cli` is not an embedded harness id and must not
|
||||
be passed to AgentHarness selection.
|
||||
|
||||
## Three things named Codex
|
||||
|
||||
@@ -34,7 +44,7 @@ Most confusion comes from three different surfaces sharing the Codex name:
|
||||
| Surface | OpenClaw name/config | What it does |
|
||||
| ---------------------------------------------------- | ------------------------------------ | --------------------------------------------------------------------------------------------------- |
|
||||
| Codex OAuth provider route | `openai-codex/*` model refs | Uses ChatGPT/Codex subscription OAuth through the normal OpenClaw PI runner. |
|
||||
| Native Codex app-server runtime | `embeddedHarness.runtime: "codex"` | Runs the embedded agent turn through the bundled Codex app-server harness. |
|
||||
| Native Codex app-server runtime | `agentRuntime.id: "codex"` | Runs the embedded agent turn through the bundled Codex app-server harness. |
|
||||
| Codex ACP adapter | `runtime: "acp"`, `agentId: "codex"` | Runs Codex through the external ACP/acpx control plane. Use only when ACP/acpx is explicitly asked. |
|
||||
| Native Codex chat-control command set | `/codex ...` | Binds, resumes, steers, stops, and inspects Codex app-server threads from chat. |
|
||||
| OpenAI Platform API route for GPT/Codex-style models | `openai/*` model refs | Uses OpenAI API-key auth unless a runtime override, such as `runtime: "codex"`, runs the turn. |
|
||||
@@ -52,8 +62,8 @@ The common Codex setup uses the `openai` provider with the `codex` runtime:
|
||||
agents: {
|
||||
defaults: {
|
||||
model: "openai/gpt-5.5",
|
||||
embeddedHarness: {
|
||||
runtime: "codex",
|
||||
agentRuntime: {
|
||||
id: "codex",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -76,7 +86,7 @@ This is the agent-facing decision tree:
|
||||
1. If the user asks for **Codex bind/control/thread/resume/steer/stop**, use the
|
||||
native `/codex` command surface when the bundled `codex` plugin is enabled.
|
||||
2. If the user asks for **Codex as the embedded runtime**, use
|
||||
`openai/<model>` with `embeddedHarness.runtime: "codex"`.
|
||||
`openai/<model>` with `agentRuntime.id: "codex"`.
|
||||
3. If the user asks for **Codex OAuth/subscription auth on the normal OpenClaw
|
||||
runner**, use `openai-codex/<model>` and leave the runtime as PI.
|
||||
4. If the user explicitly says **ACP**, **acpx**, or **Codex ACP adapter**, use
|
||||
@@ -87,7 +97,7 @@ This is the agent-facing decision tree:
|
||||
| You mean... | Use... |
|
||||
| --------------------------------------- | -------------------------------------------- |
|
||||
| Codex app-server chat/thread control | `/codex ...` from the bundled `codex` plugin |
|
||||
| Codex app-server embedded agent runtime | `embeddedHarness.runtime: "codex"` |
|
||||
| Codex app-server embedded agent runtime | `agentRuntime.id: "codex"` |
|
||||
| OpenAI Codex OAuth on the PI runner | `openai-codex/*` model refs |
|
||||
| Claude Code or other external harness | ACP/acpx |
|
||||
|
||||
@@ -122,9 +132,9 @@ OpenClaw chooses an embedded runtime after provider and model resolution:
|
||||
1. A session's recorded runtime wins. Config changes do not hot-switch an
|
||||
existing transcript to a different native thread system.
|
||||
2. `OPENCLAW_AGENT_RUNTIME=<id>` forces that runtime for new or reset sessions.
|
||||
3. `agents.defaults.embeddedHarness.runtime` or
|
||||
`agents.list[].embeddedHarness.runtime` can set `auto`, `pi`, or a registered
|
||||
runtime id such as `codex`.
|
||||
3. `agents.defaults.agentRuntime.id` or `agents.list[].agentRuntime.id` can set
|
||||
`auto`, `pi`, a registered embedded harness id such as `codex`, or a
|
||||
supported CLI backend alias such as `claude-cli`.
|
||||
4. In `auto` mode, registered plugin runtimes can claim supported provider/model
|
||||
pairs.
|
||||
5. If no runtime claims a turn in `auto` mode and `fallback: "pi"` is set
|
||||
@@ -137,6 +147,24 @@ Explicit plugin runtimes fail closed by default. For example,
|
||||
a broader fallback setting, so an agent-level `runtime: "codex"` is not silently
|
||||
routed back to PI just because defaults used `fallback: "pi"`.
|
||||
|
||||
CLI backend aliases are different from embedded harness ids. The preferred
|
||||
Claude CLI form is:
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
defaults: {
|
||||
model: "anthropic/claude-opus-4-7",
|
||||
agentRuntime: { id: "claude-cli" },
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Legacy refs such as `claude-cli/claude-opus-4-7` remain supported for
|
||||
compatibility, but new config should keep the provider/model canonical and put
|
||||
the execution backend in `agentRuntime.id`.
|
||||
|
||||
`auto` mode is intentionally conservative. Plugin runtimes can claim
|
||||
provider/model pairs they understand, but the Codex plugin does not claim the
|
||||
`openai-codex` provider in `auto` mode. That keeps
|
||||
@@ -146,7 +174,7 @@ moving subscription-auth configs onto the native app-server harness.
|
||||
If `openclaw doctor` warns that the `codex` plugin is enabled while
|
||||
`openai-codex/*` still routes through PI, treat that as a diagnosis, not a
|
||||
migration. Keep the config unchanged when PI Codex OAuth is what you want.
|
||||
Switch to `openai/<model>` plus `runtime: "codex"` only when you want native
|
||||
Switch to `openai/<model>` plus `agentRuntime.id: "codex"` only when you want native
|
||||
Codex app-server execution.
|
||||
|
||||
## Compatibility contract
|
||||
|
||||
@@ -4,26 +4,23 @@ read_when:
|
||||
- You need to explain the agent workspace or its file layout
|
||||
- You want to back up or migrate an agent workspace
|
||||
title: "Agent workspace"
|
||||
sidebarTitle: "Agent workspace"
|
||||
---
|
||||
|
||||
The workspace is the agent's home. It is the only working directory used for
|
||||
file tools and for workspace context. Keep it private and treat it as memory.
|
||||
The workspace is the agent's home. It is the only working directory used for file tools and for workspace context. Keep it private and treat it as memory.
|
||||
|
||||
This is separate from `~/.openclaw/`, which stores config, credentials, and
|
||||
sessions.
|
||||
This is separate from `~/.openclaw/`, which stores config, credentials, and sessions.
|
||||
|
||||
**Important:** the workspace is the **default cwd**, not a hard sandbox. Tools
|
||||
resolve relative paths against the workspace, but absolute paths can still reach
|
||||
elsewhere on the host unless sandboxing is enabled. If you need isolation, use
|
||||
[`agents.defaults.sandbox`](/gateway/sandboxing) (and/or per‑agent sandbox config).
|
||||
When sandboxing is enabled and `workspaceAccess` is not `"rw"`, tools operate
|
||||
inside a sandbox workspace under `~/.openclaw/sandboxes`, not your host workspace.
|
||||
<Warning>
|
||||
The workspace is the **default cwd**, not a hard sandbox. Tools resolve relative paths against the workspace, but absolute paths can still reach elsewhere on the host unless sandboxing is enabled. If you need isolation, use [`agents.defaults.sandbox`](/gateway/sandboxing) (and/or per-agent sandbox config).
|
||||
|
||||
When sandboxing is enabled and `workspaceAccess` is not `"rw"`, tools operate inside a sandbox workspace under `~/.openclaw/sandboxes`, not your host workspace.
|
||||
</Warning>
|
||||
|
||||
## Default location
|
||||
|
||||
- Default: `~/.openclaw/workspace`
|
||||
- If `OPENCLAW_PROFILE` is set and not `"default"`, the default becomes
|
||||
`~/.openclaw/workspace-<profile>`.
|
||||
- If `OPENCLAW_PROFILE` is set and not `"default"`, the default becomes `~/.openclaw/workspace-<profile>`.
|
||||
- Override in `~/.openclaw/openclaw.json`:
|
||||
|
||||
```json5
|
||||
@@ -36,13 +33,13 @@ inside a sandbox workspace under `~/.openclaw/sandboxes`, not your host workspac
|
||||
}
|
||||
```
|
||||
|
||||
`openclaw onboard`, `openclaw configure`, or `openclaw setup` will create the
|
||||
workspace and seed the bootstrap files if they are missing.
|
||||
Sandbox seed copies only accept regular in-workspace files; symlink/hardlink
|
||||
aliases that resolve outside the source workspace are ignored.
|
||||
`openclaw onboard`, `openclaw configure`, or `openclaw setup` will create the workspace and seed the bootstrap files if they are missing.
|
||||
|
||||
If you already manage the workspace files yourself, you can disable bootstrap
|
||||
file creation:
|
||||
<Note>
|
||||
Sandbox seed copies only accept regular in-workspace files; symlink/hardlink aliases that resolve outside the source workspace are ignored.
|
||||
</Note>
|
||||
|
||||
If you already manage the workspace files yourself, you can disable bootstrap file creation:
|
||||
|
||||
```json5
|
||||
{ agents: { defaults: { skipBootstrap: true } } }
|
||||
@@ -50,80 +47,60 @@ file creation:
|
||||
|
||||
## Extra workspace folders
|
||||
|
||||
Older installs may have created `~/openclaw`. Keeping multiple workspace
|
||||
directories around can cause confusing auth or state drift, because only one
|
||||
workspace is active at a time.
|
||||
Older installs may have created `~/openclaw`. Keeping multiple workspace directories around can cause confusing auth or state drift, because only one workspace is active at a time.
|
||||
|
||||
**Recommendation:** keep a single active workspace. If you no longer use the
|
||||
extra folders, archive or move them to Trash (for example `trash ~/openclaw`).
|
||||
If you intentionally keep multiple workspaces, make sure
|
||||
`agents.defaults.workspace` points to the active one.
|
||||
<Note>
|
||||
**Recommendation:** keep a single active workspace. If you no longer use the extra folders, archive or move them to Trash (for example `trash ~/openclaw`). If you intentionally keep multiple workspaces, make sure `agents.defaults.workspace` points to the active one.
|
||||
|
||||
`openclaw doctor` warns when it detects extra workspace directories.
|
||||
</Note>
|
||||
|
||||
## Workspace file map (what each file means)
|
||||
## Workspace file map
|
||||
|
||||
These are the standard files OpenClaw expects inside the workspace:
|
||||
|
||||
- `AGENTS.md`
|
||||
- Operating instructions for the agent and how it should use memory.
|
||||
- Loaded at the start of every session.
|
||||
- Good place for rules, priorities, and "how to behave" details.
|
||||
<AccordionGroup>
|
||||
<Accordion title="AGENTS.md — operating instructions">
|
||||
Operating instructions for the agent and how it should use memory. Loaded at the start of every session. Good place for rules, priorities, and "how to behave" details.
|
||||
</Accordion>
|
||||
<Accordion title="SOUL.md — persona and tone">
|
||||
Persona, tone, and boundaries. Loaded every session. Guide: [SOUL.md personality guide](/concepts/soul).
|
||||
</Accordion>
|
||||
<Accordion title="USER.md — who the user is">
|
||||
Who the user is and how to address them. Loaded every session.
|
||||
</Accordion>
|
||||
<Accordion title="IDENTITY.md — name, vibe, emoji">
|
||||
The agent's name, vibe, and emoji. Created/updated during the bootstrap ritual.
|
||||
</Accordion>
|
||||
<Accordion title="TOOLS.md — local tool conventions">
|
||||
Notes about your local tools and conventions. Does not control tool availability; it is only guidance.
|
||||
</Accordion>
|
||||
<Accordion title="HEARTBEAT.md — heartbeat checklist">
|
||||
Optional tiny checklist for heartbeat runs. Keep it short to avoid token burn.
|
||||
</Accordion>
|
||||
<Accordion title="BOOT.md — startup checklist">
|
||||
Optional startup checklist run automatically on gateway restart (when [internal hooks](/automation/hooks) are enabled). Keep it short; use the message tool for outbound sends.
|
||||
</Accordion>
|
||||
<Accordion title="BOOTSTRAP.md — first-run ritual">
|
||||
One-time first-run ritual. Only created for a brand-new workspace. Delete it after the ritual is complete.
|
||||
</Accordion>
|
||||
<Accordion title="memory/YYYY-MM-DD.md — daily memory log">
|
||||
Daily memory log (one file per day). Recommended to read today + yesterday on session start.
|
||||
</Accordion>
|
||||
<Accordion title="MEMORY.md — curated long-term memory (optional)">
|
||||
Curated long-term memory. Only load in the main, private session (not shared/group contexts). See [Memory](/concepts/memory) for the workflow and automatic memory flush.
|
||||
</Accordion>
|
||||
<Accordion title="skills/ — workspace skills (optional)">
|
||||
Workspace-specific skills. Highest-precedence skill location for that workspace. Overrides project agent skills, personal agent skills, managed skills, bundled skills, and `skills.load.extraDirs` when names collide.
|
||||
</Accordion>
|
||||
<Accordion title="canvas/ — Canvas UI files (optional)">
|
||||
Canvas UI files for node displays (for example `canvas/index.html`).
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
- `SOUL.md`
|
||||
- Persona, tone, and boundaries.
|
||||
- Loaded every session.
|
||||
- Guide: [SOUL.md Personality Guide](/concepts/soul)
|
||||
|
||||
- `USER.md`
|
||||
- Who the user is and how to address them.
|
||||
- Loaded every session.
|
||||
|
||||
- `IDENTITY.md`
|
||||
- The agent's name, vibe, and emoji.
|
||||
- Created/updated during the bootstrap ritual.
|
||||
|
||||
- `TOOLS.md`
|
||||
- Notes about your local tools and conventions.
|
||||
- Does not control tool availability; it is only guidance.
|
||||
|
||||
- `HEARTBEAT.md`
|
||||
- Optional tiny checklist for heartbeat runs.
|
||||
- Keep it short to avoid token burn.
|
||||
|
||||
- `BOOT.md`
|
||||
- Optional startup checklist run automatically on gateway restart (when [internal hooks](/automation/hooks) are enabled).
|
||||
- Keep it short; use the message tool for outbound sends.
|
||||
|
||||
- `BOOTSTRAP.md`
|
||||
- One-time first-run ritual.
|
||||
- Only created for a brand-new workspace.
|
||||
- Delete it after the ritual is complete.
|
||||
|
||||
- `memory/YYYY-MM-DD.md`
|
||||
- Daily memory log (one file per day).
|
||||
- Recommended to read today + yesterday on session start.
|
||||
|
||||
- `MEMORY.md` (optional)
|
||||
- Curated long-term memory.
|
||||
- Only load in the main, private session (not shared/group contexts).
|
||||
|
||||
See [Memory](/concepts/memory) for the workflow and automatic memory flush.
|
||||
|
||||
- `skills/` (optional)
|
||||
- Workspace-specific skills.
|
||||
- Highest-precedence skill location for that workspace.
|
||||
- Overrides project agent skills, personal agent skills, managed skills, bundled skills, and `skills.load.extraDirs` when names collide.
|
||||
|
||||
- `canvas/` (optional)
|
||||
- Canvas UI files for node displays (for example `canvas/index.html`).
|
||||
|
||||
If any bootstrap file is missing, OpenClaw injects a "missing file" marker into
|
||||
the session and continues. Large bootstrap files are truncated when injected;
|
||||
adjust limits with `agents.defaults.bootstrapMaxChars` (default: 12000) and
|
||||
`agents.defaults.bootstrapTotalMaxChars` (default: 60000).
|
||||
`openclaw setup` can recreate missing defaults without overwriting existing
|
||||
files.
|
||||
<Note>
|
||||
If any bootstrap file is missing, OpenClaw injects a "missing file" marker into the session and continues. Large bootstrap files are truncated when injected; adjust limits with `agents.defaults.bootstrapMaxChars` (default: 12000) and `agents.defaults.bootstrapTotalMaxChars` (default: 60000). `openclaw setup` can recreate missing defaults without overwriting existing files.
|
||||
</Note>
|
||||
|
||||
## What is NOT in the workspace
|
||||
|
||||
@@ -135,83 +112,82 @@ These live under `~/.openclaw/` and should NOT be committed to the workspace rep
|
||||
- `~/.openclaw/agents/<agentId>/sessions/` (session transcripts + metadata)
|
||||
- `~/.openclaw/skills/` (managed skills)
|
||||
|
||||
If you need to migrate sessions or config, copy them separately and keep them
|
||||
out of version control.
|
||||
If you need to migrate sessions or config, copy them separately and keep them out of version control.
|
||||
|
||||
## Git backup (recommended, private)
|
||||
|
||||
Treat the workspace as private memory. Put it in a **private** git repo so it is
|
||||
backed up and recoverable.
|
||||
Treat the workspace as private memory. Put it in a **private** git repo so it is backed up and recoverable.
|
||||
|
||||
Run these steps on the machine where the Gateway runs (that is where the
|
||||
workspace lives).
|
||||
Run these steps on the machine where the Gateway runs (that is where the workspace lives).
|
||||
|
||||
### 1) Initialize the repo
|
||||
<Steps>
|
||||
<Step title="Initialize the repo">
|
||||
If git is installed, brand-new workspaces are initialized automatically. If this workspace is not already a repo, run:
|
||||
|
||||
If git is installed, brand-new workspaces are initialized automatically. If this
|
||||
workspace is not already a repo, run:
|
||||
```bash
|
||||
cd ~/.openclaw/workspace
|
||||
git init
|
||||
git add AGENTS.md SOUL.md TOOLS.md IDENTITY.md USER.md HEARTBEAT.md memory/
|
||||
git commit -m "Add agent workspace"
|
||||
```
|
||||
|
||||
```bash
|
||||
cd ~/.openclaw/workspace
|
||||
git init
|
||||
git add AGENTS.md SOUL.md TOOLS.md IDENTITY.md USER.md HEARTBEAT.md memory/
|
||||
git commit -m "Add agent workspace"
|
||||
```
|
||||
</Step>
|
||||
<Step title="Add a private remote">
|
||||
<Tabs>
|
||||
<Tab title="GitHub web UI">
|
||||
1. Create a new **private** repository on GitHub.
|
||||
2. Do not initialize with a README (avoids merge conflicts).
|
||||
3. Copy the HTTPS remote URL.
|
||||
4. Add the remote and push:
|
||||
|
||||
### 2) Add a private remote (beginner-friendly options)
|
||||
```bash
|
||||
git branch -M main
|
||||
git remote add origin <https-url>
|
||||
git push -u origin main
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="GitHub CLI (gh)">
|
||||
```bash
|
||||
gh auth login
|
||||
gh repo create openclaw-workspace --private --source . --remote origin --push
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="GitLab web UI">
|
||||
1. Create a new **private** repository on GitLab.
|
||||
2. Do not initialize with a README (avoids merge conflicts).
|
||||
3. Copy the HTTPS remote URL.
|
||||
4. Add the remote and push:
|
||||
|
||||
Option A: GitHub web UI
|
||||
```bash
|
||||
git branch -M main
|
||||
git remote add origin <https-url>
|
||||
git push -u origin main
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
1. Create a new **private** repository on GitHub.
|
||||
2. Do not initialize with a README (avoids merge conflicts).
|
||||
3. Copy the HTTPS remote URL.
|
||||
4. Add the remote and push:
|
||||
|
||||
```bash
|
||||
git branch -M main
|
||||
git remote add origin <https-url>
|
||||
git push -u origin main
|
||||
```
|
||||
|
||||
Option B: GitHub CLI (`gh`)
|
||||
|
||||
```bash
|
||||
gh auth login
|
||||
gh repo create openclaw-workspace --private --source . --remote origin --push
|
||||
```
|
||||
|
||||
Option C: GitLab web UI
|
||||
|
||||
1. Create a new **private** repository on GitLab.
|
||||
2. Do not initialize with a README (avoids merge conflicts).
|
||||
3. Copy the HTTPS remote URL.
|
||||
4. Add the remote and push:
|
||||
|
||||
```bash
|
||||
git branch -M main
|
||||
git remote add origin <https-url>
|
||||
git push -u origin main
|
||||
```
|
||||
|
||||
### 3) Ongoing updates
|
||||
|
||||
```bash
|
||||
git status
|
||||
git add .
|
||||
git commit -m "Update memory"
|
||||
git push
|
||||
```
|
||||
</Step>
|
||||
<Step title="Ongoing updates">
|
||||
```bash
|
||||
git status
|
||||
git add .
|
||||
git commit -m "Update memory"
|
||||
git push
|
||||
```
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## Do not commit secrets
|
||||
|
||||
<Warning>
|
||||
Even in a private repo, avoid storing secrets in the workspace:
|
||||
|
||||
- API keys, OAuth tokens, passwords, or private credentials.
|
||||
- Anything under `~/.openclaw/`.
|
||||
- Raw dumps of chats or sensitive attachments.
|
||||
|
||||
If you must store sensitive references, use placeholders and keep the real
|
||||
secret elsewhere (password manager, environment variables, or `~/.openclaw/`).
|
||||
If you must store sensitive references, use placeholders and keep the real secret elsewhere (password manager, environment variables, or `~/.openclaw/`).
|
||||
</Warning>
|
||||
|
||||
Suggested `.gitignore` starter:
|
||||
|
||||
@@ -225,22 +201,29 @@ Suggested `.gitignore` starter:
|
||||
|
||||
## Moving the workspace to a new machine
|
||||
|
||||
1. Clone the repo to the desired path (default `~/.openclaw/workspace`).
|
||||
2. Set `agents.defaults.workspace` to that path in `~/.openclaw/openclaw.json`.
|
||||
3. Run `openclaw setup --workspace <path>` to seed any missing files.
|
||||
4. If you need sessions, copy `~/.openclaw/agents/<agentId>/sessions/` from the
|
||||
old machine separately.
|
||||
<Steps>
|
||||
<Step title="Clone the repo">
|
||||
Clone the repo to the desired path (default `~/.openclaw/workspace`).
|
||||
</Step>
|
||||
<Step title="Update config">
|
||||
Set `agents.defaults.workspace` to that path in `~/.openclaw/openclaw.json`.
|
||||
</Step>
|
||||
<Step title="Seed missing files">
|
||||
Run `openclaw setup --workspace <path>` to seed any missing files.
|
||||
</Step>
|
||||
<Step title="Copy sessions (optional)">
|
||||
If you need sessions, copy `~/.openclaw/agents/<agentId>/sessions/` from the old machine separately.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## Advanced notes
|
||||
|
||||
- Multi-agent routing can use different workspaces per agent. See
|
||||
[Channel routing](/channels/channel-routing) for routing configuration.
|
||||
- If `agents.defaults.sandbox` is enabled, non-main sessions can use per-session sandbox
|
||||
workspaces under `agents.defaults.sandbox.workspaceRoot`.
|
||||
- Multi-agent routing can use different workspaces per agent. See [Channel routing](/channels/channel-routing) for routing configuration.
|
||||
- If `agents.defaults.sandbox` is enabled, non-main sessions can use per-session sandbox workspaces under `agents.defaults.sandbox.workspaceRoot`.
|
||||
|
||||
## Related
|
||||
|
||||
- [Standing Orders](/automation/standing-orders) — persistent instructions in workspace files
|
||||
- [Heartbeat](/gateway/heartbeat) — HEARTBEAT.md workspace file
|
||||
- [Session](/concepts/session) — session storage paths
|
||||
- [Sandboxing](/gateway/sandboxing) — workspace access in sandboxed environments
|
||||
- [Session](/concepts/session) — session storage paths
|
||||
- [Standing orders](/automation/standing-orders) — persistent instructions in workspace files
|
||||
|
||||
@@ -6,9 +6,7 @@ read_when:
|
||||
title: "Compaction"
|
||||
---
|
||||
|
||||
Every model has a context window -- the maximum number of tokens it can process.
|
||||
When a conversation approaches that limit, OpenClaw **compacts** older messages
|
||||
into a summary so the chat can continue.
|
||||
Every model has a context window: the maximum number of tokens it can process. When a conversation approaches that limit, OpenClaw **compacts** older messages into a summary so the chat can continue.
|
||||
|
||||
## How it works
|
||||
|
||||
@@ -16,33 +14,54 @@ into a summary so the chat can continue.
|
||||
2. The summary is saved in the session transcript.
|
||||
3. Recent messages are kept intact.
|
||||
|
||||
When OpenClaw splits history into compaction chunks, it keeps assistant tool
|
||||
calls paired with their matching `toolResult` entries. If a split point lands
|
||||
inside a tool block, OpenClaw moves the boundary so the pair stays together and
|
||||
the current unsummarized tail is preserved.
|
||||
When OpenClaw splits history into compaction chunks, it keeps assistant tool calls paired with their matching `toolResult` entries. If a split point lands inside a tool block, OpenClaw moves the boundary so the pair stays together and the current unsummarized tail is preserved.
|
||||
|
||||
The full conversation history stays on disk. Compaction only changes what the
|
||||
model sees on the next turn.
|
||||
The full conversation history stays on disk. Compaction only changes what the model sees on the next turn.
|
||||
|
||||
## Auto-compaction
|
||||
|
||||
Auto-compaction is on by default. It runs when the session nears the context
|
||||
limit, or when the model returns a context-overflow error (in which case
|
||||
OpenClaw compacts and retries). Typical overflow signatures include
|
||||
`request_too_large`, `context length exceeded`, `input exceeds the maximum
|
||||
number of tokens`, `input token count exceeds the maximum number of input
|
||||
tokens`, `input is too long for the model`, and `ollama error: context length
|
||||
exceeded`.
|
||||
Auto-compaction is on by default. It runs when the session nears the context limit, or when the model returns a context-overflow error (in which case OpenClaw compacts and retries).
|
||||
|
||||
You will see:
|
||||
|
||||
- `🧹 Auto-compaction complete` in verbose mode.
|
||||
- `/status` showing `🧹 Compactions: <count>`.
|
||||
|
||||
<Info>
|
||||
Before compacting, OpenClaw automatically reminds the agent to save important
|
||||
notes to [memory](/concepts/memory) files. This prevents context loss.
|
||||
Before compacting, OpenClaw automatically reminds the agent to save important notes to [memory](/concepts/memory) files. This prevents context loss.
|
||||
</Info>
|
||||
|
||||
Use the `agents.defaults.compaction` setting in your `openclaw.json` to configure compaction behavior (mode, target tokens, etc.).
|
||||
Compaction summarization preserves opaque identifiers by default (`identifierPolicy: "strict"`). You can override this with `identifierPolicy: "off"` or provide custom text with `identifierPolicy: "custom"` and `identifierInstructions`.
|
||||
<AccordionGroup>
|
||||
<Accordion title="Recognized overflow signatures">
|
||||
OpenClaw detects context overflow from these provider error patterns:
|
||||
|
||||
You can optionally specify a different model for compaction summarization via `agents.defaults.compaction.model`. This is useful when your primary model is a local or small model and you want compaction summaries produced by a more capable model. The override accepts any `provider/model-id` string:
|
||||
- `request_too_large`
|
||||
- `context length exceeded`
|
||||
- `input exceeds the maximum number of tokens`
|
||||
- `input token count exceeds the maximum number of input tokens`
|
||||
- `input is too long for the model`
|
||||
- `ollama error: context length exceeded`
|
||||
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Manual compaction
|
||||
|
||||
Type `/compact` in any chat to force a compaction. Add instructions to guide the summary:
|
||||
|
||||
```
|
||||
/compact Focus on the API design decisions
|
||||
```
|
||||
|
||||
When `agents.defaults.compaction.keepRecentTokens` is set, manual compaction honors that Pi cut-point and keeps the recent tail in rebuilt context. Without an explicit keep budget, manual compaction behaves as a hard checkpoint and continues from the new summary alone.
|
||||
|
||||
## Configuration
|
||||
|
||||
Configure compaction under `agents.defaults.compaction` in your `openclaw.json`. The most common knobs are listed below; for the full reference, see [Session management deep dive](/reference/session-management-compaction).
|
||||
|
||||
### Using a different model
|
||||
|
||||
By default, compaction uses the agent's primary model. Set `agents.defaults.compaction.model` to delegate summarization to a more capable or specialized model. The override accepts any `provider/model-id` string:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -56,7 +75,7 @@ You can optionally specify a different model for compaction summarization via `a
|
||||
}
|
||||
```
|
||||
|
||||
This also works with local models, for example a second Ollama model dedicated to summarization or a fine-tuned compaction specialist:
|
||||
This works with local models too, for example a second Ollama model dedicated to summarization:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -70,75 +89,27 @@ This also works with local models, for example a second Ollama model dedicated t
|
||||
}
|
||||
```
|
||||
|
||||
When unset, compaction uses the agent’s primary model.
|
||||
When unset, compaction uses the agent's primary model.
|
||||
|
||||
## Pluggable compaction providers
|
||||
### Identifier preservation
|
||||
|
||||
Plugins can register a custom compaction provider via `registerCompactionProvider()` on the plugin API. When a provider is registered and configured, OpenClaw delegates summarization to it instead of the built-in LLM pipeline.
|
||||
Compaction summarization preserves opaque identifiers by default (`identifierPolicy: "strict"`). Override with `identifierPolicy: "off"` to disable, or `identifierPolicy: "custom"` plus `identifierInstructions` for custom guidance.
|
||||
|
||||
To use a registered provider, set the provider id in your config:
|
||||
### Active transcript byte guard
|
||||
|
||||
```json
|
||||
{
|
||||
"agents": {
|
||||
"defaults": {
|
||||
"compaction": {
|
||||
"provider": "my-provider"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
When `agents.defaults.compaction.maxActiveTranscriptBytes` is set, OpenClaw triggers normal local compaction before a run if the active JSONL reaches that size. This is useful for long-running sessions where provider-side context management may keep model context healthy while the local transcript keeps growing. It does not split raw JSONL bytes; it asks the normal compaction pipeline to create a semantic summary.
|
||||
|
||||
Setting a `provider` automatically forces `mode: "safeguard"`. Providers receive the same compaction instructions and identifier-preservation policy as the built-in path, and OpenClaw still preserves recent-turn and split-turn suffix context after provider output. If the provider fails or returns an empty result, OpenClaw falls back to built-in LLM summarization.
|
||||
<Warning>
|
||||
The byte guard requires `truncateAfterCompaction: true`. Without transcript rotation, the active file would not shrink and the guard remains inactive.
|
||||
</Warning>
|
||||
|
||||
## Auto-compaction (default on)
|
||||
### Successor transcripts
|
||||
|
||||
When a session nears or exceeds the model’s context window, OpenClaw triggers auto-compaction and may retry the original request using the compacted context.
|
||||
When `agents.defaults.compaction.truncateAfterCompaction` is enabled, OpenClaw does not rewrite the existing transcript in place. It creates a new active successor transcript from the compaction summary, preserved state, and unsummarized tail, then keeps the previous JSONL as the archived checkpoint source.
|
||||
|
||||
You’ll see:
|
||||
### Compaction notices
|
||||
|
||||
- `🧹 Auto-compaction complete` in verbose mode
|
||||
- `/status` showing `🧹 Compactions: <count>`
|
||||
|
||||
Before compaction, OpenClaw can run a **silent memory flush** turn to store
|
||||
durable notes to disk. See [Memory](/concepts/memory) for details and config.
|
||||
|
||||
## Manual compaction
|
||||
|
||||
Type `/compact` in any chat to force a compaction. Add instructions to guide
|
||||
the summary:
|
||||
|
||||
```
|
||||
/compact Focus on the API design decisions
|
||||
```
|
||||
|
||||
When `agents.defaults.compaction.keepRecentTokens` is set, manual compaction
|
||||
honors that Pi cut-point and keeps the recent tail in rebuilt context. Without
|
||||
an explicit keep budget, manual compaction behaves as a hard checkpoint and
|
||||
continues from the new summary alone.
|
||||
|
||||
## Using a different model
|
||||
|
||||
By default, compaction uses your agent's primary model. You can use a more
|
||||
capable model for better summaries:
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
defaults: {
|
||||
compaction: {
|
||||
model: "openrouter/anthropic/claude-sonnet-4-6",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Compaction notices
|
||||
|
||||
By default, compaction runs silently. To show brief notices when compaction
|
||||
starts and when it completes, enable `notifyUser`:
|
||||
By default, compaction runs silently. Set `notifyUser` to show brief status messages when compaction starts and completes:
|
||||
|
||||
```json5
|
||||
{
|
||||
@@ -152,8 +123,33 @@ starts and when it completes, enable `notifyUser`:
|
||||
}
|
||||
```
|
||||
|
||||
When enabled, the user sees short status messages around each compaction run
|
||||
(for example, "Compacting context..." and "Compaction complete").
|
||||
### Memory flush
|
||||
|
||||
Before compaction, OpenClaw can run a **silent memory flush** turn to store durable notes to disk. See [Memory](/concepts/memory) for details and config.
|
||||
|
||||
## Pluggable compaction providers
|
||||
|
||||
Plugins can register a custom compaction provider via `registerCompactionProvider()` on the plugin API. When a provider is registered and configured, OpenClaw delegates summarization to it instead of the built-in LLM pipeline.
|
||||
|
||||
To use a registered provider, set its id in your config:
|
||||
|
||||
```json
|
||||
{
|
||||
"agents": {
|
||||
"defaults": {
|
||||
"compaction": {
|
||||
"provider": "my-provider"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Setting a `provider` automatically forces `mode: "safeguard"`. Providers receive the same compaction instructions and identifier-preservation policy as the built-in path, and OpenClaw still preserves recent-turn and split-turn suffix context after provider output.
|
||||
|
||||
<Note>
|
||||
If the provider fails or returns an empty result, OpenClaw falls back to built-in LLM summarization.
|
||||
</Note>
|
||||
|
||||
## Compaction vs pruning
|
||||
|
||||
@@ -163,28 +159,21 @@ When enabled, the user sees short status messages around each compaction run
|
||||
| **Saved?** | Yes (in session transcript) | No (in-memory only, per request) |
|
||||
| **Scope** | Entire conversation | Tool results only |
|
||||
|
||||
[Session pruning](/concepts/session-pruning) is a lighter-weight complement that
|
||||
trims tool output without summarizing.
|
||||
[Session pruning](/concepts/session-pruning) is a lighter-weight complement that trims tool output without summarizing.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Compacting too often?** The model's context window may be small, or tool
|
||||
outputs may be large. Try enabling
|
||||
[session pruning](/concepts/session-pruning).
|
||||
**Compacting too often?** The model's context window may be small, or tool outputs may be large. Try enabling [session pruning](/concepts/session-pruning).
|
||||
|
||||
**Context feels stale after compaction?** Use `/compact Focus on <topic>` to
|
||||
guide the summary, or enable the [memory flush](/concepts/memory) so notes
|
||||
survive.
|
||||
**Context feels stale after compaction?** Use `/compact Focus on <topic>` to guide the summary, or enable the [memory flush](/concepts/memory) so notes survive.
|
||||
|
||||
**Need a clean slate?** `/new` starts a fresh session without compacting.
|
||||
|
||||
For advanced configuration (reserve tokens, identifier preservation, custom
|
||||
context engines, OpenAI server-side compaction), see the
|
||||
[Session Management Deep Dive](/reference/session-management-compaction).
|
||||
For advanced configuration (reserve tokens, identifier preservation, custom context engines, OpenAI server-side compaction), see the [Session management deep dive](/reference/session-management-compaction).
|
||||
|
||||
## Related
|
||||
|
||||
- [Session](/concepts/session) — session management and lifecycle
|
||||
- [Session Pruning](/concepts/session-pruning) — trimming tool results
|
||||
- [Context](/concepts/context) — how context is built for agent turns
|
||||
- [Hooks](/automation/hooks) — compaction lifecycle hooks (before_compaction, after_compaction)
|
||||
- [Session](/concepts/session): session management and lifecycle.
|
||||
- [Session pruning](/concepts/session-pruning): trimming tool results.
|
||||
- [Context](/concepts/context): how context is built for agent turns.
|
||||
- [Hooks](/automation/hooks): compaction lifecycle hooks (`before_compaction`, `after_compaction`).
|
||||
|
||||
@@ -194,6 +194,10 @@ Required members:
|
||||
Prepended to the system prompt.
|
||||
</ParamField>
|
||||
|
||||
`compact` returns a `CompactResult`. When compaction rotates the active
|
||||
transcript, `result.sessionId` and `result.sessionFile` identify the successor
|
||||
session that the next retry or turn must use.
|
||||
|
||||
Optional members:
|
||||
|
||||
| Member | Kind | Purpose |
|
||||
@@ -253,6 +257,10 @@ A no-op `compact()` is unsafe for an active non-owning engine because it disable
|
||||
The slot is exclusive at run time — only one registered context engine is resolved for a given run or compaction operation. Other enabled `kind: "context-engine"` plugins can still load and run their registration code; `plugins.slots.contextEngine` only selects which registered engine id OpenClaw resolves when it needs a context engine.
|
||||
</Note>
|
||||
|
||||
<Note>
|
||||
**Plugin uninstall:** when you uninstall the plugin currently selected as `plugins.slots.contextEngine`, OpenClaw resets the slot back to the default (`legacy`). The same reset behavior applies to `plugins.slots.memory`. No manual config edit is required.
|
||||
</Note>
|
||||
|
||||
## Relationship to compaction and memory
|
||||
|
||||
<AccordionGroup>
|
||||
|
||||
@@ -70,11 +70,15 @@ The delegate operates **autonomously** on a schedule, executing standing orders
|
||||
|
||||
This tier combines Tier 2 permissions with [Cron Jobs](/automation/cron-jobs) and [Standing Orders](/automation/standing-orders).
|
||||
|
||||
> **Security warning**: Tier 3 requires careful configuration of hard blocks — actions the agent must never take regardless of instruction. Complete the prerequisites below before granting any identity provider permissions.
|
||||
<Warning>
|
||||
Tier 3 requires careful configuration of hard blocks: actions the agent must never take regardless of instruction. Complete the prerequisites below before granting any identity provider permissions.
|
||||
</Warning>
|
||||
|
||||
## Prerequisites: isolation and hardening
|
||||
|
||||
> **Do this first.** Before you grant any credentials or identity provider access, lock down the delegate's boundaries. The steps in this section define what the agent **cannot** do — establish these constraints before giving it the ability to do anything.
|
||||
<Note>
|
||||
**Do this first.** Before you grant any credentials or identity provider access, lock down the delegate's boundaries. The steps in this section define what the agent **cannot** do. Establish these constraints before giving it the ability to do anything.
|
||||
</Note>
|
||||
|
||||
### Hard blocks (non-negotiable)
|
||||
|
||||
@@ -180,7 +184,9 @@ New-ApplicationAccessPolicy `
|
||||
-AccessRight RestrictAccess
|
||||
```
|
||||
|
||||
> **Security warning**: without an application access policy, `Mail.Read` application permission grants access to **every mailbox in the tenant**. Always create the access policy before the application reads any mail. Test by confirming the app returns `403` for mailboxes outside the security group.
|
||||
<Warning>
|
||||
Without an application access policy, `Mail.Read` application permission grants access to **every mailbox in the tenant**. Always create the access policy before the application reads any mail. Test by confirming the app returns `403` for mailboxes outside the security group.
|
||||
</Warning>
|
||||
|
||||
#### Google Workspace
|
||||
|
||||
@@ -196,7 +202,9 @@ https://www.googleapis.com/auth/calendar # Tier 2
|
||||
|
||||
The service account impersonates the delegate user (not the principal), preserving the "on behalf of" model.
|
||||
|
||||
> **Security warning**: domain-wide delegation allows the service account to impersonate **any user in the entire domain**. Restrict the scopes to the minimum required, and limit the service account's client ID to only the scopes listed above in the Admin Console (Security > API controls > Domain-wide delegation). A leaked service account key with broad scopes grants full access to every mailbox and calendar in the organization. Rotate keys on a schedule and monitor the Admin Console audit log for unexpected impersonation events.
|
||||
<Warning>
|
||||
Domain-wide delegation allows the service account to impersonate **any user in the entire domain**. Restrict the scopes to the minimum required, and limit the service account's client ID to only the scopes listed above in the Admin Console (Security > API controls > Domain-wide delegation). A leaked service account key with broad scopes grants full access to every mailbox and calendar in the organization. Rotate keys on a schedule and monitor the Admin Console audit log for unexpected impersonation events.
|
||||
</Warning>
|
||||
|
||||
### 3. Bind the delegate to channels
|
||||
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
---
|
||||
summary: "Background memory consolidation with light, deep, and REM phases plus a Dream Diary"
|
||||
title: "Dreaming"
|
||||
sidebarTitle: "Dreaming"
|
||||
read_when:
|
||||
- You want memory promotion to run automatically
|
||||
- You want to understand what each dreaming phase does
|
||||
- You want to tune consolidation without polluting MEMORY.md
|
||||
---
|
||||
|
||||
Dreaming is the background memory consolidation system in `memory-core`.
|
||||
It helps OpenClaw move strong short-term signals into durable memory while
|
||||
keeping the process explainable and reviewable.
|
||||
Dreaming is the background memory consolidation system in `memory-core`. It helps OpenClaw move strong short-term signals into durable memory while keeping the process explainable and reviewable.
|
||||
|
||||
<Note>
|
||||
Dreaming is **opt-in** and disabled by default.
|
||||
</Note>
|
||||
|
||||
## What dreaming writes
|
||||
|
||||
@@ -32,69 +33,63 @@ Dreaming uses three cooperative phases:
|
||||
| Deep | Score and promote durable candidates | Yes (`MEMORY.md`) |
|
||||
| REM | Reflect on themes and recurring ideas | No |
|
||||
|
||||
These phases are internal implementation details, not separate user-configured
|
||||
"modes."
|
||||
These phases are internal implementation details, not separate user-configured "modes."
|
||||
|
||||
### Light phase
|
||||
<AccordionGroup>
|
||||
<Accordion title="Light phase">
|
||||
Light phase ingests recent daily memory signals and recall traces, dedupes them, and stages candidate lines.
|
||||
|
||||
Light phase ingests recent daily memory signals and recall traces, dedupes them,
|
||||
and stages candidate lines.
|
||||
- Reads from short-term recall state, recent daily memory files, and redacted session transcripts when available.
|
||||
- Writes a managed `## Light Sleep` block when storage includes inline output.
|
||||
- Records reinforcement signals for later deep ranking.
|
||||
- Never writes to `MEMORY.md`.
|
||||
|
||||
- Reads from short-term recall state, recent daily memory files, and redacted session transcripts when available.
|
||||
- Writes a managed `## Light Sleep` block when storage includes inline output.
|
||||
- Records reinforcement signals for later deep ranking.
|
||||
- Never writes to `MEMORY.md`.
|
||||
</Accordion>
|
||||
<Accordion title="Deep phase">
|
||||
Deep phase decides what becomes long-term memory.
|
||||
|
||||
### Deep phase
|
||||
- Ranks candidates using weighted scoring and threshold gates.
|
||||
- Requires `minScore`, `minRecallCount`, and `minUniqueQueries` to pass.
|
||||
- Rehydrates snippets from live daily files before writing, so stale/deleted snippets are skipped.
|
||||
- Appends promoted entries to `MEMORY.md`.
|
||||
- Writes a `## Deep Sleep` summary into `DREAMS.md` and optionally writes `memory/dreaming/deep/YYYY-MM-DD.md`.
|
||||
|
||||
Deep phase decides what becomes long-term memory.
|
||||
</Accordion>
|
||||
<Accordion title="REM phase">
|
||||
REM phase extracts patterns and reflective signals.
|
||||
|
||||
- Ranks candidates using weighted scoring and threshold gates.
|
||||
- Requires `minScore`, `minRecallCount`, and `minUniqueQueries` to pass.
|
||||
- Rehydrates snippets from live daily files before writing, so stale/deleted snippets are skipped.
|
||||
- Appends promoted entries to `MEMORY.md`.
|
||||
- Writes a `## Deep Sleep` summary into `DREAMS.md` and optionally writes `memory/dreaming/deep/YYYY-MM-DD.md`.
|
||||
- Builds theme and reflection summaries from recent short-term traces.
|
||||
- Writes a managed `## REM Sleep` block when storage includes inline output.
|
||||
- Records REM reinforcement signals used by deep ranking.
|
||||
- Never writes to `MEMORY.md`.
|
||||
|
||||
### REM phase
|
||||
|
||||
REM phase extracts patterns and reflective signals.
|
||||
|
||||
- Builds theme and reflection summaries from recent short-term traces.
|
||||
- Writes a managed `## REM Sleep` block when storage includes inline output.
|
||||
- Records REM reinforcement signals used by deep ranking.
|
||||
- Never writes to `MEMORY.md`.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Session transcript ingestion
|
||||
|
||||
Dreaming can ingest redacted session transcripts into the dreaming corpus. When
|
||||
transcripts are available, they are fed into the light phase alongside daily
|
||||
memory signals and recall traces. Personal and sensitive content is redacted
|
||||
before ingestion.
|
||||
Dreaming can ingest redacted session transcripts into the dreaming corpus. When transcripts are available, they are fed into the light phase alongside daily memory signals and recall traces. Personal and sensitive content is redacted before ingestion.
|
||||
|
||||
## Dream Diary
|
||||
|
||||
Dreaming also keeps a narrative **Dream Diary** in `DREAMS.md`.
|
||||
After each phase has enough material, `memory-core` runs a best-effort background
|
||||
subagent turn (using the default runtime model) and appends a short diary entry.
|
||||
Dreaming also keeps a narrative **Dream Diary** in `DREAMS.md`. After each phase has enough material, `memory-core` runs a best-effort background subagent turn (using the default runtime model) and appends a short diary entry.
|
||||
|
||||
This diary is for human reading in the Dreams UI, not a promotion source.
|
||||
Dreaming-generated diary/report artifacts are excluded from short-term
|
||||
promotion. Only grounded memory snippets are eligible to promote into
|
||||
`MEMORY.md`.
|
||||
<Note>
|
||||
This diary is for human reading in the Dreams UI, not a promotion source. Dreaming-generated diary/report artifacts are excluded from short-term promotion. Only grounded memory snippets are eligible to promote into `MEMORY.md`.
|
||||
</Note>
|
||||
|
||||
There is also a grounded historical backfill lane for review and recovery work:
|
||||
|
||||
- `memory rem-harness --path ... --grounded` previews grounded diary output from historical `YYYY-MM-DD.md` notes.
|
||||
- `memory rem-backfill --path ...` writes reversible grounded diary entries into `DREAMS.md`.
|
||||
- `memory rem-backfill --path ... --stage-short-term` stages grounded durable candidates into the same short-term evidence store the normal deep phase already uses.
|
||||
- `memory rem-backfill --rollback` and `--rollback-short-term` remove those staged backfill artifacts without touching ordinary diary entries or live short-term recall.
|
||||
<AccordionGroup>
|
||||
<Accordion title="Backfill commands">
|
||||
- `memory rem-harness --path ... --grounded` previews grounded diary output from historical `YYYY-MM-DD.md` notes.
|
||||
- `memory rem-backfill --path ...` writes reversible grounded diary entries into `DREAMS.md`.
|
||||
- `memory rem-backfill --path ... --stage-short-term` stages grounded durable candidates into the same short-term evidence store the normal deep phase already uses.
|
||||
- `memory rem-backfill --rollback` and `--rollback-short-term` remove those staged backfill artifacts without touching ordinary diary entries or live short-term recall.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
The Control UI exposes the same diary backfill/reset flow so you can inspect
|
||||
results in the Dreams scene before deciding whether the grounded candidates
|
||||
deserve promotion. The Scene also shows a distinct grounded lane so you can see
|
||||
which staged short-term entries came from historical replay, which promoted
|
||||
items were grounded-led, and clear only grounded-only staged entries without
|
||||
touching ordinary live short-term state.
|
||||
The Control UI exposes the same diary backfill/reset flow so you can inspect results in the Dreams scene before deciding whether the grounded candidates deserve promotion. The Scene also shows a distinct grounded lane so you can see which staged short-term entries came from historical replay, which promoted items were grounded-led, and clear only grounded-only staged entries without touching ordinary live short-term state.
|
||||
|
||||
## Deep ranking signals
|
||||
|
||||
@@ -109,13 +104,11 @@ Deep ranking uses six weighted base signals plus phase reinforcement:
|
||||
| Consolidation | 0.10 | Multi-day recurrence strength |
|
||||
| Conceptual richness | 0.06 | Concept-tag density from snippet/path |
|
||||
|
||||
Light and REM phase hits add a small recency-decayed boost from
|
||||
`memory/.dreams/phase-signals.json`.
|
||||
Light and REM phase hits add a small recency-decayed boost from `memory/.dreams/phase-signals.json`.
|
||||
|
||||
## Scheduling
|
||||
|
||||
When enabled, `memory-core` auto-manages one cron job for a full dreaming
|
||||
sweep. Each sweep runs phases in order: light -> REM -> deep.
|
||||
When enabled, `memory-core` auto-manages one cron job for a full dreaming sweep. Each sweep runs phases in order: light → REM → deep.
|
||||
|
||||
Default cadence behavior:
|
||||
|
||||
@@ -125,43 +118,44 @@ Default cadence behavior:
|
||||
|
||||
## Quick start
|
||||
|
||||
Enable dreaming:
|
||||
|
||||
```json
|
||||
{
|
||||
"plugins": {
|
||||
"entries": {
|
||||
"memory-core": {
|
||||
"config": {
|
||||
"dreaming": {
|
||||
"enabled": true
|
||||
<Tabs>
|
||||
<Tab title="Enable dreaming">
|
||||
```json
|
||||
{
|
||||
"plugins": {
|
||||
"entries": {
|
||||
"memory-core": {
|
||||
"config": {
|
||||
"dreaming": {
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Enable dreaming with a custom sweep cadence:
|
||||
|
||||
```json
|
||||
{
|
||||
"plugins": {
|
||||
"entries": {
|
||||
"memory-core": {
|
||||
"config": {
|
||||
"dreaming": {
|
||||
"enabled": true,
|
||||
"timezone": "America/Los_Angeles",
|
||||
"frequency": "0 */6 * * *"
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Custom sweep cadence">
|
||||
```json
|
||||
{
|
||||
"plugins": {
|
||||
"entries": {
|
||||
"memory-core": {
|
||||
"config": {
|
||||
"dreaming": {
|
||||
"enabled": true,
|
||||
"timezone": "America/Los_Angeles",
|
||||
"frequency": "0 */6 * * *"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Slash command
|
||||
|
||||
@@ -174,47 +168,52 @@ Enable dreaming with a custom sweep cadence:
|
||||
|
||||
## CLI workflow
|
||||
|
||||
Use CLI promotion for preview or manual apply:
|
||||
<Tabs>
|
||||
<Tab title="Promotion preview / apply">
|
||||
```bash
|
||||
openclaw memory promote
|
||||
openclaw memory promote --apply
|
||||
openclaw memory promote --limit 5
|
||||
openclaw memory status --deep
|
||||
```
|
||||
|
||||
```bash
|
||||
openclaw memory promote
|
||||
openclaw memory promote --apply
|
||||
openclaw memory promote --limit 5
|
||||
openclaw memory status --deep
|
||||
```
|
||||
Manual `memory promote` uses deep-phase thresholds by default unless overridden with CLI flags.
|
||||
|
||||
Manual `memory promote` uses deep-phase thresholds by default unless overridden
|
||||
with CLI flags.
|
||||
</Tab>
|
||||
<Tab title="Explain promotion">
|
||||
Explain why a specific candidate would or would not promote:
|
||||
|
||||
Explain why a specific candidate would or would not promote:
|
||||
```bash
|
||||
openclaw memory promote-explain "router vlan"
|
||||
openclaw memory promote-explain "router vlan" --json
|
||||
```
|
||||
|
||||
```bash
|
||||
openclaw memory promote-explain "router vlan"
|
||||
openclaw memory promote-explain "router vlan" --json
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="REM harness preview">
|
||||
Preview REM reflections, candidate truths, and deep promotion output without writing anything:
|
||||
|
||||
Preview REM reflections, candidate truths, and deep promotion output without
|
||||
writing anything:
|
||||
```bash
|
||||
openclaw memory rem-harness
|
||||
openclaw memory rem-harness --json
|
||||
```
|
||||
|
||||
```bash
|
||||
openclaw memory rem-harness
|
||||
openclaw memory rem-harness --json
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Key defaults
|
||||
|
||||
All settings live under `plugins.entries.memory-core.config.dreaming`.
|
||||
|
||||
| Key | Default |
|
||||
| ----------- | ----------- |
|
||||
| `enabled` | `false` |
|
||||
| `frequency` | `0 3 * * *` |
|
||||
<ParamField path="enabled" type="boolean" default="false">
|
||||
Enable or disable the dreaming sweep.
|
||||
</ParamField>
|
||||
<ParamField path="frequency" type="string" default="0 3 * * *">
|
||||
Cron cadence for the full dreaming sweep.
|
||||
</ParamField>
|
||||
|
||||
Phase policy, thresholds, and storage behavior are internal implementation
|
||||
details (not user-facing config).
|
||||
|
||||
See [Memory configuration reference](/reference/memory-config#dreaming)
|
||||
for the full key list.
|
||||
<Note>
|
||||
Phase policy, thresholds, and storage behavior are internal implementation details (not user-facing config). See [Memory configuration reference](/reference/memory-config#dreaming) for the full key list.
|
||||
</Note>
|
||||
|
||||
## Dreams UI
|
||||
|
||||
@@ -230,6 +229,6 @@ When enabled, the Gateway **Dreams** tab shows:
|
||||
## Related
|
||||
|
||||
- [Memory](/concepts/memory)
|
||||
- [Memory Search](/concepts/memory-search)
|
||||
- [memory CLI](/cli/memory)
|
||||
- [Memory CLI](/cli/memory)
|
||||
- [Memory configuration reference](/reference/memory-config)
|
||||
- [Memory search](/concepts/memory-search)
|
||||
|
||||
@@ -7,18 +7,18 @@ read_when:
|
||||
---
|
||||
|
||||
OpenClaw remembers things by writing **plain Markdown files** in your agent's
|
||||
workspace. The model only "remembers" what gets saved to disk -- there is no
|
||||
workspace. The model only "remembers" what gets saved to disk — there is no
|
||||
hidden state.
|
||||
|
||||
## How it works
|
||||
|
||||
Your agent has three memory-related files:
|
||||
|
||||
- **`MEMORY.md`** -- long-term memory. Durable facts, preferences, and
|
||||
- **`MEMORY.md`** — long-term memory. Durable facts, preferences, and
|
||||
decisions. Loaded at the start of every DM session.
|
||||
- **`memory/YYYY-MM-DD.md`** -- daily notes. Running context and observations.
|
||||
- **`memory/YYYY-MM-DD.md`** — daily notes. Running context and observations.
|
||||
Today and yesterday's notes are loaded automatically.
|
||||
- **`DREAMS.md`** (optional) -- Dream Diary and dreaming sweep
|
||||
- **`DREAMS.md`** (optional) — Dream Diary and dreaming sweep
|
||||
summaries for human review, including grounded historical backfill entries.
|
||||
|
||||
These files live in the agent workspace (default `~/.openclaw/workspace`).
|
||||
@@ -32,9 +32,9 @@ prefer TypeScript." It will write it to the appropriate file.
|
||||
|
||||
The agent has two tools for working with memory:
|
||||
|
||||
- **`memory_search`** -- finds relevant notes using semantic search, even when
|
||||
- **`memory_search`** — finds relevant notes using semantic search, even when
|
||||
the wording differs from the original.
|
||||
- **`memory_get`** -- reads a specific memory file or line range.
|
||||
- **`memory_get`** — reads a specific memory file or line range.
|
||||
|
||||
Both tools are provided by the active memory plugin (default: `memory-core`).
|
||||
|
||||
@@ -61,7 +61,7 @@ See [Memory Wiki](/plugins/memory-wiki).
|
||||
## Memory search
|
||||
|
||||
When an embedding provider is configured, `memory_search` uses **hybrid
|
||||
search** -- combining vector similarity (semantic meaning) with keyword matching
|
||||
search** — combining vector similarity (semantic meaning) with keyword matching
|
||||
(exact terms like IDs and code symbols). This works out of the box once you have
|
||||
an API key for any supported provider.
|
||||
|
||||
@@ -104,7 +104,7 @@ dashboards, bridge mode, and Obsidian-friendly workflows.
|
||||
|
||||
Before [compaction](/concepts/compaction) summarizes your conversation, OpenClaw
|
||||
runs a silent turn that reminds the agent to save important context to memory
|
||||
files. This is on by default -- you do not need to configure anything.
|
||||
files. This is on by default — you do not need to configure anything.
|
||||
|
||||
<Tip>
|
||||
The memory flush prevents context loss during compaction. If your agent has
|
||||
@@ -176,16 +176,14 @@ openclaw memory index --force # Rebuild the index
|
||||
|
||||
## Further reading
|
||||
|
||||
- [Builtin Memory Engine](/concepts/memory-builtin) -- default SQLite backend
|
||||
- [QMD Memory Engine](/concepts/memory-qmd) -- advanced local-first sidecar
|
||||
- [Honcho Memory](/concepts/memory-honcho) -- AI-native cross-session memory
|
||||
- [Memory Wiki](/plugins/memory-wiki) -- compiled knowledge vault and wiki-native tools
|
||||
- [Memory Search](/concepts/memory-search) -- search pipeline, providers, and
|
||||
tuning
|
||||
- [Dreaming](/concepts/dreaming) -- background promotion
|
||||
from short-term recall to long-term memory
|
||||
- [Memory configuration reference](/reference/memory-config) -- all config knobs
|
||||
- [Compaction](/concepts/compaction) -- how compaction interacts with memory
|
||||
- [Builtin memory engine](/concepts/memory-builtin): default SQLite backend.
|
||||
- [QMD memory engine](/concepts/memory-qmd): advanced local-first sidecar.
|
||||
- [Honcho memory](/concepts/memory-honcho): AI-native cross-session memory.
|
||||
- [Memory Wiki](/plugins/memory-wiki): compiled knowledge vault and wiki-native tools.
|
||||
- [Memory search](/concepts/memory-search): search pipeline, providers, and tuning.
|
||||
- [Dreaming](/concepts/dreaming): background promotion from short-term recall to long-term memory.
|
||||
- [Memory configuration reference](/reference/memory-config): all config knobs.
|
||||
- [Compaction](/concepts/compaction): how compaction interacts with memory.
|
||||
|
||||
## Related
|
||||
|
||||
|
||||
@@ -7,8 +7,7 @@ read_when:
|
||||
title: "Messages"
|
||||
---
|
||||
|
||||
This page ties together how OpenClaw handles inbound messages, sessions, queueing,
|
||||
streaming, and reasoning visibility.
|
||||
OpenClaw handles inbound messages through a pipeline of session resolution, queueing, streaming, tool execution, and reasoning visibility. This page maps the path from inbound message to reply.
|
||||
|
||||
## Message flow (high level)
|
||||
|
||||
@@ -176,6 +175,11 @@ OpenClaw resolves that behavior by conversation type:
|
||||
- Groups/channels allow silence by default.
|
||||
- Internal orchestration allows silence by default.
|
||||
|
||||
OpenClaw also uses silent replies for internal runner failures that happen
|
||||
before any assistant reply in non-direct chats, so groups/channels do not see
|
||||
gateway error boilerplate. Direct chats show compact failure copy by default;
|
||||
raw runner details are shown only when `/verbose` is `on` or `full`.
|
||||
|
||||
Defaults live under `agents.defaults.silentReply` and
|
||||
`agents.defaults.silentReplyRewrite`; `surfaces.<id>.silentReply` and
|
||||
`surfaces.<id>.silentReplyRewrite` can override them per surface.
|
||||
|
||||
@@ -5,6 +5,7 @@ read_when:
|
||||
- Updating failover rules for auth profiles or models
|
||||
- Understanding how session model overrides interact with fallback retries
|
||||
title: "Model failover"
|
||||
sidebarTitle: "Model failover"
|
||||
---
|
||||
|
||||
OpenClaw handles failures in two stages:
|
||||
@@ -18,29 +19,31 @@ This doc explains the runtime rules and the data that backs them.
|
||||
|
||||
For a normal text run, OpenClaw evaluates candidates in this order:
|
||||
|
||||
1. The currently selected session model.
|
||||
2. Configured `agents.defaults.model.fallbacks` in order.
|
||||
3. The configured primary model at the end when the run started from an override.
|
||||
<Steps>
|
||||
<Step title="Resolve session state">
|
||||
Resolve the active session model and auth-profile preference.
|
||||
</Step>
|
||||
<Step title="Build candidate chain">
|
||||
Build the model candidate chain from the currently selected session model, then `agents.defaults.model.fallbacks` in order, ending with the configured primary when the run started from an override.
|
||||
</Step>
|
||||
<Step title="Try the current provider">
|
||||
Try the current provider with auth-profile rotation/cooldown rules.
|
||||
</Step>
|
||||
<Step title="Advance on failover-worthy errors">
|
||||
If that provider is exhausted with a failover-worthy error, move to the next model candidate.
|
||||
</Step>
|
||||
<Step title="Persist fallback override">
|
||||
Persist the selected fallback override before the retry starts so other session readers see the same provider/model the runner is about to use.
|
||||
</Step>
|
||||
<Step title="Roll back narrowly on failure">
|
||||
If the fallback candidate fails, roll back only the fallback-owned session override fields when they still match that failed candidate.
|
||||
</Step>
|
||||
<Step title="Throw FallbackSummaryError if exhausted">
|
||||
If every candidate fails, throw a `FallbackSummaryError` with per-attempt detail and the soonest cooldown expiry when one is known.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
Inside each candidate, OpenClaw tries auth-profile failover before advancing to
|
||||
the next model candidate.
|
||||
|
||||
High-level sequence:
|
||||
|
||||
1. Resolve the active session model and auth-profile preference.
|
||||
2. Build the model candidate chain.
|
||||
3. Try the current provider with auth-profile rotation/cooldown rules.
|
||||
4. If that provider is exhausted with a failover-worthy error, move to the next
|
||||
model candidate.
|
||||
5. Persist the selected fallback override before the retry starts so other
|
||||
session readers see the same provider/model the runner is about to use.
|
||||
6. If the fallback candidate fails, roll back only the fallback-owned session
|
||||
override fields when they still match that failed candidate.
|
||||
7. If every candidate fails, throw a `FallbackSummaryError` with per-attempt
|
||||
detail and the soonest cooldown expiry when one is known.
|
||||
|
||||
This is intentionally narrower than "save and restore the whole session". The
|
||||
reply runner only persists the model-selection fields it owns for fallback:
|
||||
This is intentionally narrower than "save and restore the whole session". The reply runner only persists the model-selection fields it owns for fallback:
|
||||
|
||||
- `providerOverride`
|
||||
- `modelOverride`
|
||||
@@ -48,9 +51,7 @@ reply runner only persists the model-selection fields it owns for fallback:
|
||||
- `authProfileOverrideSource`
|
||||
- `authProfileOverrideCompactionCount`
|
||||
|
||||
That prevents a failed fallback retry from overwriting newer unrelated session
|
||||
mutations such as manual `/model` changes or session rotation updates that
|
||||
happened while the attempt was running.
|
||||
That prevents a failed fallback retry from overwriting newer unrelated session mutations such as manual `/model` changes or session rotation updates that happened while the attempt was running.
|
||||
|
||||
## Auth storage (keys + OAuth)
|
||||
|
||||
@@ -61,7 +62,7 @@ OpenClaw uses **auth profiles** for both API keys and OAuth tokens.
|
||||
- Config `auth.profiles` / `auth.order` are **metadata + routing only** (no secrets).
|
||||
- Legacy import-only OAuth file: `~/.openclaw/credentials/oauth.json` (imported into `auth-profiles.json` on first use).
|
||||
|
||||
More detail: [/concepts/oauth](/concepts/oauth)
|
||||
More detail: [OAuth](/concepts/oauth)
|
||||
|
||||
Credential types:
|
||||
|
||||
@@ -81,9 +82,17 @@ Profiles live in `~/.openclaw/agents/<agentId>/agent/auth-profiles.json` under `
|
||||
|
||||
When a provider has multiple profiles, OpenClaw chooses an order like this:
|
||||
|
||||
1. **Explicit config**: `auth.order[provider]` (if set).
|
||||
2. **Configured profiles**: `auth.profiles` filtered by provider.
|
||||
3. **Stored profiles**: entries in `auth-profiles.json` for the provider.
|
||||
<Steps>
|
||||
<Step title="Explicit config">
|
||||
`auth.order[provider]` (if set).
|
||||
</Step>
|
||||
<Step title="Configured profiles">
|
||||
`auth.profiles` filtered by provider.
|
||||
</Step>
|
||||
<Step title="Stored profiles">
|
||||
Entries in `auth-profiles.json` for the provider.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
If no explicit order is configured, OpenClaw uses a round‑robin order:
|
||||
|
||||
@@ -93,20 +102,17 @@ If no explicit order is configured, OpenClaw uses a round‑robin order:
|
||||
|
||||
### Session stickiness (cache-friendly)
|
||||
|
||||
OpenClaw **pins the chosen auth profile per session** to keep provider caches warm.
|
||||
It does **not** rotate on every request. The pinned profile is reused until:
|
||||
OpenClaw **pins the chosen auth profile per session** to keep provider caches warm. It does **not** rotate on every request. The pinned profile is reused until:
|
||||
|
||||
- the session is reset (`/new` / `/reset`)
|
||||
- a compaction completes (compaction count increments)
|
||||
- the profile is in cooldown/disabled
|
||||
|
||||
Manual selection via `/model …@<profileId>` sets a **user override** for that session
|
||||
and is not auto‑rotated until a new session starts.
|
||||
Manual selection via `/model …@<profileId>` sets a **user override** for that session and is not auto-rotated until a new session starts.
|
||||
|
||||
Auto‑pinned profiles (selected by the session router) are treated as a **preference**:
|
||||
they are tried first, but OpenClaw may rotate to another profile on rate limits/timeouts.
|
||||
User‑pinned profiles stay locked to that profile; if it fails and model fallbacks
|
||||
are configured, OpenClaw moves to the next model instead of switching profiles.
|
||||
<Note>
|
||||
Auto-pinned profiles (selected by the session router) are treated as a **preference**: they are tried first, but OpenClaw may rotate to another profile on rate limits/timeouts. User-pinned profiles stay locked to that profile; if it fails and model fallbacks are configured, OpenClaw moves to the next model instead of switching profiles.
|
||||
</Note>
|
||||
|
||||
### Why OAuth can "look lost"
|
||||
|
||||
@@ -117,45 +123,31 @@ If you have both an OAuth profile and an API key profile for the same provider,
|
||||
|
||||
## Cooldowns
|
||||
|
||||
When a profile fails due to auth/rate‑limit errors (or a timeout that looks
|
||||
like rate limiting), OpenClaw marks it in cooldown and moves to the next profile.
|
||||
That rate-limit bucket is broader than plain `429`: it also includes provider
|
||||
messages such as `Too many concurrent requests`, `ThrottlingException`,
|
||||
`concurrency limit reached`, `workers_ai ... quota limit exceeded`,
|
||||
`throttled`, `resource exhausted`, and periodic usage-window limits such as
|
||||
`weekly/monthly limit reached`.
|
||||
Format/invalid‑request errors (for example Cloud Code Assist tool call ID
|
||||
validation failures) are treated as failover‑worthy and use the same cooldowns.
|
||||
OpenAI-compatible stop-reason errors such as `Unhandled stop reason: error`,
|
||||
`stop reason: error`, and `reason: error` are classified as timeout/failover
|
||||
signals.
|
||||
Generic server text can also land in that timeout bucket when the source matches
|
||||
a known transient pattern. For example, the bare pi-ai stream-wrapper message
|
||||
`An unknown error occurred` is treated as failover-worthy for every provider
|
||||
because pi-ai emits it when provider streams end with `stopReason: "aborted"` or
|
||||
`stopReason: "error"` without specific details. JSON `api_error` payloads with
|
||||
transient server text such as `internal server error`, `unknown error, 520`,
|
||||
`upstream error`, or `backend error` are also treated as failover-worthy
|
||||
timeouts.
|
||||
OpenRouter-specific generic upstream text such as bare `Provider returned error`
|
||||
is treated as timeout only when the provider context is actually OpenRouter.
|
||||
Generic internal fallback text such as `LLM request failed with an unknown
|
||||
error.` stays conservative and does not trigger failover by itself.
|
||||
When a profile fails due to auth/rate-limit errors (or a timeout that looks like rate limiting), OpenClaw marks it in cooldown and moves to the next profile.
|
||||
|
||||
Some provider SDKs may otherwise sleep for a long `Retry-After` window before
|
||||
returning control to OpenClaw. For Stainless-based SDKs such as Anthropic and
|
||||
OpenAI, OpenClaw caps SDK-internal `retry-after-ms` / `retry-after` waits at 60
|
||||
seconds by default and surfaces longer retryable responses immediately so this
|
||||
failover path can run. Tune or disable the cap with
|
||||
`OPENCLAW_SDK_RETRY_MAX_WAIT_SECONDS`; see [/concepts/retry](/concepts/retry).
|
||||
<AccordionGroup>
|
||||
<Accordion title="What lands in the rate-limit / timeout bucket">
|
||||
That rate-limit bucket is broader than plain `429`: it also includes provider messages such as `Too many concurrent requests`, `ThrottlingException`, `concurrency limit reached`, `workers_ai ... quota limit exceeded`, `throttled`, `resource exhausted`, and periodic usage-window limits such as `weekly/monthly limit reached`.
|
||||
|
||||
Rate-limit cooldowns can also be model-scoped:
|
||||
Format/invalid-request errors (for example Cloud Code Assist tool call ID validation failures) are treated as failover-worthy and use the same cooldowns. OpenAI-compatible stop-reason errors such as `Unhandled stop reason: error`, `stop reason: error`, and `reason: error` are classified as timeout/failover signals.
|
||||
|
||||
- OpenClaw records `cooldownModel` for rate-limit failures when the failing
|
||||
model id is known.
|
||||
- A sibling model on the same provider can still be tried when the cooldown is
|
||||
scoped to a different model.
|
||||
- Billing/disabled windows still block the whole profile across models.
|
||||
Generic server text can also land in that timeout bucket when the source matches a known transient pattern. For example, the bare pi-ai stream-wrapper message `An unknown error occurred` is treated as failover-worthy for every provider because pi-ai emits it when provider streams end with `stopReason: "aborted"` or `stopReason: "error"` without specific details. JSON `api_error` payloads with transient server text such as `internal server error`, `unknown error, 520`, `upstream error`, or `backend error` are also treated as failover-worthy timeouts.
|
||||
|
||||
OpenRouter-specific generic upstream text such as bare `Provider returned error` is treated as timeout only when the provider context is actually OpenRouter. Generic internal fallback text such as `LLM request failed with an unknown error.` stays conservative and does not trigger failover by itself.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="SDK retry-after caps">
|
||||
Some provider SDKs may otherwise sleep for a long `Retry-After` window before returning control to OpenClaw. For Stainless-based SDKs such as Anthropic and OpenAI, OpenClaw caps SDK-internal `retry-after-ms` / `retry-after` waits at 60 seconds by default and surfaces longer retryable responses immediately so this failover path can run. Tune or disable the cap with `OPENCLAW_SDK_RETRY_MAX_WAIT_SECONDS`; see [Retry behavior](/concepts/retry).
|
||||
</Accordion>
|
||||
<Accordion title="Model-scoped cooldowns">
|
||||
Rate-limit cooldowns can also be model-scoped:
|
||||
|
||||
- OpenClaw records `cooldownModel` for rate-limit failures when the failing model id is known.
|
||||
- A sibling model on the same provider can still be tried when the cooldown is scoped to a different model.
|
||||
- Billing/disabled windows still block the whole profile across models.
|
||||
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
Cooldowns use exponential backoff:
|
||||
|
||||
@@ -180,18 +172,13 @@ State is stored in `auth-state.json` under `usageStats`:
|
||||
|
||||
## Billing disables
|
||||
|
||||
Billing/credit failures (for example “insufficient credits” / “credit balance too low”) are treated as failover‑worthy, but they’re usually not transient. Instead of a short cooldown, OpenClaw marks the profile as **disabled** (with a longer backoff) and rotates to the next profile/provider.
|
||||
Billing/credit failures (for example "insufficient credits" / "credit balance too low") are treated as failover-worthy, but they're usually not transient. Instead of a short cooldown, OpenClaw marks the profile as **disabled** (with a longer backoff) and rotates to the next profile/provider.
|
||||
|
||||
Not every billing-shaped response is `402`, and not every HTTP `402` lands
|
||||
here. OpenClaw keeps explicit billing text in the billing lane even when a
|
||||
provider returns `401` or `403` instead, but provider-specific matchers stay
|
||||
scoped to the provider that owns them (for example OpenRouter `403 Key limit
|
||||
exceeded`). Meanwhile temporary `402` usage-window and
|
||||
organization/workspace spend-limit errors are classified as `rate_limit` when
|
||||
the message looks retryable (for example `weekly usage limit exhausted`, `daily
|
||||
limit reached, resets tomorrow`, or `organization spending limit exceeded`).
|
||||
Those stay on the short cooldown/failover path instead of the long
|
||||
billing-disable path.
|
||||
<Note>
|
||||
Not every billing-shaped response is `402`, and not every HTTP `402` lands here. OpenClaw keeps explicit billing text in the billing lane even when a provider returns `401` or `403` instead, but provider-specific matchers stay scoped to the provider that owns them (for example OpenRouter `403 Key limit exceeded`).
|
||||
|
||||
Meanwhile temporary `402` usage-window and organization/workspace spend-limit errors are classified as `rate_limit` when the message looks retryable (for example `weekly usage limit exhausted`, `daily limit reached, resets tomorrow`, or `organization spending limit exceeded`). Those stay on the short cooldown/failover path instead of the long billing-disable path.
|
||||
</Note>
|
||||
|
||||
State is stored in `auth-state.json`:
|
||||
|
||||
@@ -209,139 +196,115 @@ State is stored in `auth-state.json`:
|
||||
Defaults:
|
||||
|
||||
- Billing backoff starts at **5 hours**, doubles per billing failure, and caps at **24 hours**.
|
||||
- Backoff counters reset if the profile hasn’t failed for **24 hours** (configurable).
|
||||
- Backoff counters reset if the profile hasn't failed for **24 hours** (configurable).
|
||||
- Overloaded retries allow **1 same-provider profile rotation** before model fallback.
|
||||
- Overloaded retries use **0 ms backoff** by default.
|
||||
|
||||
## Model fallback
|
||||
|
||||
If all profiles for a provider fail, OpenClaw moves to the next model in
|
||||
`agents.defaults.model.fallbacks`. This applies to auth failures, rate limits, and
|
||||
timeouts that exhausted profile rotation (other errors do not advance fallback).
|
||||
If all profiles for a provider fail, OpenClaw moves to the next model in `agents.defaults.model.fallbacks`. This applies to auth failures, rate limits, and timeouts that exhausted profile rotation (other errors do not advance fallback).
|
||||
|
||||
Overloaded and rate-limit errors are handled more aggressively than billing
|
||||
cooldowns. By default, OpenClaw allows one same-provider auth-profile retry,
|
||||
then switches to the next configured model fallback without waiting.
|
||||
Provider-busy signals such as `ModelNotReadyException` land in that overloaded
|
||||
bucket. Tune this with `auth.cooldowns.overloadedProfileRotations`,
|
||||
`auth.cooldowns.overloadedBackoffMs`, and
|
||||
`auth.cooldowns.rateLimitedProfileRotations`.
|
||||
Overloaded and rate-limit errors are handled more aggressively than billing cooldowns. By default, OpenClaw allows one same-provider auth-profile retry, then switches to the next configured model fallback without waiting. Provider-busy signals such as `ModelNotReadyException` land in that overloaded bucket. Tune this with `auth.cooldowns.overloadedProfileRotations`, `auth.cooldowns.overloadedBackoffMs`, and `auth.cooldowns.rateLimitedProfileRotations`.
|
||||
|
||||
When a run starts with a model override (hooks or CLI), fallbacks still end at
|
||||
`agents.defaults.model.primary` after trying any configured fallbacks.
|
||||
When a run starts with a model override (hooks or CLI), fallbacks still end at `agents.defaults.model.primary` after trying any configured fallbacks.
|
||||
|
||||
### Candidate chain rules
|
||||
|
||||
OpenClaw builds the candidate list from the currently requested `provider/model`
|
||||
plus configured fallbacks.
|
||||
OpenClaw builds the candidate list from the currently requested `provider/model` plus configured fallbacks.
|
||||
|
||||
Rules:
|
||||
|
||||
- The requested model is always first.
|
||||
- Explicit configured fallbacks are deduplicated but not filtered by the model
|
||||
allowlist. They are treated as explicit operator intent.
|
||||
- If the current run is already on a configured fallback in the same provider
|
||||
family, OpenClaw keeps using the full configured chain.
|
||||
- If the current run is on a different provider than config and that current
|
||||
model is not already part of the configured fallback chain, OpenClaw does not
|
||||
append unrelated configured fallbacks from another provider.
|
||||
- When the run started from an override, the configured primary is appended at
|
||||
the end so the chain can settle back onto the normal default once earlier
|
||||
candidates are exhausted.
|
||||
<AccordionGroup>
|
||||
<Accordion title="Rules">
|
||||
- The requested model is always first.
|
||||
- Explicit configured fallbacks are deduplicated but not filtered by the model allowlist. They are treated as explicit operator intent.
|
||||
- If the current run is already on a configured fallback in the same provider family, OpenClaw keeps using the full configured chain.
|
||||
- If the current run is on a different provider than config and that current model is not already part of the configured fallback chain, OpenClaw does not append unrelated configured fallbacks from another provider.
|
||||
- When the run started from an override, the configured primary is appended at the end so the chain can settle back onto the normal default once earlier candidates are exhausted.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
### Which errors advance fallback
|
||||
|
||||
Model fallback continues on:
|
||||
|
||||
- auth failures
|
||||
- rate limits and cooldown exhaustion
|
||||
- overloaded/provider-busy errors
|
||||
- timeout-shaped failover errors
|
||||
- billing disables
|
||||
- `LiveSessionModelSwitchError`, which is normalized into a failover path so a
|
||||
stale persisted model does not create an outer retry loop
|
||||
- other unrecognized errors when there are still remaining candidates
|
||||
|
||||
Model fallback does not continue on:
|
||||
|
||||
- explicit aborts that are not timeout/failover-shaped
|
||||
- context overflow errors that should stay inside compaction/retry logic
|
||||
(for example `request_too_large`, `INVALID_ARGUMENT: input exceeds the maximum
|
||||
number of tokens`, `input token count exceeds the maximum number of input
|
||||
tokens`, `The input is too long for the model`, or `ollama error: context
|
||||
length exceeded`)
|
||||
- a final unknown error when there are no candidates left
|
||||
<Tabs>
|
||||
<Tab title="Continues on">
|
||||
- auth failures
|
||||
- rate limits and cooldown exhaustion
|
||||
- overloaded/provider-busy errors
|
||||
- timeout-shaped failover errors
|
||||
- billing disables
|
||||
- `LiveSessionModelSwitchError`, which is normalized into a failover path so a stale persisted model does not create an outer retry loop
|
||||
- other unrecognized errors when there are still remaining candidates
|
||||
</Tab>
|
||||
<Tab title="Does not continue on">
|
||||
- explicit aborts that are not timeout/failover-shaped
|
||||
- context overflow errors that should stay inside compaction/retry logic (for example `request_too_large`, `INVALID_ARGUMENT: input exceeds the maximum number of tokens`, `input token count exceeds the maximum number of input tokens`, `The input is too long for the model`, or `ollama error: context length exceeded`)
|
||||
- a final unknown error when there are no candidates left
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
### Cooldown skip vs probe behavior
|
||||
|
||||
When every auth profile for a provider is already in cooldown, OpenClaw does
|
||||
not automatically skip that provider forever. It makes a per-candidate decision:
|
||||
When every auth profile for a provider is already in cooldown, OpenClaw does not automatically skip that provider forever. It makes a per-candidate decision:
|
||||
|
||||
- Persistent auth failures skip the whole provider immediately.
|
||||
- Billing disables usually skip, but the primary candidate can still be probed
|
||||
on a throttle so recovery is possible without restarting.
|
||||
- The primary candidate may be probed near cooldown expiry, with a per-provider
|
||||
throttle.
|
||||
- Same-provider fallback siblings can be attempted despite cooldown when the
|
||||
failure looks transient (`rate_limit`, `overloaded`, or unknown). This is
|
||||
especially relevant when a rate limit is model-scoped and a sibling model may
|
||||
still recover immediately.
|
||||
- Transient cooldown probes are limited to one per provider per fallback run so
|
||||
a single provider does not stall cross-provider fallback.
|
||||
<AccordionGroup>
|
||||
<Accordion title="Per-candidate decisions">
|
||||
- Persistent auth failures skip the whole provider immediately.
|
||||
- Billing disables usually skip, but the primary candidate can still be probed on a throttle so recovery is possible without restarting.
|
||||
- The primary candidate may be probed near cooldown expiry, with a per-provider throttle.
|
||||
- Same-provider fallback siblings can be attempted despite cooldown when the failure looks transient (`rate_limit`, `overloaded`, or unknown). This is especially relevant when a rate limit is model-scoped and a sibling model may still recover immediately.
|
||||
- Transient cooldown probes are limited to one per provider per fallback run so a single provider does not stall cross-provider fallback.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Session overrides and live model switching
|
||||
|
||||
Session model changes are shared state. The active runner, `/model` command,
|
||||
compaction/session updates, and live-session reconciliation all read or write
|
||||
parts of the same session entry.
|
||||
Session model changes are shared state. The active runner, `/model` command, compaction/session updates, and live-session reconciliation all read or write parts of the same session entry.
|
||||
|
||||
That means fallback retries have to coordinate with live model switching:
|
||||
|
||||
- Only explicit user-driven model changes mark a pending live switch. That
|
||||
includes `/model`, `session_status(model=...)`, and `sessions.patch`.
|
||||
- System-driven model changes such as fallback rotation, heartbeat overrides,
|
||||
or compaction never mark a pending live switch on their own.
|
||||
- Before a fallback retry starts, the reply runner persists the selected
|
||||
fallback override fields to the session entry.
|
||||
- Live-session reconciliation prefers persisted session overrides over stale
|
||||
runtime model fields.
|
||||
- If the fallback attempt fails, the runner rolls back only the override fields
|
||||
it wrote, and only if they still match that failed candidate.
|
||||
- Only explicit user-driven model changes mark a pending live switch. That includes `/model`, `session_status(model=...)`, and `sessions.patch`.
|
||||
- System-driven model changes such as fallback rotation, heartbeat overrides, or compaction never mark a pending live switch on their own.
|
||||
- Before a fallback retry starts, the reply runner persists the selected fallback override fields to the session entry.
|
||||
- Live-session reconciliation prefers persisted session overrides over stale runtime model fields.
|
||||
- If a live-switch error points at a later candidate in the active fallback chain, OpenClaw jumps directly to that selected model instead of walking unrelated candidates first.
|
||||
- If the fallback attempt fails, the runner rolls back only the override fields it wrote, and only if they still match that failed candidate.
|
||||
|
||||
This prevents the classic race:
|
||||
|
||||
1. Primary fails.
|
||||
2. Fallback candidate is chosen in memory.
|
||||
3. Session store still says the old primary.
|
||||
4. Live-session reconciliation reads the stale session state.
|
||||
5. The retry gets snapped back to the old model before the fallback attempt
|
||||
starts.
|
||||
<Steps>
|
||||
<Step title="Primary fails">
|
||||
The selected primary model fails.
|
||||
</Step>
|
||||
<Step title="Fallback chosen in memory">
|
||||
Fallback candidate is chosen in memory.
|
||||
</Step>
|
||||
<Step title="Session store still says old primary">
|
||||
Session store still reflects the old primary.
|
||||
</Step>
|
||||
<Step title="Live reconciliation reads stale state">
|
||||
Live-session reconciliation reads the stale session state.
|
||||
</Step>
|
||||
<Step title="Retry snapped back">
|
||||
The retry gets snapped back to the old model before the fallback attempt starts.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
The persisted fallback override closes that window, and the narrow rollback
|
||||
keeps newer manual or runtime session changes intact.
|
||||
The persisted fallback override closes that window, and the narrow rollback keeps newer manual or runtime session changes intact.
|
||||
|
||||
## Observability and failure summaries
|
||||
|
||||
`runWithModelFallback(...)` records per-attempt details that feed logs and
|
||||
user-facing cooldown messaging:
|
||||
`runWithModelFallback(...)` records per-attempt details that feed logs and user-facing cooldown messaging:
|
||||
|
||||
- provider/model attempted
|
||||
- reason (`rate_limit`, `overloaded`, `billing`, `auth`, `model_not_found`, and
|
||||
similar failover reasons)
|
||||
- reason (`rate_limit`, `overloaded`, `billing`, `auth`, `model_not_found`, and similar failover reasons)
|
||||
- optional status/code
|
||||
- human-readable error summary
|
||||
|
||||
When every candidate fails, OpenClaw throws `FallbackSummaryError`. The outer
|
||||
reply runner can use that to build a more specific message such as "all models
|
||||
are temporarily rate-limited" and include the soonest cooldown expiry when one
|
||||
is known.
|
||||
When every candidate fails, OpenClaw throws `FallbackSummaryError`. The outer reply runner can use that to build a more specific message such as "all models are temporarily rate-limited" and include the soonest cooldown expiry when one is known.
|
||||
|
||||
That cooldown summary is model-aware:
|
||||
|
||||
- unrelated model-scoped rate limits are ignored for the attempted
|
||||
provider/model chain
|
||||
- if the remaining block is a matching model-scoped rate limit, OpenClaw
|
||||
reports the last matching expiry that still blocks that model
|
||||
- unrelated model-scoped rate limits are ignored for the attempted provider/model chain
|
||||
- if the remaining block is a matching model-scoped rate limit, OpenClaw reports the last matching expiry that still blocks that model
|
||||
|
||||
## Related config
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ Reference for **LLM/model providers** (not chat channels like WhatsApp/Telegram)
|
||||
- Model refs use `provider/model` (example: `opencode/claude-opus-4-6`).
|
||||
- `agents.defaults.models` acts as an allowlist when set.
|
||||
- CLI helpers: `openclaw onboard`, `openclaw models list`, `openclaw models set <provider/model>`.
|
||||
- `models.providers.*.models[].contextWindow` is native model metadata; `contextTokens` is the effective runtime cap.
|
||||
- `models.providers.*.contextWindow` / `contextTokens` / `maxTokens` set provider-level defaults; `models.providers.*.models[].contextWindow` / `contextTokens` / `maxTokens` override them per model.
|
||||
- Fallback rules, cooldown probes, and session-override persistence: [Model failover](/concepts/model-failover).
|
||||
</Accordion>
|
||||
<Accordion title="OpenAI provider/runtime split">
|
||||
@@ -24,17 +24,17 @@ Reference for **LLM/model providers** (not chat channels like WhatsApp/Telegram)
|
||||
|
||||
- `openai/<model>` uses the direct OpenAI API-key provider in PI.
|
||||
- `openai-codex/<model>` uses Codex OAuth in PI.
|
||||
- `openai/<model>` plus `agents.defaults.embeddedHarness.runtime: "codex"` uses the native Codex app-server harness.
|
||||
- `openai/<model>` plus `agents.defaults.agentRuntime.id: "codex"` uses the native Codex app-server harness.
|
||||
|
||||
See [OpenAI](/providers/openai) and [Codex harness](/plugins/codex-harness). If the provider/runtime split is confusing, read [Agent runtimes](/concepts/agent-runtimes) first.
|
||||
|
||||
Plugin auto-enable follows the same boundary: `openai-codex/<model>` belongs to the OpenAI plugin, while the Codex plugin is enabled by `embeddedHarness.runtime: "codex"` or legacy `codex/<model>` refs.
|
||||
Plugin auto-enable follows the same boundary: `openai-codex/<model>` belongs to the OpenAI plugin, while the Codex plugin is enabled by `agentRuntime.id: "codex"` or legacy `codex/<model>` refs.
|
||||
|
||||
GPT-5.5 is available through `openai/gpt-5.5` for direct API-key traffic, `openai-codex/gpt-5.5` in PI for Codex OAuth, and the native Codex app-server harness when `embeddedHarness.runtime: "codex"` is set.
|
||||
GPT-5.5 is available through `openai/gpt-5.5` for direct API-key traffic, `openai-codex/gpt-5.5` in PI for Codex OAuth, and the native Codex app-server harness when `agentRuntime.id: "codex"` is set.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="CLI runtimes">
|
||||
CLI runtimes use the same split: choose canonical model refs such as `anthropic/claude-*`, `google/gemini-*`, or `openai/gpt-*`, then set `agents.defaults.embeddedHarness.runtime` to `claude-cli`, `google-gemini-cli`, or `codex-cli` when you want a local CLI backend.
|
||||
CLI runtimes use the same split: choose canonical model refs such as `anthropic/claude-*`, `google/gemini-*`, or `openai/gpt-*`, then set `agents.defaults.agentRuntime.id` to `claude-cli`, `google-gemini-cli`, or `codex-cli` when you want a local CLI backend.
|
||||
|
||||
Legacy `claude-cli/*`, `google-gemini-cli/*`, and `codex-cli/*` refs migrate back to canonical provider refs with the runtime recorded separately.
|
||||
|
||||
@@ -108,6 +108,10 @@ OpenClaw ships with the pi‑ai catalog. These providers require **no** `models.
|
||||
- Example model: `anthropic/claude-opus-4-6`
|
||||
- CLI: `openclaw onboard --auth-choice apiKey`
|
||||
- Direct public Anthropic requests support the shared `/fast` toggle and `params.fastMode`, including API-key and OAuth-authenticated traffic sent to `api.anthropic.com`; OpenClaw maps that to Anthropic `service_tier` (`auto` vs `standard_only`)
|
||||
- Preferred Claude CLI config keeps the model ref canonical and selects the CLI
|
||||
backend separately: `anthropic/claude-opus-4-7` with
|
||||
`agents.defaults.agentRuntime.id: "claude-cli"`. Legacy
|
||||
`claude-cli/claude-opus-4-7` refs still work for compatibility.
|
||||
|
||||
<Note>
|
||||
Anthropic staff told us OpenClaw-style Claude CLI usage is allowed again, so OpenClaw treats Claude CLI reuse and `claude -p` usage as sanctioned for this integration unless Anthropic publishes a new policy. Anthropic setup-token remains available as a supported OpenClaw token path, but OpenClaw now prefers Claude CLI reuse and `claude -p` when available.
|
||||
@@ -124,7 +128,7 @@ Anthropic staff told us OpenClaw-style Claude CLI usage is allowed again, so Ope
|
||||
- Provider: `openai-codex`
|
||||
- Auth: OAuth (ChatGPT)
|
||||
- PI model ref: `openai-codex/gpt-5.5`
|
||||
- Native Codex app-server harness ref: `openai/gpt-5.5` with `agents.defaults.embeddedHarness.runtime: "codex"`
|
||||
- Native Codex app-server harness ref: `openai/gpt-5.5` with `agents.defaults.agentRuntime.id: "codex"`
|
||||
- Native Codex app-server harness docs: [Codex harness](/plugins/codex-harness)
|
||||
- Legacy model refs: `codex/gpt-*`
|
||||
- Plugin boundary: `openai-codex/*` loads the OpenAI plugin; the native Codex app-server plugin is selected only by the Codex harness runtime or legacy `codex/*` refs.
|
||||
@@ -621,6 +625,7 @@ Example (OpenAI‑compatible):
|
||||
baseUrl: "http://localhost:1234/v1",
|
||||
apiKey: "${LM_API_TOKEN}",
|
||||
api: "openai-completions",
|
||||
timeoutSeconds: 300,
|
||||
models: [
|
||||
{
|
||||
id: "my-local-model",
|
||||
@@ -656,6 +661,7 @@ Example (OpenAI‑compatible):
|
||||
- Proxy-style OpenAI-compatible routes also skip native OpenAI-only request shaping: no `service_tier`, no Responses `store`, no Completions `store`, no prompt-cache hints, no OpenAI reasoning-compat payload shaping, and no hidden OpenClaw attribution headers.
|
||||
- For OpenAI-compatible Completions proxies that need vendor-specific fields, set `agents.defaults.models["provider/model"].params.extra_body` (or `extraBody`) to merge extra JSON into the outbound request body.
|
||||
- For vLLM chat-template controls, set `agents.defaults.models["provider/model"].params.chat_template_kwargs`. OpenClaw automatically sends `enable_thinking: false` and `force_nonempty_content: true` for `vllm/nemotron-3-*` when the session thinking level is off.
|
||||
- For slow local models or remote LAN/tailnet hosts, set `models.providers.<id>.timeoutSeconds`. This extends provider model HTTP request handling, including connect, headers, body streaming, and the total guarded-fetch abort, without increasing the whole agent runtime timeout.
|
||||
- If `baseUrl` is empty/omitted, OpenClaw keeps the default OpenAI behavior (which resolves to `api.openai.com`).
|
||||
- For safety, an explicit `compat.supportsDeveloperRole: true` is still overridden on non-native `openai-completions` endpoints.
|
||||
</Accordion>
|
||||
|
||||
@@ -5,37 +5,53 @@ read_when:
|
||||
- Changing model fallback behavior or selection UX
|
||||
- Updating model scan probes (tools/images)
|
||||
title: "Models CLI"
|
||||
sidebarTitle: "Models CLI"
|
||||
---
|
||||
|
||||
See [/concepts/model-failover](/concepts/model-failover) for auth profile
|
||||
rotation, cooldowns, and how that interacts with fallbacks.
|
||||
Quick provider overview + examples: [/concepts/model-providers](/concepts/model-providers).
|
||||
Model refs choose a provider and model. They do not usually choose the
|
||||
low-level agent runtime. For example, `openai/gpt-5.5` can run through the
|
||||
normal OpenAI provider path or through the Codex app-server runtime, depending
|
||||
on `agents.defaults.embeddedHarness.runtime`. See
|
||||
[/concepts/agent-runtimes](/concepts/agent-runtimes).
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Model failover" href="/concepts/model-failover">
|
||||
Auth profile rotation, cooldowns, and how that interacts with fallbacks.
|
||||
</Card>
|
||||
<Card title="Model providers" href="/concepts/model-providers">
|
||||
Quick provider overview and examples.
|
||||
</Card>
|
||||
<Card title="Agent runtimes" href="/concepts/agent-runtimes">
|
||||
PI, Codex, and other agent loop runtimes.
|
||||
</Card>
|
||||
<Card title="Configuration reference" href="/gateway/config-agents#agent-defaults">
|
||||
Model config keys.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
Model refs choose a provider and model. They do not usually choose the low-level agent runtime. For example, `openai/gpt-5.5` can run through the normal OpenAI provider path or through the Codex app-server runtime, depending on `agents.defaults.agentRuntime.id`. See [Agent runtimes](/concepts/agent-runtimes).
|
||||
|
||||
## How model selection works
|
||||
|
||||
OpenClaw selects models in this order:
|
||||
|
||||
1. **Primary** model (`agents.defaults.model.primary` or `agents.defaults.model`).
|
||||
2. **Fallbacks** in `agents.defaults.model.fallbacks` (in order).
|
||||
3. **Provider auth failover** happens inside a provider before moving to the
|
||||
next model.
|
||||
<Steps>
|
||||
<Step title="Primary model">
|
||||
`agents.defaults.model.primary` (or `agents.defaults.model`).
|
||||
</Step>
|
||||
<Step title="Fallbacks">
|
||||
`agents.defaults.model.fallbacks` (in order).
|
||||
</Step>
|
||||
<Step title="Provider auth failover">
|
||||
Auth failover happens inside a provider before moving to the next model.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
Related:
|
||||
|
||||
- `agents.defaults.models` is the allowlist/catalog of models OpenClaw can use (plus aliases).
|
||||
- `agents.defaults.imageModel` is used **only when** the primary model can’t accept images.
|
||||
- `agents.defaults.pdfModel` is used by the `pdf` tool. If omitted, the tool
|
||||
falls back to `agents.defaults.imageModel`, then the resolved session/default
|
||||
model.
|
||||
- `agents.defaults.imageGenerationModel` is used by the shared image-generation capability. If omitted, `image_generate` can still infer an auth-backed provider default. It tries the current default provider first, then the remaining registered image-generation providers in provider-id order. If you set a specific provider/model, also configure that provider's auth/API key.
|
||||
- `agents.defaults.musicGenerationModel` is used by the shared music-generation capability. If omitted, `music_generate` can still infer an auth-backed provider default. It tries the current default provider first, then the remaining registered music-generation providers in provider-id order. If you set a specific provider/model, also configure that provider's auth/API key.
|
||||
- `agents.defaults.videoGenerationModel` is used by the shared video-generation capability. If omitted, `video_generate` can still infer an auth-backed provider default. It tries the current default provider first, then the remaining registered video-generation providers in provider-id order. If you set a specific provider/model, also configure that provider's auth/API key.
|
||||
- Per-agent defaults can override `agents.defaults.model` via `agents.list[].model` plus bindings (see [/concepts/multi-agent](/concepts/multi-agent)).
|
||||
<AccordionGroup>
|
||||
<Accordion title="Related model surfaces">
|
||||
- `agents.defaults.models` is the allowlist/catalog of models OpenClaw can use (plus aliases).
|
||||
- `agents.defaults.imageModel` is used **only when** the primary model can't accept images.
|
||||
- `agents.defaults.pdfModel` is used by the `pdf` tool. If omitted, the tool falls back to `agents.defaults.imageModel`, then the resolved session/default model.
|
||||
- `agents.defaults.imageGenerationModel` is used by the shared image-generation capability. If omitted, `image_generate` can still infer an auth-backed provider default. It tries the current default provider first, then the remaining registered image-generation providers in provider-id order. If you set a specific provider/model, also configure that provider's auth/API key.
|
||||
- `agents.defaults.musicGenerationModel` is used by the shared music-generation capability. If omitted, `music_generate` can still infer an auth-backed provider default. It tries the current default provider first, then the remaining registered music-generation providers in provider-id order. If you set a specific provider/model, also configure that provider's auth/API key.
|
||||
- `agents.defaults.videoGenerationModel` is used by the shared video-generation capability. If omitted, `video_generate` can still infer an auth-backed provider default. It tries the current default provider first, then the remaining registered video-generation providers in provider-id order. If you set a specific provider/model, also configure that provider's auth/API key.
|
||||
- Per-agent defaults can override `agents.defaults.model` via `agents.list[].model` plus bindings (see [Multi-agent routing](/concepts/multi-agent)).
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Quick model policy
|
||||
|
||||
@@ -45,14 +61,13 @@ Related:
|
||||
|
||||
## Onboarding (recommended)
|
||||
|
||||
If you don’t want to hand-edit config, run onboarding:
|
||||
If you don't want to hand-edit config, run onboarding:
|
||||
|
||||
```bash
|
||||
openclaw onboard
|
||||
```
|
||||
|
||||
It can set up model + auth for common providers, including **OpenAI Code (Codex)
|
||||
subscription** (OAuth) and **Anthropic** (API key or Claude CLI).
|
||||
It can set up model + auth for common providers, including **OpenAI Code (Codex) subscription** (OAuth) and **Anthropic** (API key or Claude CLI).
|
||||
|
||||
## Config keys (overview)
|
||||
|
||||
@@ -64,11 +79,11 @@ subscription** (OAuth) and **Anthropic** (API key or Claude CLI).
|
||||
- `agents.defaults.models` (allowlist + aliases + provider params)
|
||||
- `models.providers` (custom providers written into `models.json`)
|
||||
|
||||
Model refs are normalized to lowercase. Provider aliases like `z.ai/*` normalize
|
||||
to `zai/*`.
|
||||
<Note>
|
||||
Model refs are normalized to lowercase. Provider aliases like `z.ai/*` normalize to `zai/*`.
|
||||
|
||||
Provider configuration examples (including OpenCode) live in
|
||||
[/providers/opencode](/providers/opencode).
|
||||
Provider configuration examples (including OpenCode) live in [OpenCode](/providers/opencode).
|
||||
</Note>
|
||||
|
||||
### Safe allowlist edits
|
||||
|
||||
@@ -78,36 +93,30 @@ Use additive writes when updating `agents.defaults.models` by hand:
|
||||
openclaw config set agents.defaults.models '{"openai/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.
|
||||
<AccordionGroup>
|
||||
<Accordion title="Clobber protection rules">
|
||||
`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.
|
||||
Configure preserves an existing `agents.defaults.model.primary` when provider
|
||||
auth is re-applied. Explicit default-setting commands such as
|
||||
`openclaw models auth login --provider <id> --set-default` and
|
||||
`openclaw models set <model>` still replace `agents.defaults.model.primary`.
|
||||
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. Configure preserves an existing `agents.defaults.model.primary` when provider auth is re-applied. Explicit default-setting commands such as `openclaw models auth login --provider <id> --set-default` and `openclaw models set <model>` still replace `agents.defaults.model.primary`.
|
||||
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## "Model is not allowed" (and why replies stop)
|
||||
|
||||
If `agents.defaults.models` is set, it becomes the **allowlist** for `/model` and for
|
||||
session overrides. When a user selects a model that isn’t in that allowlist,
|
||||
OpenClaw returns:
|
||||
If `agents.defaults.models` is set, it becomes the **allowlist** for `/model` and for session overrides. When a user selects a model that isn't in that allowlist, OpenClaw returns:
|
||||
|
||||
```
|
||||
Model "provider/model" is not allowed. Use /model to list available models.
|
||||
```
|
||||
|
||||
This happens **before** a normal reply is generated, so the message can feel
|
||||
like it “didn’t respond.” The fix is to either:
|
||||
<Warning>
|
||||
This happens **before** a normal reply is generated, so the message can feel like it "didn't respond." The fix is to either:
|
||||
|
||||
- Add the model to `agents.defaults.models`, or
|
||||
- Clear the allowlist (remove `agents.defaults.models`), or
|
||||
- Pick a model from `/model list`.
|
||||
</Warning>
|
||||
|
||||
Example allowlist config:
|
||||
|
||||
@@ -135,26 +144,29 @@ You can switch models for the current session without restarting:
|
||||
/model status
|
||||
```
|
||||
|
||||
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 deprecated and now returns a deprecation message instead of registering models from chat.
|
||||
- `/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.
|
||||
- If a run is already active, OpenClaw marks a live switch as pending and only restarts into the new model at a clean retry point.
|
||||
- If tool activity or reply output has already started, the pending switch can stay queued until a later retry opportunity or the next user turn.
|
||||
- `/model status` is the detailed view (auth candidates and, when configured, provider endpoint `baseUrl` + `api` mode).
|
||||
- Model refs are parsed by splitting on the **first** `/`. Use `provider/model` when typing `/model <ref>`.
|
||||
- If the model ID itself contains `/` (OpenRouter-style), you must include the provider prefix (example: `/model openrouter/moonshotai/kimi-k2`).
|
||||
- If you omit the provider, OpenClaw resolves the input in this order:
|
||||
1. alias match
|
||||
2. unique configured-provider match for that exact unprefixed model id
|
||||
3. deprecated fallback to the configured default provider
|
||||
If that provider no longer exposes the configured default model, OpenClaw
|
||||
instead falls back to the first configured provider/model to avoid
|
||||
surfacing a stale removed-provider default.
|
||||
<AccordionGroup>
|
||||
<Accordion title="Picker behavior">
|
||||
- `/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 deprecated and now returns a deprecation message instead of registering models from chat.
|
||||
- `/model <#>` selects from that picker.
|
||||
</Accordion>
|
||||
<Accordion title="Persistence and live switching">
|
||||
- `/model` persists the new session selection immediately.
|
||||
- If the agent is idle, the next run uses the new model right away.
|
||||
- If a run is already active, OpenClaw marks a live switch as pending and only restarts into the new model at a clean retry point.
|
||||
- If tool activity or reply output has already started, the pending switch can stay queued until a later retry opportunity or the next user turn.
|
||||
- `/model status` is the detailed view (auth candidates and, when configured, provider endpoint `baseUrl` + `api` mode).
|
||||
</Accordion>
|
||||
<Accordion title="Ref parsing">
|
||||
- Model refs are parsed by splitting on the **first** `/`. Use `provider/model` when typing `/model <ref>`.
|
||||
- If the model ID itself contains `/` (OpenRouter-style), you must include the provider prefix (example: `/model openrouter/moonshotai/kimi-k2`).
|
||||
- If you omit the provider, OpenClaw resolves the input in this order:
|
||||
1. alias match
|
||||
2. unique configured-provider match for that exact unprefixed model id
|
||||
3. deprecated fallback to the configured default provider — if that provider no longer exposes the configured default model, OpenClaw instead falls back to the first configured provider/model to avoid surfacing a stale removed-provider default.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
Full command behavior/config: [Slash commands](/tools/slash-commands).
|
||||
|
||||
@@ -187,38 +199,39 @@ openclaw models image-fallbacks clear
|
||||
|
||||
Shows configured models by default. Useful flags:
|
||||
|
||||
- `--all`: full catalog
|
||||
- `--local`: local providers only
|
||||
- `--provider <id>`: filter by provider id, for example `moonshot`; display
|
||||
labels from interactive pickers are not accepted
|
||||
- `--plain`: one model per line
|
||||
- `--json`: machine‑readable 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.
|
||||
<ParamField path="--all" type="boolean">
|
||||
Full catalog. 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.
|
||||
</ParamField>
|
||||
<ParamField path="--local" type="boolean">
|
||||
Local providers only.
|
||||
</ParamField>
|
||||
<ParamField path="--provider <id>" type="string">
|
||||
Filter by provider id, for example `moonshot`. Display labels from interactive pickers are not accepted.
|
||||
</ParamField>
|
||||
<ParamField path="--plain" type="boolean">
|
||||
One model per line.
|
||||
</ParamField>
|
||||
<ParamField path="--json" type="boolean">
|
||||
Machine-readable output.
|
||||
</ParamField>
|
||||
|
||||
### `models status`
|
||||
|
||||
Shows the resolved primary model, fallbacks, image model, and an auth overview
|
||||
of configured providers. It also surfaces OAuth expiry status for profiles found
|
||||
in the auth store (warns within 24h by default). `--plain` prints only the
|
||||
resolved primary model.
|
||||
OAuth status is always shown (and included in `--json` output). If a configured
|
||||
provider has no credentials, `models status` prints a **Missing auth** section.
|
||||
JSON includes `auth.oauth` (warn window + profiles) and `auth.providers`
|
||||
(effective auth per provider, including env-backed credentials). `auth.oauth`
|
||||
is auth-store profile health only; env-only providers do not appear there.
|
||||
Use `--check` for automation (exit `1` when missing/expired, `2` when expiring).
|
||||
Use `--probe` for live auth checks; probe rows can come from auth profiles, env
|
||||
credentials, or `models.json`.
|
||||
If explicit `auth.order.<provider>` omits a stored profile, probe reports
|
||||
`excluded_by_auth_order` instead of trying it. If auth exists but no probeable
|
||||
model can be resolved for that provider, probe reports `status: no_model`.
|
||||
Shows the resolved primary model, fallbacks, image model, and an auth overview of configured providers. It also surfaces OAuth expiry status for profiles found in the auth store (warns within 24h by default). `--plain` prints only the resolved primary model.
|
||||
|
||||
Auth choice is provider/account dependent. For always-on gateway hosts, API
|
||||
keys are usually the most predictable; Claude CLI reuse and existing Anthropic
|
||||
OAuth/token profiles are also supported.
|
||||
<AccordionGroup>
|
||||
<Accordion title="Auth and probe behavior">
|
||||
- OAuth status is always shown (and included in `--json` output). If a configured provider has no credentials, `models status` prints a **Missing auth** section.
|
||||
- JSON includes `auth.oauth` (warn window + profiles) and `auth.providers` (effective auth per provider, including env-backed credentials). `auth.oauth` is auth-store profile health only; env-only providers do not appear there.
|
||||
- Use `--check` for automation (exit `1` when missing/expired, `2` when expiring).
|
||||
- Use `--probe` for live auth checks; probe rows can come from auth profiles, env credentials, or `models.json`.
|
||||
- If explicit `auth.order.<provider>` omits a stored profile, probe reports `excluded_by_auth_order` instead of trying it. If auth exists but no probeable model can be resolved for that provider, probe reports `status: no_model`.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
<Note>
|
||||
Auth choice is provider/account dependent. For always-on gateway hosts, API keys are usually the most predictable; Claude CLI reuse and existing Anthropic OAuth/token profiles are also supported.
|
||||
</Note>
|
||||
|
||||
Example (Claude CLI):
|
||||
|
||||
@@ -229,24 +242,33 @@ openclaw models status
|
||||
|
||||
## Scanning (OpenRouter free models)
|
||||
|
||||
`openclaw models scan` inspects OpenRouter’s **free model catalog** and can
|
||||
optionally probe models for tool and image support.
|
||||
`openclaw models scan` inspects OpenRouter's **free model catalog** and can optionally probe models for tool and image support.
|
||||
|
||||
Key flags:
|
||||
<ParamField path="--no-probe" type="boolean">
|
||||
Skip live probes (metadata only).
|
||||
</ParamField>
|
||||
<ParamField path="--min-params <b>" type="number">
|
||||
Minimum parameter size (billions).
|
||||
</ParamField>
|
||||
<ParamField path="--max-age-days <days>" type="number">
|
||||
Skip older models.
|
||||
</ParamField>
|
||||
<ParamField path="--provider <name>" type="string">
|
||||
Provider prefix filter.
|
||||
</ParamField>
|
||||
<ParamField path="--max-candidates <n>" type="number">
|
||||
Fallback list size.
|
||||
</ParamField>
|
||||
<ParamField path="--set-default" type="boolean">
|
||||
Set `agents.defaults.model.primary` to the first selection.
|
||||
</ParamField>
|
||||
<ParamField path="--set-image" type="boolean">
|
||||
Set `agents.defaults.imageModel.primary` to the first image selection.
|
||||
</ParamField>
|
||||
|
||||
- `--no-probe`: skip live probes (metadata only)
|
||||
- `--min-params <b>`: minimum parameter size (billions)
|
||||
- `--max-age-days <days>`: skip older models
|
||||
- `--provider <name>`: provider prefix filter
|
||||
- `--max-candidates <n>`: fallback list size
|
||||
- `--set-default`: set `agents.defaults.model.primary` to the first selection
|
||||
- `--set-image`: set `agents.defaults.imageModel.primary` to the first image selection
|
||||
|
||||
The OpenRouter `/models` catalog is public, so metadata-only scans can list
|
||||
free candidates without a key. Probing and inference still require an
|
||||
OpenRouter API key (from auth profiles or `OPENROUTER_API_KEY`). If no key is
|
||||
available, `openclaw models scan` falls back to metadata-only output and leaves
|
||||
config unchanged. Use `--no-probe` to request metadata-only mode explicitly.
|
||||
<Note>
|
||||
The OpenRouter `/models` catalog is public, so metadata-only scans can list free candidates without a key. Probing and inference still require an OpenRouter API key (from auth profiles or `OPENROUTER_API_KEY`). If no key is available, `openclaw models scan` falls back to metadata-only output and leaves config unchanged. Use `--no-probe` to request metadata-only mode explicitly.
|
||||
</Note>
|
||||
|
||||
Scan results are ranked by:
|
||||
|
||||
@@ -255,42 +277,43 @@ Scan results are ranked by:
|
||||
3. Context size
|
||||
4. Parameter count
|
||||
|
||||
Input
|
||||
Input:
|
||||
|
||||
- OpenRouter `/models` list (filter `:free`)
|
||||
- Live probes require OpenRouter API key from auth profiles or `OPENROUTER_API_KEY` (see [/environment](/help/environment))
|
||||
- Live probes require OpenRouter API key from auth profiles or `OPENROUTER_API_KEY` (see [Environment variables](/help/environment))
|
||||
- Optional filters: `--max-age-days`, `--min-params`, `--provider`, `--max-candidates`
|
||||
- Request/probe controls: `--timeout`, `--concurrency`
|
||||
|
||||
When live probes run in a TTY, you can select fallbacks interactively. In
|
||||
non‑interactive mode, pass `--yes` to accept defaults. Metadata-only results are
|
||||
informational; `--set-default` and `--set-image` require live probes so
|
||||
OpenClaw does not configure an unusable keyless OpenRouter model.
|
||||
When live probes run in a TTY, you can select fallbacks interactively. In non-interactive mode, pass `--yes` to accept defaults. Metadata-only results are informational; `--set-default` and `--set-image` require live probes so OpenClaw does not configure an unusable keyless OpenRouter model.
|
||||
|
||||
## Models registry (`models.json`)
|
||||
|
||||
Custom providers in `models.providers` are written into `models.json` under the
|
||||
agent directory (default `~/.openclaw/agents/<agentId>/agent/models.json`). This file
|
||||
is merged by default unless `models.mode` is set to `replace`.
|
||||
Custom providers in `models.providers` are written into `models.json` under the agent directory (default `~/.openclaw/agents/<agentId>/agent/models.json`). This file is merged by default unless `models.mode` is set to `replace`.
|
||||
|
||||
Merge mode precedence for matching provider IDs:
|
||||
<AccordionGroup>
|
||||
<Accordion title="Merge mode precedence">
|
||||
Merge mode precedence for matching provider IDs:
|
||||
|
||||
- Non-empty `baseUrl` already present in the agent `models.json` wins.
|
||||
- Non-empty `apiKey` in the agent `models.json` wins only when that provider is not SecretRef-managed in current config/auth-profile context.
|
||||
- SecretRef-managed provider `apiKey` values are refreshed from source markers (`ENV_VAR_NAME` for env refs, `secretref-managed` for file/exec refs) instead of persisting resolved secrets.
|
||||
- SecretRef-managed provider header values are refreshed from source markers (`secretref-env:ENV_VAR_NAME` for env refs, `secretref-managed` for file/exec refs).
|
||||
- Empty or missing agent `apiKey`/`baseUrl` fall back to config `models.providers`.
|
||||
- Other provider fields are refreshed from config and normalized catalog data.
|
||||
- Non-empty `baseUrl` already present in the agent `models.json` wins.
|
||||
- Non-empty `apiKey` in the agent `models.json` wins only when that provider is not SecretRef-managed in current config/auth-profile context.
|
||||
- SecretRef-managed provider `apiKey` values are refreshed from source markers (`ENV_VAR_NAME` for env refs, `secretref-managed` for file/exec refs) instead of persisting resolved secrets.
|
||||
- SecretRef-managed provider header values are refreshed from source markers (`secretref-env:ENV_VAR_NAME` for env refs, `secretref-managed` for file/exec refs).
|
||||
- Empty or missing agent `apiKey`/`baseUrl` fall back to config `models.providers`.
|
||||
- Other provider fields are refreshed from config and normalized catalog data.
|
||||
|
||||
Marker persistence is source-authoritative: OpenClaw writes markers from the active source config snapshot (pre-resolution), not from resolved runtime secret values.
|
||||
This applies whenever OpenClaw regenerates `models.json`, including command-driven paths like `openclaw agent`.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
<Note>
|
||||
Marker persistence is source-authoritative: OpenClaw writes markers from the active source config snapshot (pre-resolution), not from resolved runtime secret values. This applies whenever OpenClaw regenerates `models.json`, including command-driven paths like `openclaw agent`.
|
||||
</Note>
|
||||
|
||||
## Related
|
||||
|
||||
- [Model Providers](/concepts/model-providers) — provider routing and auth
|
||||
- [Agent Runtimes](/concepts/agent-runtimes) — PI, Codex, and other agent loop runtimes
|
||||
- [Model Failover](/concepts/model-failover) — fallback chains
|
||||
- [Image Generation](/tools/image-generation) — image model configuration
|
||||
- [Music Generation](/tools/music-generation) — music model configuration
|
||||
- [Video Generation](/tools/video-generation) — video model configuration
|
||||
- [Configuration Reference](/gateway/config-agents#agent-defaults) — model config keys
|
||||
- [Agent runtimes](/concepts/agent-runtimes) — PI, Codex, and other agent loop runtimes
|
||||
- [Configuration reference](/gateway/config-agents#agent-defaults) — model config keys
|
||||
- [Image generation](/tools/image-generation) — image model configuration
|
||||
- [Model failover](/concepts/model-failover) — fallback chains
|
||||
- [Model providers](/concepts/model-providers) — provider routing and auth
|
||||
- [Music generation](/tools/music-generation) — music model configuration
|
||||
- [Video generation](/tools/video-generation) — video model configuration
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
summary: "Multi-agent routing: isolated agents, channel accounts, and bindings"
|
||||
title: Multi-agent routing
|
||||
title: "Multi-agent routing"
|
||||
sidebarTitle: "Multi-agent routing"
|
||||
read_when: "You want multiple isolated agents (workspaces + auth) in one gateway process."
|
||||
status: active
|
||||
---
|
||||
@@ -23,32 +24,21 @@ Auth profiles are **per-agent**. Each agent reads from its own:
|
||||
~/.openclaw/agents/<agentId>/agent/auth-profiles.json
|
||||
```
|
||||
|
||||
`sessions_history` is the safer cross-session recall path here too: it returns
|
||||
a bounded, sanitized view, not a raw transcript dump. Assistant recall strips
|
||||
thinking tags, `<relevant-memories>` scaffolding, plain-text tool-call XML
|
||||
payloads (including `<tool_call>...</tool_call>`,
|
||||
`<function_call>...</function_call>`, `<tool_calls>...</tool_calls>`,
|
||||
`<function_calls>...</function_calls>`, and truncated tool-call blocks),
|
||||
downgraded tool-call scaffolding, leaked ASCII/full-width model control
|
||||
tokens, and malformed MiniMax tool-call XML before redaction/truncation.
|
||||
<Note>
|
||||
`sessions_history` is the safer cross-session recall path here too: it returns a bounded, sanitized view, not a raw transcript dump. Assistant recall strips thinking tags, `<relevant-memories>` scaffolding, plain-text tool-call XML payloads (including `<tool_call>...</tool_call>`, `<function_call>...</function_call>`, `<tool_calls>...</tool_calls>`, `<function_calls>...</function_calls>`, and truncated tool-call blocks), downgraded tool-call scaffolding, leaked ASCII/full-width model control tokens, and malformed MiniMax tool-call XML before redaction/truncation.
|
||||
</Note>
|
||||
|
||||
Main agent credentials are **not** shared automatically. Never reuse `agentDir`
|
||||
across agents (it causes auth/session collisions). If you want to share creds,
|
||||
copy `auth-profiles.json` into the other agent's `agentDir`.
|
||||
<Warning>
|
||||
Main agent credentials are **not** shared automatically. Never reuse `agentDir` across agents (it causes auth/session collisions). If you want to share creds, copy `auth-profiles.json` into the other agent's `agentDir`.
|
||||
</Warning>
|
||||
|
||||
Skills are loaded from each agent workspace plus shared roots such as
|
||||
`~/.openclaw/skills`, then filtered by the effective agent skill allowlist when
|
||||
configured. Use `agents.defaults.skills` for a shared baseline and
|
||||
`agents.list[].skills` for per-agent replacement. See
|
||||
[Skills: per-agent vs shared](/tools/skills#per-agent-vs-shared-skills) and
|
||||
[Skills: agent skill allowlists](/tools/skills#agent-skill-allowlists).
|
||||
Skills are loaded from each agent workspace plus shared roots such as `~/.openclaw/skills`, then filtered by the effective agent skill allowlist when configured. Use `agents.defaults.skills` for a shared baseline and `agents.list[].skills` for per-agent replacement. See [Skills: per-agent vs shared](/tools/skills#per-agent-vs-shared-skills) and [Skills: agent skill allowlists](/tools/skills#agent-skill-allowlists).
|
||||
|
||||
The Gateway can host **one agent** (default) or **many agents** side-by-side.
|
||||
|
||||
**Workspace note:** each agent’s workspace is the **default cwd**, not a hard
|
||||
sandbox. Relative paths resolve inside the workspace, but absolute paths can
|
||||
reach other host locations unless sandboxing is enabled. See
|
||||
[Sandboxing](/gateway/sandboxing).
|
||||
<Note>
|
||||
**Workspace note:** each agent's workspace is the **default cwd**, not a hard sandbox. Relative paths resolve inside the workspace, but absolute paths can reach other host locations unless sandboxing is enabled. See [Sandboxing](/gateway/sandboxing).
|
||||
</Note>
|
||||
|
||||
## Paths (quick map)
|
||||
|
||||
@@ -87,48 +77,39 @@ openclaw agents list --bindings
|
||||
|
||||
<Steps>
|
||||
<Step title="Create each agent workspace">
|
||||
Use the wizard or create workspaces manually:
|
||||
|
||||
Use the wizard or create workspaces manually:
|
||||
```bash
|
||||
openclaw agents add coding
|
||||
openclaw agents add social
|
||||
```
|
||||
|
||||
```bash
|
||||
openclaw agents add coding
|
||||
openclaw agents add social
|
||||
```
|
||||
|
||||
Each agent gets its own workspace with `SOUL.md`, `AGENTS.md`, and optional `USER.md`, plus a dedicated `agentDir` and session store under `~/.openclaw/agents/<agentId>`.
|
||||
Each agent gets its own workspace with `SOUL.md`, `AGENTS.md`, and optional `USER.md`, plus a dedicated `agentDir` and session store under `~/.openclaw/agents/<agentId>`.
|
||||
|
||||
</Step>
|
||||
|
||||
<Step title="Create channel accounts">
|
||||
Create one account per agent on your preferred channels:
|
||||
|
||||
Create one account per agent on your preferred channels:
|
||||
- Discord: one bot per agent, enable Message Content Intent, copy each token.
|
||||
- Telegram: one bot per agent via BotFather, copy each token.
|
||||
- WhatsApp: link each phone number per account.
|
||||
|
||||
- Discord: one bot per agent, enable Message Content Intent, copy each token.
|
||||
- Telegram: one bot per agent via BotFather, copy each token.
|
||||
- WhatsApp: link each phone number per account.
|
||||
```bash
|
||||
openclaw channels login --channel whatsapp --account work
|
||||
```
|
||||
|
||||
```bash
|
||||
openclaw channels login --channel whatsapp --account work
|
||||
```
|
||||
|
||||
See channel guides: [Discord](/channels/discord), [Telegram](/channels/telegram), [WhatsApp](/channels/whatsapp).
|
||||
See channel guides: [Discord](/channels/discord), [Telegram](/channels/telegram), [WhatsApp](/channels/whatsapp).
|
||||
|
||||
</Step>
|
||||
|
||||
<Step title="Add agents, accounts, and bindings">
|
||||
|
||||
Add agents under `agents.list`, channel accounts under `channels.<channel>.accounts`, and connect them with `bindings` (examples below).
|
||||
|
||||
Add agents under `agents.list`, channel accounts under `channels.<channel>.accounts`, and connect them with `bindings` (examples below).
|
||||
</Step>
|
||||
|
||||
<Step title="Restart and verify">
|
||||
|
||||
```bash
|
||||
openclaw gateway restart
|
||||
openclaw agents list --bindings
|
||||
openclaw channels status --probe
|
||||
```
|
||||
|
||||
```bash
|
||||
openclaw gateway restart
|
||||
openclaw agents list --bindings
|
||||
openclaw channels status --probe
|
||||
```
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
@@ -140,14 +121,11 @@ With **multiple agents**, each `agentId` becomes a **fully isolated persona**:
|
||||
- **Different personalities** (per-agent workspace files like `AGENTS.md` and `SOUL.md`).
|
||||
- **Separate auth + sessions** (no cross-talk unless explicitly enabled).
|
||||
|
||||
This lets **multiple people** share one Gateway server while keeping their AI “brains” and data isolated.
|
||||
This lets **multiple people** share one Gateway server while keeping their AI "brains" and data isolated.
|
||||
|
||||
## Cross-agent QMD memory search
|
||||
|
||||
If one agent should search another agent's QMD session transcripts, add
|
||||
extra collections under `agents.list[].memorySearch.qmd.extraCollections`.
|
||||
Use `agents.defaults.memorySearch.qmd.extraCollections` only when every agent
|
||||
should inherit the same shared transcript collections.
|
||||
If one agent should search another agent's QMD session transcripts, add extra collections under `agents.list[].memorySearch.qmd.extraCollections`. Use `agents.defaults.memorySearch.qmd.extraCollections` only when every agent should inherit the same shared transcript collections.
|
||||
|
||||
```json5
|
||||
{
|
||||
@@ -180,15 +158,15 @@ should inherit the same shared transcript collections.
|
||||
}
|
||||
```
|
||||
|
||||
The extra collection path can be shared across agents, but the collection name
|
||||
stays explicit when the path is outside the agent workspace. Paths inside the
|
||||
workspace remain agent-scoped so each agent keeps its own transcript search set.
|
||||
The extra collection path can be shared across agents, but the collection name stays explicit when the path is outside the agent workspace. Paths inside the workspace remain agent-scoped so each agent keeps its own transcript search set.
|
||||
|
||||
## One WhatsApp number, multiple people (DM split)
|
||||
|
||||
You can route **different WhatsApp DMs** to different agents while staying on **one WhatsApp account**. Match on sender E.164 (like `+15551234567`) with `peer.kind: "direct"`. Replies still come from the same WhatsApp number (no per‑agent sender identity).
|
||||
You can route **different WhatsApp DMs** to different agents while staying on **one WhatsApp account**. Match on sender E.164 (like `+15551234567`) with `peer.kind: "direct"`. Replies still come from the same WhatsApp number (no per-agent sender identity).
|
||||
|
||||
Important detail: direct chats collapse to the agent’s **main session key**, so true isolation requires **one agent per person**.
|
||||
<Note>
|
||||
Direct chats collapse to the agent's **main session key**, so true isolation requires **one agent per person**.
|
||||
</Note>
|
||||
|
||||
Example:
|
||||
|
||||
@@ -228,33 +206,50 @@ Notes:
|
||||
|
||||
Bindings are **deterministic** and **most-specific wins**:
|
||||
|
||||
1. `peer` match (exact DM/group/channel id)
|
||||
2. `parentPeer` match (thread inheritance)
|
||||
3. `guildId + roles` (Discord role routing)
|
||||
4. `guildId` (Discord)
|
||||
5. `teamId` (Slack)
|
||||
6. `accountId` match for a channel
|
||||
7. channel-level match (`accountId: "*"`)
|
||||
8. fallback to default agent (`agents.list[].default`, else first list entry, default: `main`)
|
||||
<Steps>
|
||||
<Step title="peer match">
|
||||
Exact DM/group/channel id.
|
||||
</Step>
|
||||
<Step title="parentPeer match">
|
||||
Thread inheritance.
|
||||
</Step>
|
||||
<Step title="guildId + roles">
|
||||
Discord role routing.
|
||||
</Step>
|
||||
<Step title="guildId">
|
||||
Discord.
|
||||
</Step>
|
||||
<Step title="teamId">
|
||||
Slack.
|
||||
</Step>
|
||||
<Step title="accountId match for a channel">
|
||||
Per-account fallback.
|
||||
</Step>
|
||||
<Step title="Channel-level match">
|
||||
`accountId: "*"`.
|
||||
</Step>
|
||||
<Step title="Default agent">
|
||||
Fallback to `agents.list[].default`, else first list entry, default: `main`.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
If multiple bindings match in the same tier, the first one in config order wins.
|
||||
If a binding sets multiple match fields (for example `peer` + `guildId`), all specified fields are required (`AND` semantics).
|
||||
|
||||
Important account-scope detail:
|
||||
|
||||
- A binding that omits `accountId` matches the default account only.
|
||||
- Use `accountId: "*"` for a channel-wide fallback across all accounts.
|
||||
- If you later add the same binding for the same agent with an explicit account id, OpenClaw upgrades the existing channel-only binding to account-scoped instead of duplicating it.
|
||||
<AccordionGroup>
|
||||
<Accordion title="Tie-breaking and AND semantics">
|
||||
- If multiple bindings match in the same tier, the first one in config order wins.
|
||||
- If a binding sets multiple match fields (for example `peer` + `guildId`), all specified fields are required (`AND` semantics).
|
||||
</Accordion>
|
||||
<Accordion title="Account-scope detail">
|
||||
- A binding that omits `accountId` matches the default account only.
|
||||
- Use `accountId: "*"` for a channel-wide fallback across all accounts.
|
||||
- If you later add the same binding for the same agent with an explicit account id, OpenClaw upgrades the existing channel-only binding to account-scoped instead of duplicating it.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Multiple accounts / phone numbers
|
||||
|
||||
Channels that support **multiple accounts** (e.g. WhatsApp) use `accountId` to identify
|
||||
each login. Each `accountId` can be routed to a different agent, so one server can host
|
||||
multiple phone numbers without mixing sessions.
|
||||
Channels that support **multiple accounts** (e.g. WhatsApp) use `accountId` to identify each login. Each `accountId` can be routed to a different agent, so one server can host multiple phone numbers without mixing sessions.
|
||||
|
||||
If you want a channel-wide default account when `accountId` is omitted, set
|
||||
`channels.<channel>.defaultAccount` (optional). When unset, OpenClaw falls back
|
||||
to `default` if present, otherwise the first configured account id (sorted).
|
||||
If you want a channel-wide default account when `accountId` is omitted, set `channels.<channel>.defaultAccount` (optional). When unset, OpenClaw falls back to `default` if present, otherwise the first configured account id (sorted).
|
||||
|
||||
Common channels supporting this pattern include:
|
||||
|
||||
@@ -264,297 +259,298 @@ Common channels supporting this pattern include:
|
||||
|
||||
## Concepts
|
||||
|
||||
- `agentId`: one “brain” (workspace, per-agent auth, per-agent session store).
|
||||
- `agentId`: one "brain" (workspace, per-agent auth, per-agent session store).
|
||||
- `accountId`: one channel account instance (e.g. WhatsApp account `"personal"` vs `"biz"`).
|
||||
- `binding`: routes inbound messages to an `agentId` by `(channel, accountId, peer)` and optionally guild/team ids.
|
||||
- Direct chats collapse to `agent:<agentId>:<mainKey>` (per-agent “main”; `session.mainKey`).
|
||||
- Direct chats collapse to `agent:<agentId>:<mainKey>` (per-agent "main"; `session.mainKey`).
|
||||
|
||||
## Platform examples
|
||||
|
||||
### Discord bots per agent
|
||||
<AccordionGroup>
|
||||
<Accordion title="Discord bots per agent">
|
||||
Each Discord bot account maps to a unique `accountId`. Bind each account to an agent and keep allowlists per bot.
|
||||
|
||||
Each Discord bot account maps to a unique `accountId`. Bind each account to an agent and keep allowlists per bot.
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
list: [
|
||||
{ id: "main", workspace: "~/.openclaw/workspace-main" },
|
||||
{ id: "coding", workspace: "~/.openclaw/workspace-coding" },
|
||||
],
|
||||
},
|
||||
bindings: [
|
||||
{ agentId: "main", match: { channel: "discord", accountId: "default" } },
|
||||
{ agentId: "coding", match: { channel: "discord", accountId: "coding" } },
|
||||
],
|
||||
channels: {
|
||||
discord: {
|
||||
groupPolicy: "allowlist",
|
||||
accounts: {
|
||||
default: {
|
||||
token: "DISCORD_BOT_TOKEN_MAIN",
|
||||
guilds: {
|
||||
"123456789012345678": {
|
||||
channels: {
|
||||
"222222222222222222": { allow: true, requireMention: false },
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
list: [
|
||||
{ id: "main", workspace: "~/.openclaw/workspace-main" },
|
||||
{ id: "coding", workspace: "~/.openclaw/workspace-coding" },
|
||||
],
|
||||
},
|
||||
bindings: [
|
||||
{ agentId: "main", match: { channel: "discord", accountId: "default" } },
|
||||
{ agentId: "coding", match: { channel: "discord", accountId: "coding" } },
|
||||
],
|
||||
channels: {
|
||||
discord: {
|
||||
groupPolicy: "allowlist",
|
||||
accounts: {
|
||||
default: {
|
||||
token: "DISCORD_BOT_TOKEN_MAIN",
|
||||
guilds: {
|
||||
"123456789012345678": {
|
||||
channels: {
|
||||
"222222222222222222": { allow: true, requireMention: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
coding: {
|
||||
token: "DISCORD_BOT_TOKEN_CODING",
|
||||
guilds: {
|
||||
"123456789012345678": {
|
||||
channels: {
|
||||
"333333333333333333": { allow: true, requireMention: false },
|
||||
coding: {
|
||||
token: "DISCORD_BOT_TOKEN_CODING",
|
||||
guilds: {
|
||||
"123456789012345678": {
|
||||
channels: {
|
||||
"333333333333333333": { allow: true, requireMention: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
- Invite each bot to the guild and enable Message Content Intent.
|
||||
- Tokens live in `channels.discord.accounts.<id>.token` (default account can use `DISCORD_BOT_TOKEN`).
|
||||
|
||||
- Invite each bot to the guild and enable Message Content Intent.
|
||||
- Tokens live in `channels.discord.accounts.<id>.token` (default account can use `DISCORD_BOT_TOKEN`).
|
||||
|
||||
### Telegram bots per agent
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
list: [
|
||||
{ id: "main", workspace: "~/.openclaw/workspace-main" },
|
||||
{ id: "alerts", workspace: "~/.openclaw/workspace-alerts" },
|
||||
],
|
||||
},
|
||||
bindings: [
|
||||
{ agentId: "main", match: { channel: "telegram", accountId: "default" } },
|
||||
{ agentId: "alerts", match: { channel: "telegram", accountId: "alerts" } },
|
||||
],
|
||||
channels: {
|
||||
telegram: {
|
||||
accounts: {
|
||||
default: {
|
||||
botToken: "123456:ABC...",
|
||||
dmPolicy: "pairing",
|
||||
},
|
||||
alerts: {
|
||||
botToken: "987654:XYZ...",
|
||||
dmPolicy: "allowlist",
|
||||
allowFrom: ["tg:123456789"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- Create one bot per agent with BotFather and copy each token.
|
||||
- Tokens live in `channels.telegram.accounts.<id>.botToken` (default account can use `TELEGRAM_BOT_TOKEN`).
|
||||
|
||||
### WhatsApp numbers per agent
|
||||
|
||||
Link each account before starting the gateway:
|
||||
|
||||
```bash
|
||||
openclaw channels login --channel whatsapp --account personal
|
||||
openclaw channels login --channel whatsapp --account biz
|
||||
```
|
||||
|
||||
`~/.openclaw/openclaw.json` (JSON5):
|
||||
|
||||
```js
|
||||
{
|
||||
agents: {
|
||||
list: [
|
||||
{
|
||||
id: "home",
|
||||
default: true,
|
||||
name: "Home",
|
||||
workspace: "~/.openclaw/workspace-home",
|
||||
agentDir: "~/.openclaw/agents/home/agent",
|
||||
},
|
||||
{
|
||||
id: "work",
|
||||
name: "Work",
|
||||
workspace: "~/.openclaw/workspace-work",
|
||||
agentDir: "~/.openclaw/agents/work/agent",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// Deterministic routing: first match wins (most-specific first).
|
||||
bindings: [
|
||||
{ agentId: "home", match: { channel: "whatsapp", accountId: "personal" } },
|
||||
{ agentId: "work", match: { channel: "whatsapp", accountId: "biz" } },
|
||||
|
||||
// Optional per-peer override (example: send a specific group to work agent).
|
||||
</Accordion>
|
||||
<Accordion title="Telegram bots per agent">
|
||||
```json5
|
||||
{
|
||||
agentId: "work",
|
||||
match: {
|
||||
channel: "whatsapp",
|
||||
accountId: "personal",
|
||||
peer: { kind: "group", id: "1203630...@g.us" },
|
||||
agents: {
|
||||
list: [
|
||||
{ id: "main", workspace: "~/.openclaw/workspace-main" },
|
||||
{ id: "alerts", workspace: "~/.openclaw/workspace-alerts" },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
// Off by default: agent-to-agent messaging must be explicitly enabled + allowlisted.
|
||||
tools: {
|
||||
agentToAgent: {
|
||||
enabled: false,
|
||||
allow: ["home", "work"],
|
||||
},
|
||||
},
|
||||
|
||||
channels: {
|
||||
whatsapp: {
|
||||
accounts: {
|
||||
personal: {
|
||||
// Optional override. Default: ~/.openclaw/credentials/whatsapp/personal
|
||||
// authDir: "~/.openclaw/credentials/whatsapp/personal",
|
||||
},
|
||||
biz: {
|
||||
// Optional override. Default: ~/.openclaw/credentials/whatsapp/biz
|
||||
// authDir: "~/.openclaw/credentials/whatsapp/biz",
|
||||
bindings: [
|
||||
{ agentId: "main", match: { channel: "telegram", accountId: "default" } },
|
||||
{ agentId: "alerts", match: { channel: "telegram", accountId: "alerts" } },
|
||||
],
|
||||
channels: {
|
||||
telegram: {
|
||||
accounts: {
|
||||
default: {
|
||||
botToken: "123456:ABC...",
|
||||
dmPolicy: "pairing",
|
||||
},
|
||||
alerts: {
|
||||
botToken: "987654:XYZ...",
|
||||
dmPolicy: "allowlist",
|
||||
allowFrom: ["tg:123456789"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
}
|
||||
```
|
||||
|
||||
## Example: WhatsApp daily chat + Telegram deep work
|
||||
- Create one bot per agent with BotFather and copy each token.
|
||||
- Tokens live in `channels.telegram.accounts.<id>.botToken` (default account can use `TELEGRAM_BOT_TOKEN`).
|
||||
|
||||
Split by channel: route WhatsApp to a fast everyday agent and Telegram to an Opus agent.
|
||||
</Accordion>
|
||||
<Accordion title="WhatsApp numbers per agent">
|
||||
Link each account before starting the gateway:
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
list: [
|
||||
{
|
||||
id: "chat",
|
||||
name: "Everyday",
|
||||
workspace: "~/.openclaw/workspace-chat",
|
||||
model: "anthropic/claude-sonnet-4-6",
|
||||
},
|
||||
{
|
||||
id: "opus",
|
||||
name: "Deep Work",
|
||||
workspace: "~/.openclaw/workspace-opus",
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
},
|
||||
],
|
||||
},
|
||||
bindings: [
|
||||
{ agentId: "chat", match: { channel: "whatsapp" } },
|
||||
{ agentId: "opus", match: { channel: "telegram" } },
|
||||
],
|
||||
}
|
||||
```
|
||||
```bash
|
||||
openclaw channels login --channel whatsapp --account personal
|
||||
openclaw channels login --channel whatsapp --account biz
|
||||
```
|
||||
|
||||
Notes:
|
||||
`~/.openclaw/openclaw.json` (JSON5):
|
||||
|
||||
- If you have multiple accounts for a channel, add `accountId` to the binding (for example `{ channel: "whatsapp", accountId: "personal" }`).
|
||||
- To route a single DM/group to Opus while keeping the rest on chat, add a `match.peer` binding for that peer; peer matches always win over channel-wide rules.
|
||||
|
||||
## Example: same channel, one peer to Opus
|
||||
|
||||
Keep WhatsApp on the fast agent, but route one DM to Opus:
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
list: [
|
||||
{
|
||||
id: "chat",
|
||||
name: "Everyday",
|
||||
workspace: "~/.openclaw/workspace-chat",
|
||||
model: "anthropic/claude-sonnet-4-6",
|
||||
},
|
||||
{
|
||||
id: "opus",
|
||||
name: "Deep Work",
|
||||
workspace: "~/.openclaw/workspace-opus",
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
},
|
||||
],
|
||||
},
|
||||
bindings: [
|
||||
```js
|
||||
{
|
||||
agentId: "opus",
|
||||
match: { channel: "whatsapp", peer: { kind: "direct", id: "+15551234567" } },
|
||||
},
|
||||
{ agentId: "chat", match: { channel: "whatsapp" } },
|
||||
],
|
||||
}
|
||||
```
|
||||
agents: {
|
||||
list: [
|
||||
{
|
||||
id: "home",
|
||||
default: true,
|
||||
name: "Home",
|
||||
workspace: "~/.openclaw/workspace-home",
|
||||
agentDir: "~/.openclaw/agents/home/agent",
|
||||
},
|
||||
{
|
||||
id: "work",
|
||||
name: "Work",
|
||||
workspace: "~/.openclaw/workspace-work",
|
||||
agentDir: "~/.openclaw/agents/work/agent",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
Peer bindings always win, so keep them above the channel-wide rule.
|
||||
// Deterministic routing: first match wins (most-specific first).
|
||||
bindings: [
|
||||
{ agentId: "home", match: { channel: "whatsapp", accountId: "personal" } },
|
||||
{ agentId: "work", match: { channel: "whatsapp", accountId: "biz" } },
|
||||
|
||||
## Family agent bound to a WhatsApp group
|
||||
|
||||
Bind a dedicated family agent to a single WhatsApp group, with mention gating
|
||||
and a tighter tool policy:
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
list: [
|
||||
{
|
||||
id: "family",
|
||||
name: "Family",
|
||||
workspace: "~/.openclaw/workspace-family",
|
||||
identity: { name: "Family Bot" },
|
||||
groupChat: {
|
||||
mentionPatterns: ["@family", "@familybot", "@Family Bot"],
|
||||
// Optional per-peer override (example: send a specific group to work agent).
|
||||
{
|
||||
agentId: "work",
|
||||
match: {
|
||||
channel: "whatsapp",
|
||||
accountId: "personal",
|
||||
peer: { kind: "group", id: "1203630...@g.us" },
|
||||
},
|
||||
},
|
||||
sandbox: {
|
||||
mode: "all",
|
||||
scope: "agent",
|
||||
},
|
||||
tools: {
|
||||
allow: [
|
||||
"exec",
|
||||
"read",
|
||||
"sessions_list",
|
||||
"sessions_history",
|
||||
"sessions_send",
|
||||
"sessions_spawn",
|
||||
"session_status",
|
||||
],
|
||||
deny: ["write", "edit", "apply_patch", "browser", "canvas", "nodes", "cron"],
|
||||
],
|
||||
|
||||
// Off by default: agent-to-agent messaging must be explicitly enabled + allowlisted.
|
||||
tools: {
|
||||
agentToAgent: {
|
||||
enabled: false,
|
||||
allow: ["home", "work"],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
bindings: [
|
||||
|
||||
channels: {
|
||||
whatsapp: {
|
||||
accounts: {
|
||||
personal: {
|
||||
// Optional override. Default: ~/.openclaw/credentials/whatsapp/personal
|
||||
// authDir: "~/.openclaw/credentials/whatsapp/personal",
|
||||
},
|
||||
biz: {
|
||||
// Optional override. Default: ~/.openclaw/credentials/whatsapp/biz
|
||||
// authDir: "~/.openclaw/credentials/whatsapp/biz",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Common patterns
|
||||
|
||||
<Tabs>
|
||||
<Tab title="WhatsApp daily + Telegram deep work">
|
||||
Split by channel: route WhatsApp to a fast everyday agent and Telegram to an Opus agent.
|
||||
|
||||
```json5
|
||||
{
|
||||
agentId: "family",
|
||||
match: {
|
||||
channel: "whatsapp",
|
||||
peer: { kind: "group", id: "120363999999999999@g.us" },
|
||||
agents: {
|
||||
list: [
|
||||
{
|
||||
id: "chat",
|
||||
name: "Everyday",
|
||||
workspace: "~/.openclaw/workspace-chat",
|
||||
model: "anthropic/claude-sonnet-4-6",
|
||||
},
|
||||
{
|
||||
id: "opus",
|
||||
name: "Deep Work",
|
||||
workspace: "~/.openclaw/workspace-opus",
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
bindings: [
|
||||
{ agentId: "chat", match: { channel: "whatsapp" } },
|
||||
{ agentId: "opus", match: { channel: "telegram" } },
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
Notes:
|
||||
|
||||
- Tool allow/deny lists are **tools**, not skills. If a skill needs to run a
|
||||
binary, ensure `exec` is allowed and the binary exists in the sandbox.
|
||||
- For stricter gating, set `agents.list[].groupChat.mentionPatterns` and keep
|
||||
group allowlists enabled for the channel.
|
||||
- If you have multiple accounts for a channel, add `accountId` to the binding (for example `{ channel: "whatsapp", accountId: "personal" }`).
|
||||
- To route a single DM/group to Opus while keeping the rest on chat, add a `match.peer` binding for that peer; peer matches always win over channel-wide rules.
|
||||
|
||||
## Per-Agent Sandbox and Tool Configuration
|
||||
</Tab>
|
||||
<Tab title="Same channel, one peer to Opus">
|
||||
Keep WhatsApp on the fast agent, but route one DM to Opus:
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
list: [
|
||||
{
|
||||
id: "chat",
|
||||
name: "Everyday",
|
||||
workspace: "~/.openclaw/workspace-chat",
|
||||
model: "anthropic/claude-sonnet-4-6",
|
||||
},
|
||||
{
|
||||
id: "opus",
|
||||
name: "Deep Work",
|
||||
workspace: "~/.openclaw/workspace-opus",
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
},
|
||||
],
|
||||
},
|
||||
bindings: [
|
||||
{
|
||||
agentId: "opus",
|
||||
match: { channel: "whatsapp", peer: { kind: "direct", id: "+15551234567" } },
|
||||
},
|
||||
{ agentId: "chat", match: { channel: "whatsapp" } },
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
Peer bindings always win, so keep them above the channel-wide rule.
|
||||
|
||||
</Tab>
|
||||
<Tab title="Family agent bound to a WhatsApp group">
|
||||
Bind a dedicated family agent to a single WhatsApp group, with mention gating and a tighter tool policy:
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
list: [
|
||||
{
|
||||
id: "family",
|
||||
name: "Family",
|
||||
workspace: "~/.openclaw/workspace-family",
|
||||
identity: { name: "Family Bot" },
|
||||
groupChat: {
|
||||
mentionPatterns: ["@family", "@familybot", "@Family Bot"],
|
||||
},
|
||||
sandbox: {
|
||||
mode: "all",
|
||||
scope: "agent",
|
||||
},
|
||||
tools: {
|
||||
allow: [
|
||||
"exec",
|
||||
"read",
|
||||
"sessions_list",
|
||||
"sessions_history",
|
||||
"sessions_send",
|
||||
"sessions_spawn",
|
||||
"session_status",
|
||||
],
|
||||
deny: ["write", "edit", "apply_patch", "browser", "canvas", "nodes", "cron"],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
bindings: [
|
||||
{
|
||||
agentId: "family",
|
||||
match: {
|
||||
channel: "whatsapp",
|
||||
peer: { kind: "group", id: "120363999999999999@g.us" },
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- Tool allow/deny lists are **tools**, not skills. If a skill needs to run a binary, ensure `exec` is allowed and the binary exists in the sandbox.
|
||||
- For stricter gating, set `agents.list[].groupChat.mentionPatterns` and keep group allowlists enabled for the channel.
|
||||
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Per-agent sandbox and tool configuration
|
||||
|
||||
Each agent can have its own sandbox and tool restrictions:
|
||||
|
||||
@@ -591,25 +587,26 @@ Each agent can have its own sandbox and tool restrictions:
|
||||
}
|
||||
```
|
||||
|
||||
Note: `setupCommand` lives under `sandbox.docker` and runs once on container creation.
|
||||
Per-agent `sandbox.docker.*` overrides are ignored when the resolved scope is `"shared"`.
|
||||
<Note>
|
||||
`setupCommand` lives under `sandbox.docker` and runs once on container creation. Per-agent `sandbox.docker.*` overrides are ignored when the resolved scope is `"shared"`.
|
||||
</Note>
|
||||
|
||||
**Benefits:**
|
||||
|
||||
- **Security isolation**: Restrict tools for untrusted agents
|
||||
- **Resource control**: Sandbox specific agents while keeping others on host
|
||||
- **Flexible policies**: Different permissions per agent
|
||||
- **Security isolation**: restrict tools for untrusted agents.
|
||||
- **Resource control**: sandbox specific agents while keeping others on host.
|
||||
- **Flexible policies**: different permissions per agent.
|
||||
|
||||
Note: `tools.elevated` is **global** and sender-based; it is not configurable per agent.
|
||||
If you need per-agent boundaries, use `agents.list[].tools` to deny `exec`.
|
||||
For group targeting, use `agents.list[].groupChat.mentionPatterns` so @mentions map cleanly to the intended agent.
|
||||
<Note>
|
||||
`tools.elevated` is **global** and sender-based; it is not configurable per agent. If you need per-agent boundaries, use `agents.list[].tools` to deny `exec`. For group targeting, use `agents.list[].groupChat.mentionPatterns` so @mentions map cleanly to the intended agent.
|
||||
</Note>
|
||||
|
||||
See [Multi-Agent Sandbox & Tools](/tools/multi-agent-sandbox-tools) for detailed examples.
|
||||
See [Multi-agent sandbox and tools](/tools/multi-agent-sandbox-tools) for detailed examples.
|
||||
|
||||
## Related
|
||||
|
||||
- [Channel Routing](/channels/channel-routing) — how messages route to agents
|
||||
- [Sub-Agents](/tools/subagents) — spawning background agent runs
|
||||
- [ACP Agents](/tools/acp-agents) — running external coding harnesses
|
||||
- [ACP agents](/tools/acp-agents) — running external coding harnesses
|
||||
- [Channel routing](/channels/channel-routing) — how messages route to agents
|
||||
- [Presence](/concepts/presence) — agent presence and availability
|
||||
- [Session](/concepts/session) — session isolation and routing
|
||||
- [Sub-agents](/tools/subagents) — spawning background agent runs
|
||||
|
||||
@@ -65,10 +65,15 @@ model calls must not export `StreamAbandoned` on successful turns; raw diagnosti
|
||||
`openclaw.content.*` attributes must stay out of the trace. It writes
|
||||
`otel-smoke-summary.json` next to the QA suite artifacts.
|
||||
|
||||
Observability QA stays source-checkout only. The npm tarball intentionally omits
|
||||
QA Lab, so package Docker release lanes do not run `qa` commands. Use
|
||||
`pnpm qa:otel:smoke` from a built source checkout when changing diagnostics
|
||||
instrumentation.
|
||||
|
||||
For a transport-real Matrix smoke lane, run:
|
||||
|
||||
```bash
|
||||
pnpm openclaw qa matrix
|
||||
pnpm openclaw qa matrix --profile fast --fail-fast
|
||||
```
|
||||
|
||||
That lane provisions a disposable Tuwunel homeserver in Docker, registers
|
||||
@@ -79,9 +84,15 @@ the child config scoped to the transport under test, so Matrix runs without
|
||||
a combined stdout/stderr log into the selected Matrix QA output directory. To
|
||||
capture the outer `scripts/run-node.mjs` build/launcher output too, set
|
||||
`OPENCLAW_RUN_NODE_OUTPUT_LOG=<path>` to a repo-local log file.
|
||||
Matrix progress is printed by default. `OPENCLAW_QA_MATRIX_TIMEOUT_MS` bounds
|
||||
the full run, and `OPENCLAW_QA_MATRIX_CLEANUP_TIMEOUT_MS` bounds cleanup so a
|
||||
stuck Docker teardown reports the exact recovery command instead of hanging.
|
||||
Matrix progress is printed by default. The CLI default profile is `all`, so
|
||||
plain `pnpm openclaw qa matrix` still runs the full catalog. Use `--profile
|
||||
fast` for the release-critical transport contract, or shard full coverage with
|
||||
`transport`, `media`, `e2ee-smoke`, `e2ee-deep`, and `e2ee-cli`. `--fail-fast`
|
||||
stops after the first failed scenario when you want a release gate instead of a
|
||||
full inventory. `OPENCLAW_QA_MATRIX_TIMEOUT_MS` bounds the full run,
|
||||
`OPENCLAW_QA_MATRIX_NO_REPLY_WINDOW_MS` can shorten no-reply quiet windows for
|
||||
CI, and `OPENCLAW_QA_MATRIX_CLEANUP_TIMEOUT_MS` bounds cleanup so a stuck
|
||||
Docker teardown reports the exact recovery command instead of hanging.
|
||||
|
||||
For a transport-real Telegram smoke lane, run:
|
||||
|
||||
|
||||
@@ -152,6 +152,7 @@ Legacy key migration:
|
||||
Telegram:
|
||||
|
||||
- Uses `sendMessage` + `editMessageText` preview updates across DMs and group/topics.
|
||||
- Sends a fresh final message instead of editing in place when a preview has been visible for about one minute, then cleans up the preview so Telegram's timestamp reflects reply completion.
|
||||
- Preview streaming is skipped when Telegram block streaming is explicitly enabled (to avoid double-streaming).
|
||||
- `/reasoning stream` can write reasoning to preview.
|
||||
|
||||
|
||||
@@ -116,12 +116,9 @@ heartbeats are disabled for the default agent or
|
||||
files concise — especially `MEMORY.md`, which can grow over time and lead to
|
||||
unexpectedly high context usage and more frequent compaction.
|
||||
|
||||
> **Note:** `memory/*.md` daily files are **not** part of the normal bootstrap
|
||||
> Project Context. On ordinary turns they are accessed on demand via the
|
||||
> `memory_search` and `memory_get` tools, so they do not count against the
|
||||
> context window unless the model explicitly reads them. Bare `/new` and
|
||||
> `/reset` turns are the exception: the runtime can prepend recent daily memory
|
||||
> as a one-shot startup-context block for that first turn.
|
||||
<Note>
|
||||
`memory/*.md` daily files are **not** part of the normal bootstrap Project Context. On ordinary turns they are accessed on demand via the `memory_search` and `memory_get` tools, so they do not count against the context window unless the model explicitly reads them. Bare `/new` and `/reset` turns are the exception: the runtime can prepend recent daily memory as a one-shot startup-context block for that first turn.
|
||||
</Note>
|
||||
|
||||
Large files are truncated with a marker. The max per-file size is controlled by
|
||||
`agents.defaults.bootstrapMaxChars` (default: 12000). Total injected bootstrap
|
||||
|
||||
@@ -1442,6 +1442,7 @@
|
||||
"gateway/doctor",
|
||||
"logging",
|
||||
"gateway/opentelemetry",
|
||||
"gateway/prometheus",
|
||||
"gateway/logging",
|
||||
"gateway/diagnostics",
|
||||
"gateway/troubleshooting"
|
||||
|
||||
@@ -7,7 +7,7 @@ title: "Authentication"
|
||||
---
|
||||
|
||||
<Note>
|
||||
This page covers **model provider** authentication (API keys, OAuth, Claude CLI reuse, and Anthropic setup-token). For **gateway connection** authentication (token, password, trusted-proxy), see [Configuration](/gateway/configuration) and [Trusted Proxy Auth](/gateway/trusted-proxy-auth).
|
||||
This page is the **model provider** authentication reference (API keys, OAuth, Claude CLI reuse, and Anthropic setup-token). For **gateway connection** authentication (token, password, trusted-proxy), see [Configuration](/gateway/configuration) and [Trusted Proxy Auth](/gateway/trusted-proxy-auth).
|
||||
</Note>
|
||||
|
||||
OpenClaw supports OAuth and API keys for model providers. For always-on gateway
|
||||
|
||||
@@ -179,11 +179,10 @@ openclaw plugins disable bonjour
|
||||
|
||||
## Docker gotchas
|
||||
|
||||
Bundled Docker Compose sets `OPENCLAW_DISABLE_BONJOUR=1` for the Gateway service
|
||||
by default. Docker bridge networks usually do not forward mDNS multicast
|
||||
(`224.0.0.251:5353`) between the container and the LAN, so leaving Bonjour on can
|
||||
produce repeated ciao `probing` or `announcing` failures without making discovery
|
||||
work.
|
||||
The bundled Bonjour plugin auto-disables LAN multicast advertising in detected
|
||||
containers when `OPENCLAW_DISABLE_BONJOUR` is unset. Docker bridge networks
|
||||
usually do not forward mDNS multicast (`224.0.0.251:5353`) between the container
|
||||
and the LAN, so advertising from the container rarely makes discovery work.
|
||||
|
||||
Important gotchas:
|
||||
|
||||
@@ -193,16 +192,16 @@ Important gotchas:
|
||||
`OPENCLAW_GATEWAY_BIND=lan` so the published host port can work.
|
||||
- Disabling Bonjour does not disable wide-area DNS-SD. Use wide-area discovery
|
||||
or Tailnet when the Gateway and node are not on the same LAN.
|
||||
- Reusing the same `OPENCLAW_CONFIG_DIR` outside Docker does not inherit the
|
||||
Compose default unless the environment still sets `OPENCLAW_DISABLE_BONJOUR`.
|
||||
- Reusing the same `OPENCLAW_CONFIG_DIR` outside Docker does not persist the
|
||||
container auto-disable policy.
|
||||
- Set `OPENCLAW_DISABLE_BONJOUR=0` only for host networking, macvlan, or another
|
||||
network where mDNS multicast is known to pass.
|
||||
network where mDNS multicast is known to pass; set it to `1` to force-disable.
|
||||
|
||||
## Troubleshooting disabled Bonjour
|
||||
|
||||
If a node no longer auto-discovers the Gateway after Docker setup:
|
||||
|
||||
1. Confirm whether the Gateway is intentionally suppressing LAN advertising:
|
||||
1. Confirm whether the Gateway is running in auto, forced-on, or forced-off mode:
|
||||
|
||||
```bash
|
||||
docker compose config | grep OPENCLAW_DISABLE_BONJOUR
|
||||
@@ -239,9 +238,9 @@ If a node no longer auto-discovers the Gateway after Docker setup:
|
||||
container bridges, WSL, or interface churn can leave the ciao advertiser in a
|
||||
non-announced state. OpenClaw retries a few times and then disables Bonjour
|
||||
for the current Gateway process instead of restarting the advertiser forever.
|
||||
- **Docker bridge networking**: bundled Docker Compose disables Bonjour by
|
||||
default with `OPENCLAW_DISABLE_BONJOUR=1`. Set it to `0` only for host,
|
||||
macvlan, or another mDNS-capable network.
|
||||
- **Docker bridge networking**: Bonjour auto-disables in detected containers.
|
||||
Set `OPENCLAW_DISABLE_BONJOUR=0` only for host, macvlan, or another
|
||||
mDNS-capable network.
|
||||
- **Sleep / interface churn**: macOS may temporarily drop mDNS results; retry.
|
||||
- **Browse works but resolve fails**: keep machine names simple (avoid emojis or
|
||||
punctuation), then restart the Gateway. The service instance name derives from
|
||||
@@ -260,7 +259,8 @@ sequences (e.g. spaces become `\032`).
|
||||
- `openclaw plugins disable bonjour` disables LAN multicast advertising by disabling the bundled plugin.
|
||||
- `openclaw plugins enable bonjour` restores the default LAN discovery plugin.
|
||||
- `OPENCLAW_DISABLE_BONJOUR=1` disables LAN multicast advertising without changing plugin config; accepted truthy values are `1`, `true`, `yes`, and `on` (legacy: `OPENCLAW_DISABLE_BONJOUR`).
|
||||
- Docker Compose sets `OPENCLAW_DISABLE_BONJOUR=1` by default for bridge networking; override with `OPENCLAW_DISABLE_BONJOUR=0` only when mDNS multicast is available.
|
||||
- `OPENCLAW_DISABLE_BONJOUR=0` forces LAN multicast advertising on, including inside detected containers; accepted falsy values are `0`, `false`, `no`, and `off`.
|
||||
- When `OPENCLAW_DISABLE_BONJOUR` is unset, Bonjour advertises on normal hosts and auto-disables inside detected containers.
|
||||
- `gateway.bind` in `~/.openclaw/openclaw.json` controls the Gateway bind mode.
|
||||
- `OPENCLAW_SSH_PORT` overrides the SSH port when `sshPort` is advertised (legacy: `OPENCLAW_SSH_PORT`).
|
||||
- `OPENCLAW_TAILNET_DNS` publishes a MagicDNS hint in TXT when mDNS full mode is enabled (legacy: `OPENCLAW_TAILNET_DNS`).
|
||||
|
||||
@@ -316,8 +316,8 @@ Time format in system prompt. Default: `auto` (OS preference).
|
||||
fallbacks: ["openai/gpt-5.4-mini"],
|
||||
},
|
||||
params: { cacheRetention: "long" }, // global default provider params
|
||||
embeddedHarness: {
|
||||
runtime: "pi", // pi | auto | registered harness id, e.g. codex
|
||||
agentRuntime: {
|
||||
id: "pi", // pi | auto | registered harness id, e.g. codex
|
||||
fallback: "pi", // pi | none
|
||||
},
|
||||
pdfMaxBytesMb: 10,
|
||||
@@ -373,25 +373,25 @@ Time format in system prompt. Default: `auto` (OS preference).
|
||||
- `params.extra_body`/`params.extraBody`: advanced pass-through JSON merged into `api: "openai-completions"` request bodies for OpenAI-compatible proxies. If it collides with generated request keys, the extra body wins; non-native completions routes still strip OpenAI-only `store` afterward.
|
||||
- `params.chat_template_kwargs`: vLLM/OpenAI-compatible chat-template arguments merged into top-level `api: "openai-completions"` request bodies. For `vllm/nemotron-3-*` with thinking off, OpenClaw automatically sends `enable_thinking: false` and `force_nonempty_content: true`; explicit `chat_template_kwargs` override those defaults, and `extra_body.chat_template_kwargs` still has final precedence.
|
||||
- `params.preserveThinking`: Z.AI-only opt-in for preserved thinking. When enabled and thinking is on, OpenClaw sends `thinking.clear_thinking: false` and replays prior `reasoning_content`; see [Z.AI thinking and preserved thinking](/providers/zai#thinking-and-preserved-thinking).
|
||||
- `embeddedHarness`: default low-level embedded agent runtime policy. Omitted runtime defaults to OpenClaw Pi. Use `runtime: "pi"` to force the built-in PI harness, `runtime: "auto"` to let registered plugin harnesses claim supported models, or a registered harness id such as `runtime: "codex"`. Set `fallback: "none"` to disable automatic PI fallback. Explicit plugin runtimes such as `codex` fail closed by default unless you set `fallback: "pi"` in the same override scope. Keep model refs canonical as `provider/model`; select Codex, Claude CLI, Gemini CLI, and other execution backends through runtime config instead of legacy runtime provider prefixes. See [Agent runtimes](/concepts/agent-runtimes) for how this differs from provider/model selection.
|
||||
- `agentRuntime`: default low-level agent runtime policy. Omitted id defaults to OpenClaw Pi. Use `id: "pi"` to force the built-in PI harness, `id: "auto"` to let registered plugin harnesses claim supported models, a registered harness id such as `id: "codex"`, or a supported CLI backend alias such as `id: "claude-cli"`. Set `fallback: "none"` to disable automatic PI fallback. Explicit plugin runtimes such as `codex` fail closed by default unless you set `fallback: "pi"` in the same override scope. Keep model refs canonical as `provider/model`; select Codex, Claude CLI, Gemini CLI, and other execution backends through runtime config instead of legacy runtime provider prefixes. See [Agent runtimes](/concepts/agent-runtimes) for how this differs from provider/model selection.
|
||||
- Config writers that mutate these fields (for example `/models set`, `/models set-image`, and fallback add/remove commands) save canonical object form and preserve existing fallback lists when possible.
|
||||
- `maxConcurrent`: max parallel agent runs across sessions (each session still serialized). Default: 4.
|
||||
|
||||
### `agents.defaults.embeddedHarness`
|
||||
### `agents.defaults.agentRuntime`
|
||||
|
||||
`embeddedHarness` controls which low-level executor runs embedded agent turns.
|
||||
Most deployments should keep the default OpenClaw Pi runtime.
|
||||
Use it when a trusted plugin provides a native harness, such as the bundled
|
||||
Codex app-server harness. For the mental model, see
|
||||
[Agent runtimes](/concepts/agent-runtimes).
|
||||
`agentRuntime` controls which low-level executor runs agent turns. Most
|
||||
deployments should keep the default OpenClaw Pi runtime. Use it when a trusted
|
||||
plugin provides a native harness, such as the bundled Codex app-server harness,
|
||||
or when you want a supported CLI backend such as Claude CLI. For the mental
|
||||
model, see [Agent runtimes](/concepts/agent-runtimes).
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
defaults: {
|
||||
model: "openai/gpt-5.5",
|
||||
embeddedHarness: {
|
||||
runtime: "codex",
|
||||
agentRuntime: {
|
||||
id: "codex",
|
||||
fallback: "none",
|
||||
},
|
||||
},
|
||||
@@ -399,12 +399,14 @@ Codex app-server harness. For the mental model, see
|
||||
}
|
||||
```
|
||||
|
||||
- `runtime`: `"auto"`, `"pi"`, or a registered plugin harness id. The bundled Codex plugin registers `codex`.
|
||||
- `fallback`: `"pi"` or `"none"`. In `runtime: "auto"`, omitted fallback defaults to `"pi"` so old configs can keep using PI when no plugin harness claims a run. In explicit plugin runtime mode, such as `runtime: "codex"`, omitted fallback defaults to `"none"` so a missing harness fails instead of silently using PI. Runtime overrides do not inherit fallback from a broader scope; set `fallback: "pi"` alongside the explicit runtime when you intentionally want that compatibility fallback. Selected plugin harness failures always surface directly.
|
||||
- Environment overrides: `OPENCLAW_AGENT_RUNTIME=<id|auto|pi>` overrides `runtime`; `OPENCLAW_AGENT_HARNESS_FALLBACK=pi|none` overrides fallback for that process.
|
||||
- For Codex-only deployments, set `model: "openai/gpt-5.5"` and `embeddedHarness.runtime: "codex"`. You may also set `embeddedHarness.fallback: "none"` explicitly for readability; it is the default for explicit plugin runtimes.
|
||||
- `id`: `"auto"`, `"pi"`, a registered plugin harness id, or a supported CLI backend alias. The bundled Codex plugin registers `codex`; the bundled Anthropic plugin provides the `claude-cli` CLI backend.
|
||||
- `fallback`: `"pi"` or `"none"`. In `id: "auto"`, omitted fallback defaults to `"pi"` so old configs can keep using PI when no plugin harness claims a run. In explicit plugin runtime mode, such as `id: "codex"`, omitted fallback defaults to `"none"` so a missing harness fails instead of silently using PI. Runtime overrides do not inherit fallback from a broader scope; set `fallback: "pi"` alongside the explicit runtime when you intentionally want that compatibility fallback. Selected plugin harness failures always surface directly.
|
||||
- Environment overrides: `OPENCLAW_AGENT_RUNTIME=<id|auto|pi>` overrides `id`; `OPENCLAW_AGENT_HARNESS_FALLBACK=pi|none` overrides fallback for that process.
|
||||
- For Codex-only deployments, set `model: "openai/gpt-5.5"` and `agentRuntime.id: "codex"`. You may also set `agentRuntime.fallback: "none"` explicitly for readability; it is the default for explicit plugin runtimes.
|
||||
- For Claude CLI deployments, prefer `model: "anthropic/claude-opus-4-7"` plus `agentRuntime.id: "claude-cli"`. Legacy `claude-cli/claude-opus-4-7` model refs still work for compatibility, but new config should keep provider/model selection canonical and put the execution backend in `agentRuntime.id`.
|
||||
- Older runtime-policy keys are rewritten to `agentRuntime` by `openclaw doctor --fix`.
|
||||
- Harness choice is pinned per session id after the first embedded run. Config/env changes affect new or reset sessions, not an existing transcript. Legacy sessions with transcript history but no recorded pin are treated as PI-pinned. `/status` reports the effective runtime, for example `Runtime: OpenClaw Pi Default` or `Runtime: OpenAI Codex`.
|
||||
- This only controls the embedded chat harness. Media generation, vision, PDF, music, video, and TTS still use their provider/model settings.
|
||||
- This only controls text agent-turn execution. Media generation, vision, PDF, music, video, and TTS still use their provider/model settings.
|
||||
|
||||
**Built-in alias shorthands** (only apply when the model is in `agents.defaults.models`):
|
||||
|
||||
@@ -552,6 +554,8 @@ Periodic heartbeat runs.
|
||||
qualityGuard: { enabled: true, maxRetries: 1 },
|
||||
postCompactionSections: ["Session Startup", "Red Lines"], // [] disables reinjection
|
||||
model: "openrouter/anthropic/claude-sonnet-4-6", // optional compaction-only model override
|
||||
truncateAfterCompaction: true, // rotate to a smaller successor JSONL after compaction
|
||||
maxActiveTranscriptBytes: "20mb", // optional preflight local compaction trigger
|
||||
notifyUser: true, // send brief notices when compaction starts and completes (default: false)
|
||||
memoryFlush: {
|
||||
enabled: true,
|
||||
@@ -574,6 +578,7 @@ Periodic heartbeat runs.
|
||||
- `qualityGuard`: retry-on-malformed-output checks for safeguard summaries. Enabled by default in safeguard mode; set `enabled: false` to skip the audit.
|
||||
- `postCompactionSections`: optional AGENTS.md H2/H3 section names to re-inject after compaction. Defaults to `["Session Startup", "Red Lines"]`; set `[]` to disable reinjection. When unset or explicitly set to that default pair, older `Every Session`/`Safety` headings are also accepted as a legacy fallback.
|
||||
- `model`: optional `provider/model-id` override for compaction summarization only. Use this when the main session should keep one model but compaction summaries should run on another; when unset, compaction uses the session's primary model.
|
||||
- `maxActiveTranscriptBytes`: optional byte threshold (`number` or strings like `"20mb"`) that triggers normal local compaction before a run when the active JSONL grows past the threshold. Requires `truncateAfterCompaction` so successful compaction can rotate to a smaller successor transcript. Disabled when unset or `0`.
|
||||
- `notifyUser`: when `true`, sends brief notices to the user when compaction starts and when it completes (for example, "Compacting context..." and "Compaction complete"). Disabled by default to keep compaction silent.
|
||||
- `memoryFlush`: silent agentic turn before auto-compaction to store durable memories. Skipped when workspace is read-only.
|
||||
|
||||
@@ -923,7 +928,7 @@ for provider examples and precedence.
|
||||
thinkingDefault: "high", // per-agent thinking level override
|
||||
reasoningDefault: "on", // per-agent reasoning visibility override
|
||||
fastModeDefault: false, // per-agent fast mode override
|
||||
embeddedHarness: { runtime: "auto", fallback: "pi" },
|
||||
agentRuntime: { id: "auto", fallback: "pi" },
|
||||
params: { cacheRetention: "none" }, // overrides matching defaults.models params by key
|
||||
tts: {
|
||||
providers: {
|
||||
@@ -970,7 +975,7 @@ for provider examples and precedence.
|
||||
- `thinkingDefault`: optional per-agent default thinking level (`off | minimal | low | medium | high | xhigh | adaptive | max`). Overrides `agents.defaults.thinkingDefault` for this agent when no per-message or session override is set. The selected provider/model profile controls which values are valid; for Google Gemini, `adaptive` keeps provider-owned dynamic thinking (`thinkingLevel` omitted on Gemini 3/3.1, `thinkingBudget: -1` on Gemini 2.5).
|
||||
- `reasoningDefault`: optional per-agent default reasoning visibility (`on | off | stream`). Applies when no per-message or session reasoning override is set.
|
||||
- `fastModeDefault`: optional per-agent default for fast mode (`true | false`). Applies when no per-message or session fast-mode override is set.
|
||||
- `embeddedHarness`: optional per-agent low-level harness policy override. Use `{ runtime: "codex" }` to make one agent Codex-only while other agents keep the default PI fallback in `auto` mode.
|
||||
- `agentRuntime`: optional per-agent low-level runtime policy override. Use `{ id: "codex" }` to make one agent Codex-only while other agents keep the default PI fallback in `auto` mode.
|
||||
- `runtime`: optional per-agent runtime descriptor. Use `type: "acp"` with `runtime.acp` defaults (`agent`, `backend`, `mode`, `cwd`) when the agent should default to ACP harness sessions.
|
||||
- `identity.avatar`: workspace-relative path, `http(s)` URL, or `data:` URI.
|
||||
- `identity` derives defaults: `ackReaction` from `emoji`, `mentionPatterns` from `name`/`emoji`.
|
||||
|
||||
@@ -5,11 +5,10 @@ read_when:
|
||||
- Registering custom providers or overriding base URLs
|
||||
- Setting up OpenAI-compatible self-hosted endpoints
|
||||
title: "Configuration — tools and custom providers"
|
||||
sidebarTitle: "Tools and custom providers"
|
||||
---
|
||||
|
||||
`tools.*` config keys and custom provider / base-URL setup. For agents,
|
||||
channels, and other top-level config keys, see
|
||||
[Configuration reference](/gateway/configuration-reference).
|
||||
`tools.*` config keys and custom provider / base-URL setup. For agents, channels, and other top-level config keys, see [Configuration reference](/gateway/configuration-reference).
|
||||
|
||||
## Tools
|
||||
|
||||
@@ -17,7 +16,9 @@ channels, and other top-level config keys, see
|
||||
|
||||
`tools.profile` sets a base allowlist before `tools.allow`/`tools.deny`:
|
||||
|
||||
<Note>
|
||||
Local onboarding defaults new local configs to `tools.profile: "coding"` when unset (existing explicit profiles are preserved).
|
||||
</Note>
|
||||
|
||||
| Profile | Includes |
|
||||
| ----------- | ------------------------------------------------------------------------------------------------------------------------------- |
|
||||
@@ -113,8 +114,7 @@ Controls elevated exec access outside the sandbox:
|
||||
|
||||
### `tools.loopDetection`
|
||||
|
||||
Tool-loop safety checks are **disabled by default**. Set `enabled: true` to activate detection.
|
||||
Settings can be defined globally in `tools.loopDetection` and overridden per-agent at `agents.list[].tools.loopDetection`.
|
||||
Tool-loop safety checks are **disabled by default**. Set `enabled: true` to activate detection. Settings can be defined globally in `tools.loopDetection` and overridden per-agent at `agents.list[].tools.loopDetection`.
|
||||
|
||||
```json5
|
||||
{
|
||||
@@ -135,14 +135,31 @@ Settings can be defined globally in `tools.loopDetection` and overridden per-age
|
||||
}
|
||||
```
|
||||
|
||||
- `historySize`: max tool-call history retained for loop analysis.
|
||||
- `warningThreshold`: repeating no-progress pattern threshold for warnings.
|
||||
- `criticalThreshold`: higher repeating threshold for blocking critical loops.
|
||||
- `globalCircuitBreakerThreshold`: hard stop threshold for any no-progress run.
|
||||
- `detectors.genericRepeat`: warn on repeated same-tool/same-args calls.
|
||||
- `detectors.knownPollNoProgress`: warn/block on known poll tools (`process.poll`, `command_status`, etc.).
|
||||
- `detectors.pingPong`: warn/block on alternating no-progress pair patterns.
|
||||
- If `warningThreshold >= criticalThreshold` or `criticalThreshold >= globalCircuitBreakerThreshold`, validation fails.
|
||||
<ParamField path="historySize" type="number">
|
||||
Max tool-call history retained for loop analysis.
|
||||
</ParamField>
|
||||
<ParamField path="warningThreshold" type="number">
|
||||
Repeating no-progress pattern threshold for warnings.
|
||||
</ParamField>
|
||||
<ParamField path="criticalThreshold" type="number">
|
||||
Higher repeating threshold for blocking critical loops.
|
||||
</ParamField>
|
||||
<ParamField path="globalCircuitBreakerThreshold" type="number">
|
||||
Hard stop threshold for any no-progress run.
|
||||
</ParamField>
|
||||
<ParamField path="detectors.genericRepeat" type="boolean">
|
||||
Warn on repeated same-tool/same-args calls.
|
||||
</ParamField>
|
||||
<ParamField path="detectors.knownPollNoProgress" type="boolean">
|
||||
Warn/block on known poll tools (`process.poll`, `command_status`, etc.).
|
||||
</ParamField>
|
||||
<ParamField path="detectors.pingPong" type="boolean">
|
||||
Warn/block on alternating no-progress pair patterns.
|
||||
</ParamField>
|
||||
|
||||
<Warning>
|
||||
If `warningThreshold >= criticalThreshold` or `criticalThreshold >= globalCircuitBreakerThreshold`, validation fails.
|
||||
</Warning>
|
||||
|
||||
### `tools.web`
|
||||
|
||||
@@ -208,34 +225,33 @@ Configures inbound media understanding (image/audio/video):
|
||||
}
|
||||
```
|
||||
|
||||
<Accordion title="Media model entry fields">
|
||||
<AccordionGroup>
|
||||
<Accordion title="Media model entry fields">
|
||||
**Provider entry** (`type: "provider"` or omitted):
|
||||
|
||||
**Provider entry** (`type: "provider"` or omitted):
|
||||
- `provider`: API provider id (`openai`, `anthropic`, `google`/`gemini`, `groq`, etc.)
|
||||
- `model`: model id override
|
||||
- `profile` / `preferredProfile`: `auth-profiles.json` profile selection
|
||||
|
||||
- `provider`: API provider id (`openai`, `anthropic`, `google`/`gemini`, `groq`, etc.)
|
||||
- `model`: model id override
|
||||
- `profile` / `preferredProfile`: `auth-profiles.json` profile selection
|
||||
**CLI entry** (`type: "cli"`):
|
||||
|
||||
**CLI entry** (`type: "cli"`):
|
||||
- `command`: executable to run
|
||||
- `args`: templated args (supports `{{MediaPath}}`, `{{Prompt}}`, `{{MaxChars}}`, etc.)
|
||||
|
||||
- `command`: executable to run
|
||||
- `args`: templated args (supports `{{MediaPath}}`, `{{Prompt}}`, `{{MaxChars}}`, etc.)
|
||||
**Common fields:**
|
||||
|
||||
**Common fields:**
|
||||
- `capabilities`: optional list (`image`, `audio`, `video`). Defaults: `openai`/`anthropic`/`minimax` → image, `google` → image+audio+video, `groq` → audio.
|
||||
- `prompt`, `maxChars`, `maxBytes`, `timeoutSeconds`, `language`: per-entry overrides.
|
||||
- Failures fall back to the next entry.
|
||||
|
||||
- `capabilities`: optional list (`image`, `audio`, `video`). Defaults: `openai`/`anthropic`/`minimax` → image, `google` → image+audio+video, `groq` → audio.
|
||||
- `prompt`, `maxChars`, `maxBytes`, `timeoutSeconds`, `language`: per-entry overrides.
|
||||
- Failures fall back to the next entry.
|
||||
Provider auth follows standard order: `auth-profiles.json` → env vars → `models.providers.*.apiKey`.
|
||||
|
||||
Provider auth follows standard order: `auth-profiles.json` → env vars → `models.providers.*.apiKey`.
|
||||
**Async completion fields:**
|
||||
|
||||
**Async completion fields:**
|
||||
- `asyncCompletion.directSend`: when `true`, completed async `music_generate` and `video_generate` tasks try direct channel delivery first. Default: `false` (legacy requester-session wake/model-delivery path).
|
||||
|
||||
- `asyncCompletion.directSend`: when `true`, completed async `music_generate`
|
||||
and `video_generate` tasks try direct channel delivery first. Default: `false`
|
||||
(legacy requester-session wake/model-delivery path).
|
||||
|
||||
</Accordion>
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
### `tools.agentToAgent`
|
||||
|
||||
@@ -267,13 +283,15 @@ Default: `tree` (current session + sessions spawned by it, such as subagents).
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- `self`: only the current session key.
|
||||
- `tree`: current session + sessions spawned by the current session (subagents).
|
||||
- `agent`: any session belonging to the current agent id (can include other users if you run per-sender sessions under the same agent id).
|
||||
- `all`: any session. Cross-agent targeting still requires `tools.agentToAgent`.
|
||||
- Sandbox clamp: when the current session is sandboxed and `agents.defaults.sandbox.sessionToolsVisibility="spawned"`, visibility is forced to `tree` even if `tools.sessions.visibility="all"`.
|
||||
<AccordionGroup>
|
||||
<Accordion title="Visibility scopes">
|
||||
- `self`: only the current session key.
|
||||
- `tree`: current session + sessions spawned by the current session (subagents).
|
||||
- `agent`: any session belonging to the current agent id (can include other users if you run per-sender sessions under the same agent id).
|
||||
- `all`: any session. Cross-agent targeting still requires `tools.agentToAgent`.
|
||||
- Sandbox clamp: when the current session is sandboxed and `agents.defaults.sandbox.sessionToolsVisibility="spawned"`, visibility is forced to `tree` even if `tools.sessions.visibility="all"`.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
### `tools.sessions_spawn`
|
||||
|
||||
@@ -295,14 +313,16 @@ Controls inline attachment support for `sessions_spawn`.
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- Attachments are only supported for `runtime: "subagent"`. ACP runtime rejects them.
|
||||
- Files are materialized into the child workspace at `.openclaw/attachments/<uuid>/` with a `.manifest.json`.
|
||||
- Attachment content is automatically redacted from transcript persistence.
|
||||
- Base64 inputs are validated with strict alphabet/padding checks and a pre-decode size guard.
|
||||
- File permissions are `0700` for directories and `0600` for files.
|
||||
- Cleanup follows the `cleanup` policy: `delete` always removes attachments; `keep` retains them only when `retainOnSessionKeep: true`.
|
||||
<AccordionGroup>
|
||||
<Accordion title="Attachment notes">
|
||||
- Attachments are only supported for `runtime: "subagent"`. ACP runtime rejects them.
|
||||
- Files are materialized into the child workspace at `.openclaw/attachments/<uuid>/` with a `.manifest.json`.
|
||||
- Attachment content is automatically redacted from transcript persistence.
|
||||
- Base64 inputs are validated with strict alphabet/padding checks and a pre-decode size guard.
|
||||
- File permissions are `0700` for directories and `0600` for files.
|
||||
- Cleanup follows the `cleanup` policy: `delete` always removes attachments; `keep` retains them only when `retainOnSessionKeep: true`.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
<a id="toolsexperimental"></a>
|
||||
|
||||
@@ -320,8 +340,6 @@ Experimental built-in tool flags. Default off unless a strict-agentic GPT-5 auto
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- `planTool`: enables the structured `update_plan` tool for non-trivial multi-step work tracking.
|
||||
- Default: `false` unless `agents.defaults.embeddedPi.executionContract` (or a per-agent override) is set to `"strict-agentic"` for an OpenAI or OpenAI Codex GPT-5-family run. Set `true` to force the tool on outside that scope, or `false` to keep it off even for strict-agentic GPT-5 runs.
|
||||
- When enabled, the system prompt also adds usage guidance so the model only uses it for substantial work and keeps at most one step `in_progress`.
|
||||
@@ -382,286 +400,285 @@ OpenClaw uses the built-in model catalog. Add custom providers via `models.provi
|
||||
}
|
||||
```
|
||||
|
||||
- Use `authHeader: true` + `headers` for custom auth needs.
|
||||
- Override agent config root with `OPENCLAW_AGENT_DIR` (or `PI_CODING_AGENT_DIR`, a legacy environment variable alias).
|
||||
- Merge precedence for matching provider IDs:
|
||||
- Non-empty agent `models.json` `baseUrl` values win.
|
||||
- Non-empty agent `apiKey` values win only when that provider is not SecretRef-managed in current config/auth-profile context.
|
||||
- SecretRef-managed provider `apiKey` values are refreshed from source markers (`ENV_VAR_NAME` for env refs, `secretref-managed` for file/exec refs) instead of persisting resolved secrets.
|
||||
- SecretRef-managed provider header values are refreshed from source markers (`secretref-env:ENV_VAR_NAME` for env refs, `secretref-managed` for file/exec refs).
|
||||
- Empty or missing agent `apiKey`/`baseUrl` fall back to `models.providers` in config.
|
||||
- Matching model `contextWindow`/`maxTokens` use the higher value between explicit config and implicit catalog values.
|
||||
- Matching model `contextTokens` preserves an explicit runtime cap when present; use it to limit effective context without changing native model metadata.
|
||||
- Use `models.mode: "replace"` when you want config to fully rewrite `models.json`.
|
||||
- Marker persistence is source-authoritative: markers are written from the active source config snapshot (pre-resolution), not from resolved runtime secret values.
|
||||
<AccordionGroup>
|
||||
<Accordion title="Auth and merge precedence">
|
||||
- Use `authHeader: true` + `headers` for custom auth needs.
|
||||
- Override agent config root with `OPENCLAW_AGENT_DIR` (or `PI_CODING_AGENT_DIR`, a legacy environment variable alias).
|
||||
- Merge precedence for matching provider IDs:
|
||||
- Non-empty agent `models.json` `baseUrl` values win.
|
||||
- Non-empty agent `apiKey` values win only when that provider is not SecretRef-managed in current config/auth-profile context.
|
||||
- SecretRef-managed provider `apiKey` values are refreshed from source markers (`ENV_VAR_NAME` for env refs, `secretref-managed` for file/exec refs) instead of persisting resolved secrets.
|
||||
- SecretRef-managed provider header values are refreshed from source markers (`secretref-env:ENV_VAR_NAME` for env refs, `secretref-managed` for file/exec refs).
|
||||
- Empty or missing agent `apiKey`/`baseUrl` fall back to `models.providers` in config.
|
||||
- Matching model `contextWindow`/`maxTokens` use the higher value between explicit config and implicit catalog values.
|
||||
- Matching model `contextTokens` preserves an explicit runtime cap when present; use it to limit effective context without changing native model metadata.
|
||||
- Use `models.mode: "replace"` when you want config to fully rewrite `models.json`.
|
||||
- Marker persistence is source-authoritative: markers are written from the active source config snapshot (pre-resolution), not from resolved runtime secret values.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
### Provider field details
|
||||
|
||||
- `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`).
|
||||
- `models.providers.*.injectNumCtxForOpenAICompat`: for Ollama + `openai-completions`, inject `options.num_ctx` into requests (default: `true`).
|
||||
- `models.providers.*.authHeader`: force credential transport in the `Authorization` header when required.
|
||||
- `models.providers.*.baseUrl`: upstream API base URL.
|
||||
- `models.providers.*.headers`: extra static headers for proxy/tenant routing.
|
||||
- `models.providers.*.request`: transport overrides for model-provider HTTP requests.
|
||||
- `request.headers`: extra headers (merged with provider defaults). Values accept SecretRef.
|
||||
- `request.auth`: auth strategy override. Modes: `"provider-default"` (use provider's built-in auth), `"authorization-bearer"` (with `token`), `"header"` (with `headerName`, `value`, optional `prefix`).
|
||||
- `request.proxy`: HTTP proxy override. Modes: `"env-proxy"` (use `HTTP_PROXY`/`HTTPS_PROXY` env vars), `"explicit-proxy"` (with `url`). Both modes accept an optional `tls` sub-object.
|
||||
- `request.tls`: TLS override for direct connections. Fields: `ca`, `cert`, `key`, `passphrase` (all accept SecretRef), `serverName`, `insecureSkipVerify`.
|
||||
- `request.allowPrivateNetwork`: when `true`, allow HTTPS to `baseUrl` when DNS resolves to private, CGNAT, or similar ranges, via the provider HTTP fetch guard (operator opt-in for trusted self-hosted OpenAI-compatible endpoints). WebSocket uses the same `request` for headers/TLS but not that fetch SSRF gate. Default `false`.
|
||||
- `models.providers.*.models`: explicit provider model catalog entries.
|
||||
- `models.providers.*.models.*.contextWindow`: native model context window metadata.
|
||||
- `models.providers.*.models.*.contextTokens`: optional runtime context cap. Use this when you want a smaller effective context budget than the model's native `contextWindow`; `openclaw models list` shows both values when they differ.
|
||||
- `models.providers.*.models.*.compat.supportsDeveloperRole`: optional compatibility hint. For `api: "openai-completions"` with a non-empty non-native `baseUrl` (host not `api.openai.com`), OpenClaw forces this to `false` at runtime. Empty/omitted `baseUrl` keeps default OpenAI behavior.
|
||||
- `models.providers.*.models.*.compat.requiresStringContent`: optional compatibility hint for string-only OpenAI-compatible chat endpoints. When `true`, OpenClaw flattens pure text `messages[].content` arrays into plain strings before sending the request.
|
||||
- `plugins.entries.amazon-bedrock.config.discovery`: Bedrock auto-discovery settings root.
|
||||
- `plugins.entries.amazon-bedrock.config.discovery.enabled`: turn implicit discovery on/off.
|
||||
- `plugins.entries.amazon-bedrock.config.discovery.region`: AWS region for discovery.
|
||||
- `plugins.entries.amazon-bedrock.config.discovery.providerFilter`: optional provider-id filter for targeted discovery.
|
||||
- `plugins.entries.amazon-bedrock.config.discovery.refreshInterval`: polling interval for discovery refresh.
|
||||
- `plugins.entries.amazon-bedrock.config.discovery.defaultContextWindow`: fallback context window for discovered models.
|
||||
- `plugins.entries.amazon-bedrock.config.discovery.defaultMaxTokens`: fallback max output tokens for discovered models.
|
||||
<AccordionGroup>
|
||||
<Accordion title="Top-level catalog">
|
||||
- `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`.
|
||||
</Accordion>
|
||||
<Accordion title="Provider connection and auth">
|
||||
- `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`).
|
||||
- `models.providers.*.contextWindow`: default native context window for models under this provider when the model entry does not set `contextWindow`.
|
||||
- `models.providers.*.contextTokens`: default effective runtime context cap for models under this provider when the model entry does not set `contextTokens`.
|
||||
- `models.providers.*.maxTokens`: default output-token cap for models under this provider when the model entry does not set `maxTokens`.
|
||||
- `models.providers.*.timeoutSeconds`: optional per-provider model HTTP request timeout in seconds, including connect, headers, body, and total request abort handling.
|
||||
- `models.providers.*.injectNumCtxForOpenAICompat`: for Ollama + `openai-completions`, inject `options.num_ctx` into requests (default: `true`).
|
||||
- `models.providers.*.authHeader`: force credential transport in the `Authorization` header when required.
|
||||
- `models.providers.*.baseUrl`: upstream API base URL.
|
||||
- `models.providers.*.headers`: extra static headers for proxy/tenant routing.
|
||||
</Accordion>
|
||||
<Accordion title="Request transport overrides">
|
||||
`models.providers.*.request`: transport overrides for model-provider HTTP requests.
|
||||
|
||||
- `request.headers`: extra headers (merged with provider defaults). Values accept SecretRef.
|
||||
- `request.auth`: auth strategy override. Modes: `"provider-default"` (use provider's built-in auth), `"authorization-bearer"` (with `token`), `"header"` (with `headerName`, `value`, optional `prefix`).
|
||||
- `request.proxy`: HTTP proxy override. Modes: `"env-proxy"` (use `HTTP_PROXY`/`HTTPS_PROXY` env vars), `"explicit-proxy"` (with `url`). Both modes accept an optional `tls` sub-object.
|
||||
- `request.tls`: TLS override for direct connections. Fields: `ca`, `cert`, `key`, `passphrase` (all accept SecretRef), `serverName`, `insecureSkipVerify`.
|
||||
- `request.allowPrivateNetwork`: when `true`, allow HTTPS to `baseUrl` when DNS resolves to private, CGNAT, or similar ranges, via the provider HTTP fetch guard (operator opt-in for trusted self-hosted OpenAI-compatible endpoints). WebSocket uses the same `request` for headers/TLS but not that fetch SSRF gate. Default `false`.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Model catalog entries">
|
||||
- `models.providers.*.models`: explicit provider model catalog entries.
|
||||
- `models.providers.*.models.*.contextWindow`: native model context window metadata. This overrides provider-level `contextWindow` for that model.
|
||||
- `models.providers.*.models.*.contextTokens`: optional runtime context cap. This overrides provider-level `contextTokens`; use it when you want a smaller effective context budget than the model's native `contextWindow`; `openclaw models list` shows both values when they differ.
|
||||
- `models.providers.*.models.*.compat.supportsDeveloperRole`: optional compatibility hint. For `api: "openai-completions"` with a non-empty non-native `baseUrl` (host not `api.openai.com`), OpenClaw forces this to `false` at runtime. Empty/omitted `baseUrl` keeps default OpenAI behavior.
|
||||
- `models.providers.*.models.*.compat.requiresStringContent`: optional compatibility hint for string-only OpenAI-compatible chat endpoints. When `true`, OpenClaw flattens pure text `messages[].content` arrays into plain strings before sending the request.
|
||||
</Accordion>
|
||||
<Accordion title="Amazon Bedrock discovery">
|
||||
- `plugins.entries.amazon-bedrock.config.discovery`: Bedrock auto-discovery settings root.
|
||||
- `plugins.entries.amazon-bedrock.config.discovery.enabled`: turn implicit discovery on/off.
|
||||
- `plugins.entries.amazon-bedrock.config.discovery.region`: AWS region for discovery.
|
||||
- `plugins.entries.amazon-bedrock.config.discovery.providerFilter`: optional provider-id filter for targeted discovery.
|
||||
- `plugins.entries.amazon-bedrock.config.discovery.refreshInterval`: polling interval for discovery refresh.
|
||||
- `plugins.entries.amazon-bedrock.config.discovery.defaultContextWindow`: fallback context window for discovered models.
|
||||
- `plugins.entries.amazon-bedrock.config.discovery.defaultMaxTokens`: fallback max output tokens for discovered models.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
### Provider examples
|
||||
|
||||
<Accordion title="Cerebras (GLM 4.6 / 4.7)">
|
||||
|
||||
```json5
|
||||
{
|
||||
env: { CEREBRAS_API_KEY: "sk-..." },
|
||||
agents: {
|
||||
defaults: {
|
||||
model: {
|
||||
primary: "cerebras/zai-glm-4.7",
|
||||
fallbacks: ["cerebras/zai-glm-4.6"],
|
||||
<AccordionGroup>
|
||||
<Accordion title="Cerebras (GLM 4.6 / 4.7)">
|
||||
```json5
|
||||
{
|
||||
env: { CEREBRAS_API_KEY: "sk-..." },
|
||||
agents: {
|
||||
defaults: {
|
||||
model: {
|
||||
primary: "cerebras/zai-glm-4.7",
|
||||
fallbacks: ["cerebras/zai-glm-4.6"],
|
||||
},
|
||||
models: {
|
||||
"cerebras/zai-glm-4.7": { alias: "GLM 4.7 (Cerebras)" },
|
||||
"cerebras/zai-glm-4.6": { alias: "GLM 4.6 (Cerebras)" },
|
||||
},
|
||||
},
|
||||
},
|
||||
models: {
|
||||
"cerebras/zai-glm-4.7": { alias: "GLM 4.7 (Cerebras)" },
|
||||
"cerebras/zai-glm-4.6": { alias: "GLM 4.6 (Cerebras)" },
|
||||
},
|
||||
},
|
||||
},
|
||||
models: {
|
||||
mode: "merge",
|
||||
providers: {
|
||||
cerebras: {
|
||||
baseUrl: "https://api.cerebras.ai/v1",
|
||||
apiKey: "${CEREBRAS_API_KEY}",
|
||||
api: "openai-completions",
|
||||
models: [
|
||||
{ id: "zai-glm-4.7", name: "GLM 4.7 (Cerebras)" },
|
||||
{ id: "zai-glm-4.6", name: "GLM 4.6 (Cerebras)" },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Use `cerebras/zai-glm-4.7` for Cerebras; `zai/glm-4.7` for Z.AI direct.
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="OpenCode">
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
defaults: {
|
||||
model: { primary: "opencode/claude-opus-4-6" },
|
||||
models: { "opencode/claude-opus-4-6": { alias: "Opus" } },
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Set `OPENCODE_API_KEY` (or `OPENCODE_ZEN_API_KEY`). Use `opencode/...` refs for the Zen catalog or `opencode-go/...` refs for the Go catalog. Shortcut: `openclaw onboard --auth-choice opencode-zen` or `openclaw onboard --auth-choice opencode-go`.
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Z.AI (GLM-4.7)">
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
defaults: {
|
||||
model: { primary: "zai/glm-4.7" },
|
||||
models: { "zai/glm-4.7": {} },
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Set `ZAI_API_KEY`. `z.ai/*` and `z-ai/*` are accepted aliases. Shortcut: `openclaw onboard --auth-choice zai-api-key`.
|
||||
|
||||
- General endpoint: `https://api.z.ai/api/paas/v4`
|
||||
- Coding endpoint (default): `https://api.z.ai/api/coding/paas/v4`
|
||||
- For the general endpoint, define a custom provider with the base URL override.
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Moonshot AI (Kimi)">
|
||||
|
||||
```json5
|
||||
{
|
||||
env: { MOONSHOT_API_KEY: "sk-..." },
|
||||
agents: {
|
||||
defaults: {
|
||||
model: { primary: "moonshot/kimi-k2.6" },
|
||||
models: { "moonshot/kimi-k2.6": { alias: "Kimi K2.6" } },
|
||||
},
|
||||
},
|
||||
models: {
|
||||
mode: "merge",
|
||||
providers: {
|
||||
moonshot: {
|
||||
baseUrl: "https://api.moonshot.ai/v1",
|
||||
apiKey: "${MOONSHOT_API_KEY}",
|
||||
api: "openai-completions",
|
||||
models: [
|
||||
{
|
||||
id: "kimi-k2.6",
|
||||
name: "Kimi K2.6",
|
||||
reasoning: false,
|
||||
input: ["text", "image"],
|
||||
cost: { input: 0.95, output: 4, cacheRead: 0.16, cacheWrite: 0 },
|
||||
contextWindow: 262144,
|
||||
maxTokens: 262144,
|
||||
mode: "merge",
|
||||
providers: {
|
||||
cerebras: {
|
||||
baseUrl: "https://api.cerebras.ai/v1",
|
||||
apiKey: "${CEREBRAS_API_KEY}",
|
||||
api: "openai-completions",
|
||||
models: [
|
||||
{ id: "zai-glm-4.7", name: "GLM 4.7 (Cerebras)" },
|
||||
{ id: "zai-glm-4.6", name: "GLM 4.6 (Cerebras)" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
}
|
||||
```
|
||||
|
||||
For the China endpoint: `baseUrl: "https://api.moonshot.cn/v1"` or `openclaw onboard --auth-choice moonshot-api-key-cn`.
|
||||
Use `cerebras/zai-glm-4.7` for Cerebras; `zai/glm-4.7` for Z.AI direct.
|
||||
|
||||
Native Moonshot endpoints advertise streaming usage compatibility on the shared
|
||||
`openai-completions` transport, and OpenClaw keys that off endpoint capabilities
|
||||
rather than the built-in provider id alone.
|
||||
</Accordion>
|
||||
<Accordion title="Kimi Coding">
|
||||
```json5
|
||||
{
|
||||
env: { KIMI_API_KEY: "sk-..." },
|
||||
agents: {
|
||||
defaults: {
|
||||
model: { primary: "kimi/kimi-code" },
|
||||
models: { "kimi/kimi-code": { alias: "Kimi Code" } },
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
Anthropic-compatible, built-in provider. Shortcut: `openclaw onboard --auth-choice kimi-code-api-key`.
|
||||
|
||||
<Accordion title="Kimi Coding">
|
||||
|
||||
```json5
|
||||
{
|
||||
env: { KIMI_API_KEY: "sk-..." },
|
||||
agents: {
|
||||
defaults: {
|
||||
model: { primary: "kimi/kimi-code" },
|
||||
models: { "kimi/kimi-code": { alias: "Kimi Code" } },
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Anthropic-compatible, built-in provider. Shortcut: `openclaw onboard --auth-choice kimi-code-api-key`.
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Synthetic (Anthropic-compatible)">
|
||||
|
||||
```json5
|
||||
{
|
||||
env: { SYNTHETIC_API_KEY: "sk-..." },
|
||||
agents: {
|
||||
defaults: {
|
||||
model: { primary: "synthetic/hf:MiniMaxAI/MiniMax-M2.5" },
|
||||
models: { "synthetic/hf:MiniMaxAI/MiniMax-M2.5": { alias: "MiniMax M2.5" } },
|
||||
},
|
||||
},
|
||||
models: {
|
||||
mode: "merge",
|
||||
providers: {
|
||||
synthetic: {
|
||||
baseUrl: "https://api.synthetic.new/anthropic",
|
||||
apiKey: "${SYNTHETIC_API_KEY}",
|
||||
api: "anthropic-messages",
|
||||
models: [
|
||||
{
|
||||
id: "hf:MiniMaxAI/MiniMax-M2.5",
|
||||
name: "MiniMax M2.5",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 192000,
|
||||
maxTokens: 65536,
|
||||
</Accordion>
|
||||
<Accordion title="Local models (LM Studio)">
|
||||
See [Local Models](/gateway/local-models). TL;DR: run a large local model via LM Studio Responses API on serious hardware; keep hosted models merged for fallback.
|
||||
</Accordion>
|
||||
<Accordion title="MiniMax M2.7 (direct)">
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
defaults: {
|
||||
model: { primary: "minimax/MiniMax-M2.7" },
|
||||
models: {
|
||||
"minimax/MiniMax-M2.7": { alias: "Minimax" },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Base URL should omit `/v1` (Anthropic client appends it). Shortcut: `openclaw onboard --auth-choice synthetic-api-key`.
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="MiniMax M2.7 (direct)">
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
defaults: {
|
||||
model: { primary: "minimax/MiniMax-M2.7" },
|
||||
models: {
|
||||
"minimax/MiniMax-M2.7": { alias: "Minimax" },
|
||||
},
|
||||
},
|
||||
},
|
||||
models: {
|
||||
mode: "merge",
|
||||
providers: {
|
||||
minimax: {
|
||||
baseUrl: "https://api.minimax.io/anthropic",
|
||||
apiKey: "${MINIMAX_API_KEY}",
|
||||
api: "anthropic-messages",
|
||||
models: [
|
||||
{
|
||||
id: "MiniMax-M2.7",
|
||||
name: "MiniMax M2.7",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: { input: 0.3, output: 1.2, cacheRead: 0.06, cacheWrite: 0.375 },
|
||||
contextWindow: 204800,
|
||||
maxTokens: 131072,
|
||||
mode: "merge",
|
||||
providers: {
|
||||
minimax: {
|
||||
baseUrl: "https://api.minimax.io/anthropic",
|
||||
apiKey: "${MINIMAX_API_KEY}",
|
||||
api: "anthropic-messages",
|
||||
models: [
|
||||
{
|
||||
id: "MiniMax-M2.7",
|
||||
name: "MiniMax M2.7",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: { input: 0.3, output: 1.2, cacheRead: 0.06, cacheWrite: 0.375 },
|
||||
contextWindow: 204800,
|
||||
maxTokens: 131072,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
}
|
||||
```
|
||||
|
||||
Set `MINIMAX_API_KEY`. Shortcuts:
|
||||
`openclaw onboard --auth-choice minimax-global-api` or
|
||||
`openclaw onboard --auth-choice minimax-cn-api`.
|
||||
The model catalog defaults to M2.7 only.
|
||||
On the Anthropic-compatible streaming path, OpenClaw disables MiniMax thinking
|
||||
by default unless you explicitly set `thinking` yourself. `/fast on` or
|
||||
`params.fastMode: true` rewrites `MiniMax-M2.7` to
|
||||
`MiniMax-M2.7-highspeed`.
|
||||
Set `MINIMAX_API_KEY`. Shortcuts: `openclaw onboard --auth-choice minimax-global-api` or `openclaw onboard --auth-choice minimax-cn-api`. The model catalog defaults to M2.7 only. On the Anthropic-compatible streaming path, OpenClaw disables MiniMax thinking by default unless you explicitly set `thinking` yourself. `/fast on` or `params.fastMode: true` rewrites `MiniMax-M2.7` to `MiniMax-M2.7-highspeed`.
|
||||
|
||||
</Accordion>
|
||||
</Accordion>
|
||||
<Accordion title="Moonshot AI (Kimi)">
|
||||
```json5
|
||||
{
|
||||
env: { MOONSHOT_API_KEY: "sk-..." },
|
||||
agents: {
|
||||
defaults: {
|
||||
model: { primary: "moonshot/kimi-k2.6" },
|
||||
models: { "moonshot/kimi-k2.6": { alias: "Kimi K2.6" } },
|
||||
},
|
||||
},
|
||||
models: {
|
||||
mode: "merge",
|
||||
providers: {
|
||||
moonshot: {
|
||||
baseUrl: "https://api.moonshot.ai/v1",
|
||||
apiKey: "${MOONSHOT_API_KEY}",
|
||||
api: "openai-completions",
|
||||
models: [
|
||||
{
|
||||
id: "kimi-k2.6",
|
||||
name: "Kimi K2.6",
|
||||
reasoning: false,
|
||||
input: ["text", "image"],
|
||||
cost: { input: 0.95, output: 4, cacheRead: 0.16, cacheWrite: 0 },
|
||||
contextWindow: 262144,
|
||||
maxTokens: 262144,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
<Accordion title="Local models (LM Studio)">
|
||||
For the China endpoint: `baseUrl: "https://api.moonshot.cn/v1"` or `openclaw onboard --auth-choice moonshot-api-key-cn`.
|
||||
|
||||
See [Local Models](/gateway/local-models). TL;DR: run a large local model via LM Studio Responses API on serious hardware; keep hosted models merged for fallback.
|
||||
Native Moonshot endpoints advertise streaming usage compatibility on the shared `openai-completions` transport, and OpenClaw keys that off endpoint capabilities rather than the built-in provider id alone.
|
||||
|
||||
</Accordion>
|
||||
</Accordion>
|
||||
<Accordion title="OpenCode">
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
defaults: {
|
||||
model: { primary: "opencode/claude-opus-4-6" },
|
||||
models: { "opencode/claude-opus-4-6": { alias: "Opus" } },
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Set `OPENCODE_API_KEY` (or `OPENCODE_ZEN_API_KEY`). Use `opencode/...` refs for the Zen catalog or `opencode-go/...` refs for the Go catalog. Shortcut: `openclaw onboard --auth-choice opencode-zen` or `openclaw onboard --auth-choice opencode-go`.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Synthetic (Anthropic-compatible)">
|
||||
```json5
|
||||
{
|
||||
env: { SYNTHETIC_API_KEY: "sk-..." },
|
||||
agents: {
|
||||
defaults: {
|
||||
model: { primary: "synthetic/hf:MiniMaxAI/MiniMax-M2.5" },
|
||||
models: { "synthetic/hf:MiniMaxAI/MiniMax-M2.5": { alias: "MiniMax M2.5" } },
|
||||
},
|
||||
},
|
||||
models: {
|
||||
mode: "merge",
|
||||
providers: {
|
||||
synthetic: {
|
||||
baseUrl: "https://api.synthetic.new/anthropic",
|
||||
apiKey: "${SYNTHETIC_API_KEY}",
|
||||
api: "anthropic-messages",
|
||||
models: [
|
||||
{
|
||||
id: "hf:MiniMaxAI/MiniMax-M2.5",
|
||||
name: "MiniMax M2.5",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 192000,
|
||||
maxTokens: 65536,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Base URL should omit `/v1` (Anthropic client appends it). Shortcut: `openclaw onboard --auth-choice synthetic-api-key`.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Z.AI (GLM-4.7)">
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
defaults: {
|
||||
model: { primary: "zai/glm-4.7" },
|
||||
models: { "zai/glm-4.7": {} },
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Set `ZAI_API_KEY`. `z.ai/*` and `z-ai/*` are accepted aliases. Shortcut: `openclaw onboard --auth-choice zai-api-key`.
|
||||
|
||||
- General endpoint: `https://api.z.ai/api/paas/v4`
|
||||
- Coding endpoint (default): `https://api.z.ai/api/coding/paas/v4`
|
||||
- For the general endpoint, define a custom provider with the base URL override.
|
||||
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
---
|
||||
|
||||
## Related
|
||||
|
||||
- [Configuration reference](/gateway/configuration-reference) — other top-level keys
|
||||
- [Configuration — agents](/gateway/config-agents)
|
||||
- [Configuration — channels](/gateway/config-channels)
|
||||
- [Configuration reference](/gateway/configuration-reference) — other top-level keys
|
||||
- [Tools and plugins](/tools)
|
||||
|
||||
@@ -859,6 +859,7 @@ Notes:
|
||||
- Set `logging.file` for a stable path.
|
||||
- `consoleLevel` bumps to `debug` when `--verbose`.
|
||||
- `maxFileBytes`: maximum active log file size in bytes before rotation (positive integer; default: `104857600` = 100 MB). OpenClaw keeps up to five numbered archives beside the active file.
|
||||
- `redactSensitive` / `redactPatterns`: best-effort masking for console output, file logs, OTLP log records, and persisted session transcript text.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -86,9 +86,9 @@ Security notes:
|
||||
Disable/override:
|
||||
|
||||
- `OPENCLAW_DISABLE_BONJOUR=1` disables advertising.
|
||||
- Docker Compose defaults `OPENCLAW_DISABLE_BONJOUR=1` because bridge networks
|
||||
usually do not carry mDNS multicast reliably; use `0` only on host, macvlan,
|
||||
or another mDNS-capable network.
|
||||
- When `OPENCLAW_DISABLE_BONJOUR` is unset, Bonjour advertises on normal hosts
|
||||
and auto-disables inside detected containers. Use `0` only on host, macvlan,
|
||||
or another mDNS-capable network; use `1` to force-disable.
|
||||
- `gateway.bind` in `~/.openclaw/openclaw.json` controls the Gateway bind mode.
|
||||
- `OPENCLAW_SSH_PORT` overrides the SSH port advertised when `sshPort` is emitted.
|
||||
- `OPENCLAW_TAILNET_DNS` publishes a `tailnetDns` hint (MagicDNS).
|
||||
|
||||
@@ -85,6 +85,7 @@ cat ~/.openclaw/openclaw.json
|
||||
- Legacy on-disk state migration (sessions/agent dir/WhatsApp auth).
|
||||
- Legacy plugin manifest contract key migration (`speechProviders`, `realtimeTranscriptionProviders`, `realtimeVoiceProviders`, `mediaUnderstandingProviders`, `imageGenerationProviders`, `videoGenerationProviders`, `webFetchProviders`, `webSearchProviders` → `contracts`).
|
||||
- Legacy cron store migration (`jobId`, `schedule.cron`, top-level delivery/payload fields, payload `provider`, simple `notify: true` webhook fallback jobs).
|
||||
- Legacy agent runtime-policy migration to `agents.defaults.agentRuntime` and `agents.list[].agentRuntime`.
|
||||
</Accordion>
|
||||
<Accordion title="State and integrity">
|
||||
- Session lock file inspection and stale lock cleanup.
|
||||
@@ -196,6 +197,7 @@ That stages grounded durable candidates into the short-term dreaming store while
|
||||
- `browser.ssrfPolicy.allowPrivateNetwork` → `browser.ssrfPolicy.dangerouslyAllowPrivateNetwork`
|
||||
- `browser.profiles.*.driver: "extension"` → `"existing-session"`
|
||||
- remove `browser.relayBindHost` (legacy extension relay setting)
|
||||
- legacy `models.providers.*.api: "openai"` → `"openai-completions"` (gateway startup also skips providers whose `api` is set to a future or unknown enum value rather than failing closed)
|
||||
|
||||
Doctor warnings also include account-default guidance for multi-account channels:
|
||||
|
||||
@@ -237,7 +239,7 @@ That stages grounded durable candidates into the short-term dreaming store while
|
||||
If you previously added legacy OpenAI transport settings under `models.providers.openai-codex`, they can shadow the built-in Codex OAuth provider path that newer releases use automatically. Doctor warns when it sees those old transport settings alongside Codex OAuth so you can remove or rewrite the stale transport override and get the built-in routing/fallback behavior back. Custom proxies and header-only overrides are still supported and do not trigger this warning.
|
||||
</Accordion>
|
||||
<Accordion title="2f. Codex plugin route warnings">
|
||||
When the bundled Codex plugin is enabled, doctor also checks whether `openai-codex/*` primary model refs still resolve through the default PI runner. That combination is valid when you want Codex OAuth/subscription auth through PI, but it is easy to confuse with the native Codex app-server harness. Doctor warns and points to the explicit app-server shape: `openai/*` plus `embeddedHarness.runtime: "codex"` or `OPENCLAW_AGENT_RUNTIME=codex`.
|
||||
When the bundled Codex plugin is enabled, doctor also checks whether `openai-codex/*` primary model refs still resolve through the default PI runner. That combination is valid when you want Codex OAuth/subscription auth through PI, but it is easy to confuse with the native Codex app-server harness. Doctor warns and points to the explicit app-server shape: `openai/*` plus `agentRuntime.id: "codex"` or `OPENCLAW_AGENT_RUNTIME=codex`.
|
||||
|
||||
Doctor does not repair this automatically because both routes are valid:
|
||||
|
||||
@@ -429,6 +431,7 @@ That stages grounded durable candidates into the short-term dreaming store while
|
||||
- `openclaw doctor --yes` accepts the default repair prompts.
|
||||
- `openclaw doctor --repair` applies recommended fixes without prompts.
|
||||
- `openclaw doctor --repair --force` overwrites custom supervisor configs.
|
||||
- `OPENCLAW_SERVICE_REPAIR_POLICY=external` keeps doctor read-only for gateway service lifecycle. It still reports service health and runs non-service repairs, but skips service install/start/restart/bootstrap, supervisor config rewrites, and legacy service cleanup because an external supervisor owns that lifecycle.
|
||||
- If token auth requires a token and `gateway.auth.token` is SecretRef-managed, doctor service install/repair validates the SecretRef but does not persist resolved plaintext token values into supervisor service environment metadata.
|
||||
- If token auth requires a token and the configured token SecretRef is unresolved, doctor blocks the install/repair path with actionable guidance.
|
||||
- If both `gateway.auth.token` and `gateway.auth.password` are configured and `gateway.auth.mode` is unset, doctor blocks install/repair until mode is set explicitly.
|
||||
|
||||
@@ -4,27 +4,38 @@ read_when:
|
||||
- Adjusting heartbeat cadence or messaging
|
||||
- Deciding between heartbeat and cron for scheduled tasks
|
||||
title: "Heartbeat"
|
||||
sidebarTitle: "Heartbeat"
|
||||
---
|
||||
|
||||
> **Heartbeat vs Cron?** See [Automation & Tasks](/automation) for guidance on when to use each.
|
||||
<Note>
|
||||
**Heartbeat vs cron?** See [Automation & Tasks](/automation) for guidance on when to use each.
|
||||
</Note>
|
||||
|
||||
Heartbeat runs **periodic agent turns** in the main session so the model can
|
||||
surface anything that needs attention without spamming you.
|
||||
Heartbeat runs **periodic agent turns** in the main session so the model can surface anything that needs attention without spamming you.
|
||||
|
||||
Heartbeat is a scheduled main-session turn — it does **not** create [background task](/automation/tasks) records.
|
||||
Task records are for detached work (ACP runs, subagents, isolated cron jobs).
|
||||
Heartbeat is a scheduled main-session turn — it does **not** create [background task](/automation/tasks) records. Task records are for detached work (ACP runs, subagents, isolated cron jobs).
|
||||
|
||||
Troubleshooting: [Scheduled Tasks](/automation/cron-jobs#troubleshooting)
|
||||
|
||||
## Quick start (beginner)
|
||||
|
||||
1. Leave heartbeats enabled (default is `30m`, or `1h` for Anthropic OAuth/token auth, including Claude CLI reuse) or set your own cadence.
|
||||
2. Create a tiny `HEARTBEAT.md` checklist or `tasks:` block in the agent workspace (optional but recommended).
|
||||
3. Decide where heartbeat messages should go (`target: "none"` is the default; set `target: "last"` to route to the last contact).
|
||||
4. Optional: enable heartbeat reasoning delivery for transparency.
|
||||
5. Optional: use lightweight bootstrap context if heartbeat runs only need `HEARTBEAT.md`.
|
||||
6. Optional: enable isolated sessions to avoid sending full conversation history each heartbeat.
|
||||
7. Optional: restrict heartbeats to active hours (local time).
|
||||
<Steps>
|
||||
<Step title="Pick a cadence">
|
||||
Leave heartbeats enabled (default is `30m`, or `1h` for Anthropic OAuth/token auth, including Claude CLI reuse) or set your own cadence.
|
||||
</Step>
|
||||
<Step title="Add HEARTBEAT.md (optional)">
|
||||
Create a tiny `HEARTBEAT.md` checklist or `tasks:` block in the agent workspace.
|
||||
</Step>
|
||||
<Step title="Decide where heartbeat messages should go">
|
||||
`target: "none"` is the default; set `target: "last"` to route to the last contact.
|
||||
</Step>
|
||||
<Step title="Optional tuning">
|
||||
- Enable heartbeat reasoning delivery for transparency.
|
||||
- Use lightweight bootstrap context if heartbeat runs only need `HEARTBEAT.md`.
|
||||
- Enable isolated sessions to avoid sending full conversation history each heartbeat.
|
||||
- Restrict heartbeats to active hours (local time).
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
Example config:
|
||||
|
||||
@@ -49,44 +60,30 @@ Example config:
|
||||
## Defaults
|
||||
|
||||
- Interval: `30m` (or `1h` when Anthropic OAuth/token auth is the detected auth mode, including Claude CLI reuse). Set `agents.defaults.heartbeat.every` or per-agent `agents.list[].heartbeat.every`; use `0m` to disable.
|
||||
- Prompt body (configurable via `agents.defaults.heartbeat.prompt`):
|
||||
`Read HEARTBEAT.md if it exists (workspace context). Follow it strictly. Do not infer or repeat old tasks from prior chats. If nothing needs attention, reply HEARTBEAT_OK.`
|
||||
- The heartbeat prompt is sent **verbatim** as the user message. The system
|
||||
prompt includes a “Heartbeat” section only when heartbeats are enabled for the
|
||||
default agent, and the run is flagged internally.
|
||||
- When heartbeats are disabled with `0m`, normal runs also omit `HEARTBEAT.md`
|
||||
from bootstrap context so the model does not see heartbeat-only instructions.
|
||||
- Active hours (`heartbeat.activeHours`) are checked in the configured timezone.
|
||||
Outside the window, heartbeats are skipped until the next tick inside the window.
|
||||
- Prompt body (configurable via `agents.defaults.heartbeat.prompt`): `Read HEARTBEAT.md if it exists (workspace context). Follow it strictly. Do not infer or repeat old tasks from prior chats. If nothing needs attention, reply HEARTBEAT_OK.`
|
||||
- The heartbeat prompt is sent **verbatim** as the user message. The system prompt includes a "Heartbeat" section only when heartbeats are enabled for the default agent, and the run is flagged internally.
|
||||
- When heartbeats are disabled with `0m`, normal runs also omit `HEARTBEAT.md` from bootstrap context so the model does not see heartbeat-only instructions.
|
||||
- Active hours (`heartbeat.activeHours`) are checked in the configured timezone. Outside the window, heartbeats are skipped until the next tick inside the window.
|
||||
|
||||
## What the heartbeat prompt is for
|
||||
|
||||
The default prompt is intentionally broad:
|
||||
|
||||
- **Background tasks**: “Consider outstanding tasks” nudges the agent to review
|
||||
follow-ups (inbox, calendar, reminders, queued work) and surface anything urgent.
|
||||
- **Human check-in**: “Checkup sometimes on your human during day time” nudges an
|
||||
occasional lightweight “anything you need?” message, but avoids night-time spam
|
||||
by using your configured local timezone (see [/concepts/timezone](/concepts/timezone)).
|
||||
- **Background tasks**: "Consider outstanding tasks" nudges the agent to review follow-ups (inbox, calendar, reminders, queued work) and surface anything urgent.
|
||||
- **Human check-in**: "Checkup sometimes on your human during day time" nudges an occasional lightweight "anything you need?" message, but avoids night-time spam by using your configured local timezone (see [Timezone](/concepts/timezone)).
|
||||
|
||||
Heartbeat can react to completed [background tasks](/automation/tasks), but a heartbeat run itself does not create a task record.
|
||||
|
||||
If you want a heartbeat to do something very specific (e.g. “check Gmail PubSub
|
||||
stats” or “verify gateway health”), set `agents.defaults.heartbeat.prompt` (or
|
||||
`agents.list[].heartbeat.prompt`) to a custom body (sent verbatim).
|
||||
If you want a heartbeat to do something very specific (e.g. "check Gmail PubSub stats" or "verify gateway health"), set `agents.defaults.heartbeat.prompt` (or `agents.list[].heartbeat.prompt`) to a custom body (sent verbatim).
|
||||
|
||||
## Response contract
|
||||
|
||||
- If nothing needs attention, reply with **`HEARTBEAT_OK`**.
|
||||
- During heartbeat runs, OpenClaw treats `HEARTBEAT_OK` as an ack when it appears
|
||||
at the **start or end** of the reply. The token is stripped and the reply is
|
||||
dropped if the remaining content is **≤ `ackMaxChars`** (default: 300).
|
||||
- If `HEARTBEAT_OK` appears in the **middle** of a reply, it is not treated
|
||||
specially.
|
||||
- During heartbeat runs, OpenClaw treats `HEARTBEAT_OK` as an ack when it appears at the **start or end** of the reply. The token is stripped and the reply is dropped if the remaining content is **≤ `ackMaxChars`** (default: 300).
|
||||
- If `HEARTBEAT_OK` appears in the **middle** of a reply, it is not treated specially.
|
||||
- For alerts, **do not** include `HEARTBEAT_OK`; return only the alert text.
|
||||
|
||||
Outside heartbeats, stray `HEARTBEAT_OK` at the start/end of a message is stripped
|
||||
and logged; a message that is only `HEARTBEAT_OK` is dropped.
|
||||
Outside heartbeats, stray `HEARTBEAT_OK` at the start/end of a message is stripped and logged; a message that is only `HEARTBEAT_OK` is dropped.
|
||||
|
||||
## Config
|
||||
|
||||
@@ -121,9 +118,7 @@ and logged; a message that is only `HEARTBEAT_OK` is dropped.
|
||||
|
||||
### Per-agent heartbeats
|
||||
|
||||
If any `agents.list[]` entry includes a `heartbeat` block, **only those agents**
|
||||
run heartbeats. The per-agent block merges on top of `agents.defaults.heartbeat`
|
||||
(so you can set shared defaults once and override per agent).
|
||||
If any `agents.list[]` entry includes a `heartbeat` block, **only those agents** run heartbeats. The per-agent block merges on top of `agents.defaults.heartbeat` (so you can set shared defaults once and override per agent).
|
||||
|
||||
Example: two agents, only the second agent runs heartbeats.
|
||||
|
||||
@@ -184,10 +179,11 @@ If you want heartbeats to run all day, use one of these patterns:
|
||||
- Omit `activeHours` entirely (no time-window restriction; this is the default behavior).
|
||||
- Set a full-day window: `activeHours: { start: "00:00", end: "24:00" }`.
|
||||
|
||||
Do not set the same `start` and `end` time (for example `08:00` to `08:00`).
|
||||
That is treated as a zero-width window, so heartbeats are always skipped.
|
||||
<Warning>
|
||||
Do not set the same `start` and `end` time (for example `08:00` to `08:00`). That is treated as a zero-width window, so heartbeats are always skipped.
|
||||
</Warning>
|
||||
|
||||
### Multi account example
|
||||
### Multi-account example
|
||||
|
||||
Use `accountId` to target a specific account on multi-account channels like Telegram:
|
||||
|
||||
@@ -218,63 +214,87 @@ Use `accountId` to target a specific account on multi-account channels like Tele
|
||||
|
||||
### Field notes
|
||||
|
||||
- `every`: heartbeat interval (duration string; default unit = minutes).
|
||||
- `model`: optional model override for heartbeat runs (`provider/model`).
|
||||
- `includeReasoning`: when enabled, also deliver the separate `Reasoning:` message when available (same shape as `/reasoning on`).
|
||||
- `lightContext`: when true, heartbeat runs use lightweight bootstrap context and keep only `HEARTBEAT.md` from workspace bootstrap files.
|
||||
- `isolatedSession`: when true, each heartbeat runs in a fresh session with no prior conversation history. Uses the same isolation pattern as cron `sessionTarget: "isolated"`. Dramatically reduces per-heartbeat token cost. Combine with `lightContext: true` for maximum savings. Delivery routing still uses the main session context.
|
||||
- `session`: optional session key for heartbeat runs.
|
||||
- `main` (default): agent main session.
|
||||
- Explicit session key (copy from `openclaw sessions --json` or the [sessions CLI](/cli/sessions)).
|
||||
- Session key formats: see [Sessions](/concepts/session) and [Groups](/channels/groups).
|
||||
- `target`:
|
||||
- `last`: deliver to the last used external channel.
|
||||
- explicit channel: any configured channel or plugin id, for example `discord`, `matrix`, `telegram`, or `whatsapp`.
|
||||
- `none` (default): run the heartbeat but **do not deliver** externally.
|
||||
- `directPolicy`: controls direct/DM delivery behavior:
|
||||
- `allow` (default): allow direct/DM heartbeat delivery.
|
||||
- `block`: suppress direct/DM delivery (`reason=dm-blocked`).
|
||||
- `to`: optional recipient override (channel-specific id, e.g. E.164 for WhatsApp or a Telegram chat id). For Telegram topics/threads, use `<chatId>:topic:<messageThreadId>`.
|
||||
- `accountId`: optional account id for multi-account channels. When `target: "last"`, the account id applies to the resolved last channel if it supports accounts; otherwise it is ignored. If the account id does not match a configured account for the resolved channel, delivery is skipped.
|
||||
- `prompt`: overrides the default prompt body (not merged).
|
||||
- `ackMaxChars`: max chars allowed after `HEARTBEAT_OK` before delivery.
|
||||
- `suppressToolErrorWarnings`: when true, suppresses tool error warning payloads during heartbeat runs.
|
||||
- `activeHours`: restricts heartbeat runs to a time window. Object with `start` (HH:MM, inclusive; use `00:00` for start-of-day), `end` (HH:MM exclusive; `24:00` allowed for end-of-day), and optional `timezone`.
|
||||
- Omitted or `"user"`: uses your `agents.defaults.userTimezone` if set, otherwise falls back to the host system timezone.
|
||||
- `"local"`: always uses the host system timezone.
|
||||
- Any IANA identifier (e.g. `America/New_York`): used directly; if invalid, falls back to the `"user"` behavior above.
|
||||
- `start` and `end` must not be equal for an active window; equal values are treated as zero-width (always outside the window).
|
||||
- Outside the active window, heartbeats are skipped until the next tick inside the window.
|
||||
<ParamField path="every" type="string">
|
||||
Heartbeat interval (duration string; default unit = minutes).
|
||||
</ParamField>
|
||||
<ParamField path="model" type="string">
|
||||
Optional model override for heartbeat runs (`provider/model`).
|
||||
</ParamField>
|
||||
<ParamField path="includeReasoning" type="boolean" default="false">
|
||||
When enabled, also deliver the separate `Reasoning:` message when available (same shape as `/reasoning on`).
|
||||
</ParamField>
|
||||
<ParamField path="lightContext" type="boolean" default="false">
|
||||
When true, heartbeat runs use lightweight bootstrap context and keep only `HEARTBEAT.md` from workspace bootstrap files.
|
||||
</ParamField>
|
||||
<ParamField path="isolatedSession" type="boolean" default="false">
|
||||
When true, each heartbeat runs in a fresh session with no prior conversation history. Uses the same isolation pattern as cron `sessionTarget: "isolated"`. Dramatically reduces per-heartbeat token cost. Combine with `lightContext: true` for maximum savings. Delivery routing still uses the main session context.
|
||||
</ParamField>
|
||||
<ParamField path="session" type="string">
|
||||
Optional session key for heartbeat runs.
|
||||
|
||||
- `main` (default): agent main session.
|
||||
- Explicit session key (copy from `openclaw sessions --json` or the [sessions CLI](/cli/sessions)).
|
||||
- Session key formats: see [Sessions](/concepts/session) and [Groups](/channels/groups).
|
||||
</ParamField>
|
||||
<ParamField path="target" type="string">
|
||||
- `last`: deliver to the last used external channel.
|
||||
- explicit channel: any configured channel or plugin id, for example `discord`, `matrix`, `telegram`, or `whatsapp`.
|
||||
- `none` (default): run the heartbeat but **do not deliver** externally.
|
||||
</ParamField>
|
||||
<ParamField path="directPolicy" type='"allow" | "block"' default="allow">
|
||||
Controls direct/DM delivery behavior. `allow`: allow direct/DM heartbeat delivery. `block`: suppress direct/DM delivery (`reason=dm-blocked`).
|
||||
</ParamField>
|
||||
<ParamField path="to" type="string">
|
||||
Optional recipient override (channel-specific id, e.g. E.164 for WhatsApp or a Telegram chat id). For Telegram topics/threads, use `<chatId>:topic:<messageThreadId>`.
|
||||
</ParamField>
|
||||
<ParamField path="accountId" type="string">
|
||||
Optional account id for multi-account channels. When `target: "last"`, the account id applies to the resolved last channel if it supports accounts; otherwise it is ignored. If the account id does not match a configured account for the resolved channel, delivery is skipped.
|
||||
</ParamField>
|
||||
<ParamField path="prompt" type="string">
|
||||
Overrides the default prompt body (not merged).
|
||||
</ParamField>
|
||||
<ParamField path="ackMaxChars" type="number" default="300">
|
||||
Max chars allowed after `HEARTBEAT_OK` before delivery.
|
||||
</ParamField>
|
||||
<ParamField path="suppressToolErrorWarnings" type="boolean">
|
||||
When true, suppresses tool error warning payloads during heartbeat runs.
|
||||
</ParamField>
|
||||
<ParamField path="activeHours" type="object">
|
||||
Restricts heartbeat runs to a time window. Object with `start` (HH:MM, inclusive; use `00:00` for start-of-day), `end` (HH:MM exclusive; `24:00` allowed for end-of-day), and optional `timezone`.
|
||||
|
||||
- Omitted or `"user"`: uses your `agents.defaults.userTimezone` if set, otherwise falls back to the host system timezone.
|
||||
- `"local"`: always uses the host system timezone.
|
||||
- Any IANA identifier (e.g. `America/New_York`): used directly; if invalid, falls back to the `"user"` behavior above.
|
||||
- `start` and `end` must not be equal for an active window; equal values are treated as zero-width (always outside the window).
|
||||
- Outside the active window, heartbeats are skipped until the next tick inside the window.
|
||||
</ParamField>
|
||||
|
||||
## Delivery behavior
|
||||
|
||||
- Heartbeats run in the agent’s main session by default (`agent:<id>:<mainKey>`),
|
||||
or `global` when `session.scope = "global"`. Set `session` to override to a
|
||||
specific channel session (Discord/WhatsApp/etc.).
|
||||
- `session` only affects the run context; delivery is controlled by `target` and `to`.
|
||||
- To deliver to a specific channel/recipient, set `target` + `to`. With
|
||||
`target: "last"`, delivery uses the last external channel for that session.
|
||||
- Heartbeat deliveries allow direct/DM targets by default. Set `directPolicy: "block"` to suppress direct-target sends while still running the heartbeat turn.
|
||||
- If the main queue is busy, the heartbeat is skipped and retried later.
|
||||
- If `target` resolves to no external destination, the run still happens but no
|
||||
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. Heartbeat metadata
|
||||
may update the session row, but idle expiry uses `lastInteractionAt` from the
|
||||
last real user/channel message, and daily expiry uses `sessionStartedAt`.
|
||||
- Control UI and WebChat history hide heartbeat prompts and OK-only
|
||||
acknowledgments. The underlying session transcript can still contain those
|
||||
turns for audit/replay.
|
||||
- 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.
|
||||
<AccordionGroup>
|
||||
<Accordion title="Session and target routing">
|
||||
- Heartbeats run in the agent's main session by default (`agent:<id>:<mainKey>`), or `global` when `session.scope = "global"`. Set `session` to override to a specific channel session (Discord/WhatsApp/etc.).
|
||||
- `session` only affects the run context; delivery is controlled by `target` and `to`.
|
||||
- To deliver to a specific channel/recipient, set `target` + `to`. With `target: "last"`, delivery uses the last external channel for that session.
|
||||
- Heartbeat deliveries allow direct/DM targets by default. Set `directPolicy: "block"` to suppress direct-target sends while still running the heartbeat turn.
|
||||
- If the main queue is busy, the heartbeat is skipped and retried later.
|
||||
- If `target` resolves to no external destination, the run still happens but no outbound message is sent.
|
||||
</Accordion>
|
||||
<Accordion title="Visibility and skip behavior">
|
||||
- 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"`.
|
||||
</Accordion>
|
||||
<Accordion title="Session lifecycle and audit">
|
||||
- Heartbeat-only replies do **not** keep the session alive. Heartbeat metadata may update the session row, but idle expiry uses `lastInteractionAt` from the last real user/channel message, and daily expiry uses `sessionStartedAt`.
|
||||
- Control UI and WebChat history hide heartbeat prompts and OK-only acknowledgments. The underlying session transcript can still contain those turns for audit/replay.
|
||||
- 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.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Visibility controls
|
||||
|
||||
By default, `HEARTBEAT_OK` acknowledgments are suppressed while alert content is
|
||||
delivered. You can adjust this per channel or per account:
|
||||
By default, `HEARTBEAT_OK` acknowledgments are suppressed while alert content is delivered. You can adjust this per channel or per account:
|
||||
|
||||
```yaml
|
||||
channels:
|
||||
@@ -335,19 +355,11 @@ channels:
|
||||
|
||||
## HEARTBEAT.md (optional)
|
||||
|
||||
If a `HEARTBEAT.md` file exists in the workspace, the default prompt tells the
|
||||
agent to read it. Think of it as your “heartbeat checklist”: small, stable, and
|
||||
safe to include every 30 minutes.
|
||||
If a `HEARTBEAT.md` file exists in the workspace, the default prompt tells the agent to read it. Think of it as your "heartbeat checklist": small, stable, and safe to include every 30 minutes.
|
||||
|
||||
On normal runs, `HEARTBEAT.md` is only injected when heartbeat guidance is
|
||||
enabled for the default agent. Disabling the heartbeat cadence with `0m` or
|
||||
setting `includeSystemPromptSection: false` omits it from normal bootstrap
|
||||
context.
|
||||
On normal runs, `HEARTBEAT.md` is only injected when heartbeat guidance is enabled for the default agent. Disabling the heartbeat cadence with `0m` or setting `includeSystemPromptSection: false` omits it from normal bootstrap context.
|
||||
|
||||
If `HEARTBEAT.md` exists but is effectively empty (only blank lines and markdown
|
||||
headers like `# Heading`), OpenClaw skips the heartbeat run to save API calls.
|
||||
That skip is reported as `reason=empty-heartbeat-file`.
|
||||
If the file is missing, the heartbeat still runs and the model decides what to do.
|
||||
If `HEARTBEAT.md` exists but is effectively empty (only blank lines and markdown headers like `# Heading`), OpenClaw skips the heartbeat run to save API calls. That skip is reported as `reason=empty-heartbeat-file`. If the file is missing, the heartbeat still runs and the model decides what to do.
|
||||
|
||||
Keep it tiny (short checklist or reminders) to avoid prompt bloat.
|
||||
|
||||
@@ -357,14 +369,13 @@ Example `HEARTBEAT.md`:
|
||||
# Heartbeat checklist
|
||||
|
||||
- Quick scan: anything urgent in inboxes?
|
||||
- If it’s daytime, do a lightweight check-in if nothing else is pending.
|
||||
- If it's daytime, do a lightweight check-in if nothing else is pending.
|
||||
- If a task is blocked, write down _what is missing_ and ask Peter next time.
|
||||
```
|
||||
|
||||
### `tasks:` blocks
|
||||
|
||||
`HEARTBEAT.md` also supports a small structured `tasks:` block for interval-based
|
||||
checks inside heartbeat itself.
|
||||
`HEARTBEAT.md` also supports a small structured `tasks:` block for interval-based checks inside heartbeat itself.
|
||||
|
||||
Example:
|
||||
|
||||
@@ -384,14 +395,16 @@ tasks:
|
||||
- If nothing needs attention after all due tasks, reply HEARTBEAT_OK.
|
||||
```
|
||||
|
||||
Behavior:
|
||||
|
||||
- OpenClaw parses the `tasks:` block and checks each task against its own `interval`.
|
||||
- Only **due** tasks are included in the heartbeat prompt for that tick.
|
||||
- If no tasks are due, the heartbeat is skipped entirely (`reason=no-tasks-due`) to avoid a wasted model call.
|
||||
- Non-task content in `HEARTBEAT.md` is preserved and appended as additional context after the due-task list.
|
||||
- Task last-run timestamps are stored in session state (`heartbeatTaskState`), so intervals survive normal restarts.
|
||||
- Task timestamps are only advanced after a heartbeat run completes its normal reply path. Skipped `empty-heartbeat-file` / `no-tasks-due` runs do not mark tasks as completed.
|
||||
<AccordionGroup>
|
||||
<Accordion title="Behavior">
|
||||
- OpenClaw parses the `tasks:` block and checks each task against its own `interval`.
|
||||
- Only **due** tasks are included in the heartbeat prompt for that tick.
|
||||
- If no tasks are due, the heartbeat is skipped entirely (`reason=no-tasks-due`) to avoid a wasted model call.
|
||||
- Non-task content in `HEARTBEAT.md` is preserved and appended as additional context after the due-task list.
|
||||
- Task last-run timestamps are stored in session state (`heartbeatTaskState`), so intervals survive normal restarts.
|
||||
- Task timestamps are only advanced after a heartbeat run completes its normal reply path. Skipped `empty-heartbeat-file` / `no-tasks-due` runs do not mark tasks as completed.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
Task mode is useful when you want one heartbeat file to hold several periodic checks without paying for all of them every tick.
|
||||
|
||||
@@ -399,18 +412,16 @@ Task mode is useful when you want one heartbeat file to hold several periodic ch
|
||||
|
||||
Yes — if you ask it to.
|
||||
|
||||
`HEARTBEAT.md` is just a normal file in the agent workspace, so you can tell the
|
||||
agent (in a normal chat) something like:
|
||||
`HEARTBEAT.md` is just a normal file in the agent workspace, so you can tell the agent (in a normal chat) something like:
|
||||
|
||||
- “Update `HEARTBEAT.md` to add a daily calendar check.”
|
||||
- “Rewrite `HEARTBEAT.md` so it’s shorter and focused on inbox follow-ups.”
|
||||
- "Update `HEARTBEAT.md` to add a daily calendar check."
|
||||
- "Rewrite `HEARTBEAT.md` so it's shorter and focused on inbox follow-ups."
|
||||
|
||||
If you want this to happen proactively, you can also include an explicit line in
|
||||
your heartbeat prompt like: “If the checklist becomes stale, update HEARTBEAT.md
|
||||
with a better one.”
|
||||
If you want this to happen proactively, you can also include an explicit line in your heartbeat prompt like: "If the checklist becomes stale, update HEARTBEAT.md with a better one."
|
||||
|
||||
Safety note: don’t put secrets (API keys, phone numbers, private tokens) into
|
||||
`HEARTBEAT.md` — it becomes part of the prompt context.
|
||||
<Warning>
|
||||
Don't put secrets (API keys, phone numbers, private tokens) into `HEARTBEAT.md` — it becomes part of the prompt context.
|
||||
</Warning>
|
||||
|
||||
## Manual wake (on-demand)
|
||||
|
||||
@@ -420,24 +431,19 @@ You can enqueue a system event and trigger an immediate heartbeat with:
|
||||
openclaw system event --text "Check for urgent follow-ups" --mode now
|
||||
```
|
||||
|
||||
If multiple agents have `heartbeat` configured, a manual wake runs each of those
|
||||
agent heartbeats immediately.
|
||||
If multiple agents have `heartbeat` configured, a manual wake runs each of those agent heartbeats immediately.
|
||||
|
||||
Use `--mode next-heartbeat` to wait for the next scheduled tick.
|
||||
|
||||
## Reasoning delivery (optional)
|
||||
|
||||
By default, heartbeats deliver only the final “answer” payload.
|
||||
By default, heartbeats deliver only the final "answer" payload.
|
||||
|
||||
If you want transparency, enable:
|
||||
|
||||
- `agents.defaults.heartbeat.includeReasoning: true`
|
||||
|
||||
When enabled, heartbeats will also deliver a separate message prefixed
|
||||
`Reasoning:` (same shape as `/reasoning on`). This can be useful when the agent
|
||||
is managing multiple sessions/codexes and you want to see why it decided to ping
|
||||
you — but it can also leak more internal detail than you want. Prefer keeping it
|
||||
off in group chats.
|
||||
When enabled, heartbeats will also deliver a separate message prefixed `Reasoning:` (same shape as `/reasoning on`). This can be useful when the agent is managing multiple sessions/codexes and you want to see why it decided to ping you — but it can also leak more internal detail than you want. Prefer keeping it off in group chats.
|
||||
|
||||
## Cost awareness
|
||||
|
||||
|
||||
@@ -19,26 +19,26 @@ Best current local stack. Load a large model in LM Studio (for example, a full-s
|
||||
{
|
||||
agents: {
|
||||
defaults: {
|
||||
model: { primary: “lmstudio/my-local-model” },
|
||||
model: { primary: "lmstudio/my-local-model" },
|
||||
models: {
|
||||
“anthropic/claude-opus-4-6”: { alias: “Opus” },
|
||||
“lmstudio/my-local-model”: { alias: “Local” },
|
||||
"anthropic/claude-opus-4-6": { alias: "Opus" },
|
||||
"lmstudio/my-local-model": { alias: "Local" },
|
||||
},
|
||||
},
|
||||
},
|
||||
models: {
|
||||
mode: “merge”,
|
||||
mode: "merge",
|
||||
providers: {
|
||||
lmstudio: {
|
||||
baseUrl: “http://127.0.0.1:1234/v1”,
|
||||
apiKey: “lmstudio”,
|
||||
api: “openai-responses”,
|
||||
baseUrl: "http://127.0.0.1:1234/v1",
|
||||
apiKey: "lmstudio",
|
||||
api: "openai-responses",
|
||||
models: [
|
||||
{
|
||||
id: “my-local-model”,
|
||||
name: “Local Model”,
|
||||
id: "my-local-model",
|
||||
name: "Local Model",
|
||||
reasoning: false,
|
||||
input: [“text”],
|
||||
input: ["text"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 196608,
|
||||
maxTokens: 8192,
|
||||
@@ -124,6 +124,7 @@ vLLM, LiteLLM, OAI-proxy, or custom gateways work if they expose an OpenAI-style
|
||||
baseUrl: "http://127.0.0.1:8000/v1",
|
||||
apiKey: "sk-local",
|
||||
api: "openai-responses",
|
||||
timeoutSeconds: 300,
|
||||
models: [
|
||||
{
|
||||
id: "my-local-model",
|
||||
@@ -142,6 +143,10 @@ vLLM, LiteLLM, OAI-proxy, or custom gateways work if they expose an OpenAI-style
|
||||
```
|
||||
|
||||
Keep `models.mode: "merge"` so hosted models stay available as fallbacks.
|
||||
Use `models.providers.<id>.timeoutSeconds` for slow local or remote model
|
||||
servers before raising `agents.defaults.timeoutSeconds`. The provider timeout
|
||||
applies only to model HTTP requests, including connect, headers, body streaming,
|
||||
and the total guarded-fetch abort.
|
||||
|
||||
Behavior note for local/proxied `/v1` backends:
|
||||
|
||||
|
||||
@@ -52,10 +52,12 @@ You can tune console verbosity independently via:
|
||||
- `logging.consoleLevel` (default `info`)
|
||||
- `logging.consoleStyle` (`pretty` | `compact` | `json`)
|
||||
|
||||
## Tool summary redaction
|
||||
## Redaction
|
||||
|
||||
Verbose tool summaries (e.g. `🛠️ Exec: ...`) can mask sensitive tokens before they hit the
|
||||
console stream. This is **tools-only** and does not alter file logs.
|
||||
OpenClaw can mask sensitive tokens before log or transcript output leaves the
|
||||
process. The same redaction policy is applied at console, file-log, OTLP
|
||||
log-record, and session transcript text sinks, so matching secret values are
|
||||
masked before JSONL lines or messages are written to disk.
|
||||
|
||||
- `logging.redactSensitive`: `off` | `tools` (default: `tools`)
|
||||
- `logging.redactPatterns`: array of regex strings (overrides defaults)
|
||||
|
||||
@@ -110,9 +110,9 @@ Best for:
|
||||
- You want lower per-turn sync overhead.
|
||||
- You do not want host-local edits to silently overwrite remote sandbox state.
|
||||
|
||||
Important: if you edit files on the host outside OpenClaw after the initial seed,
|
||||
the remote sandbox does **not** see those changes. Use
|
||||
`openclaw sandbox recreate` to re-seed.
|
||||
<Warning>
|
||||
If you edit files on the host outside OpenClaw after the initial seed, the remote sandbox does **not** see those changes. Use `openclaw sandbox recreate` to re-seed.
|
||||
</Warning>
|
||||
|
||||
### Choosing a mode
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user