mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-21 22:43:15 +08:00
Compare commits
949 Commits
v2026.4.2
...
cache/cont
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1358cba962 | ||
|
|
6aa591ba56 | ||
|
|
d01cb5ecc6 | ||
|
|
5bea93fd63 | ||
|
|
fe72474153 | ||
|
|
411282c36d | ||
|
|
43272d27f8 | ||
|
|
bb4e54ccf7 | ||
|
|
df83374a54 | ||
|
|
236a9003b6 | ||
|
|
3a3fdf1920 | ||
|
|
ff62705206 | ||
|
|
306fe841f5 | ||
|
|
de2eaccfce | ||
|
|
0f18e44538 | ||
|
|
d02fc365b4 | ||
|
|
ab318de8b7 | ||
|
|
e4b5027c5e | ||
|
|
c19321ed9e | ||
|
|
ff607adc69 | ||
|
|
4540effd6c | ||
|
|
5fc9918a20 | ||
|
|
94fee8486f | ||
|
|
e19dce0aed | ||
|
|
1322aa2ba2 | ||
|
|
2af05ac558 | ||
|
|
3d2734185b | ||
|
|
9004ef65df | ||
|
|
3fd29e549d | ||
|
|
ccf16a25d2 | ||
|
|
cc0987f7b1 | ||
|
|
42ffdf882f | ||
|
|
393d8c7606 | ||
|
|
5f1f43af4d | ||
|
|
4d60d61dec | ||
|
|
da68fa4079 | ||
|
|
6ddc86a3d1 | ||
|
|
30fd4c6cdb | ||
|
|
01534d9bd5 | ||
|
|
381a865822 | ||
|
|
2b54ce30ae | ||
|
|
bb649de1ad | ||
|
|
4518b9ea7a | ||
|
|
32f9a7c7bc | ||
|
|
10062e8111 | ||
|
|
4fb0837220 | ||
|
|
e8e7d1fab3 | ||
|
|
037da3ce34 | ||
|
|
ee45a59b4e | ||
|
|
932379b19f | ||
|
|
be1d31fa8a | ||
|
|
9f132fc1b0 | ||
|
|
dc21e3bb1e | ||
|
|
5b29483ab1 | ||
|
|
001e0c1f65 | ||
|
|
5a94909654 | ||
|
|
8ae8a5c174 | ||
|
|
759598f737 | ||
|
|
267b6f595c | ||
|
|
361efd28c9 | ||
|
|
66dfe18c36 | ||
|
|
9b667fc534 | ||
|
|
94e170763e | ||
|
|
8343a11a6b | ||
|
|
1c5a4d01c9 | ||
|
|
eb6698002c | ||
|
|
51eb877a15 | ||
|
|
db4d0c0abc | ||
|
|
8e023ffd06 | ||
|
|
2e9cad224d | ||
|
|
cfef9bf856 | ||
|
|
0204b8dd28 | ||
|
|
5d3edb1d40 | ||
|
|
5ec53fff0c | ||
|
|
dbb0164934 | ||
|
|
40ae49effa | ||
|
|
f336f0b83c | ||
|
|
21e53aea9e | ||
|
|
f3a6d13965 | ||
|
|
183601c347 | ||
|
|
fa6e6603fa | ||
|
|
5361b5cf04 | ||
|
|
8cb3316afb | ||
|
|
63603876bf | ||
|
|
6317fce9cb | ||
|
|
8158597f84 | ||
|
|
8d557c19d5 | ||
|
|
f59c52bc16 | ||
|
|
faff198777 | ||
|
|
fde573bab2 | ||
|
|
de6997a203 | ||
|
|
ee63fdb056 | ||
|
|
93e716e775 | ||
|
|
756597e6ad | ||
|
|
328b7bee75 | ||
|
|
78022740fc | ||
|
|
4c0f51df81 | ||
|
|
b57922552e | ||
|
|
58ee283658 | ||
|
|
299ed8cb39 | ||
|
|
2a13508379 | ||
|
|
067496b129 | ||
|
|
3e0ddaf5bc | ||
|
|
d44af743db | ||
|
|
f8a0f9ffd3 | ||
|
|
d007559c38 | ||
|
|
84db697cd6 | ||
|
|
2a5fbf0fd6 | ||
|
|
7db148706a | ||
|
|
f56a9f3b3b | ||
|
|
1ff586cda1 | ||
|
|
314512ae14 | ||
|
|
2247089381 | ||
|
|
92409aa4d6 | ||
|
|
52fb51db77 | ||
|
|
3f86972e46 | ||
|
|
5942726d25 | ||
|
|
ae976a90a5 | ||
|
|
4481c41368 | ||
|
|
aa983566c4 | ||
|
|
6f8f2a012b | ||
|
|
f7f467b042 | ||
|
|
a715b83e67 | ||
|
|
6c5064b437 | ||
|
|
30e43550bb | ||
|
|
4265a59892 | ||
|
|
d9af49a7af | ||
|
|
58d6c16d12 | ||
|
|
6068497409 | ||
|
|
c8c0aeda76 | ||
|
|
489a62e788 | ||
|
|
63443acc2b | ||
|
|
0805add3a4 | ||
|
|
a18167a2cb | ||
|
|
f5ec0e429f | ||
|
|
1fbf863f53 | ||
|
|
e286ba2bab | ||
|
|
ee5113b1ae | ||
|
|
6a465611d8 | ||
|
|
6286ef55da | ||
|
|
9224afca3d | ||
|
|
cc1881a838 | ||
|
|
0273062dfd | ||
|
|
b361667f98 | ||
|
|
24afd52fcd | ||
|
|
1d4fcb6a01 | ||
|
|
724dd5ca3d | ||
|
|
c7554d3072 | ||
|
|
0bbacca828 | ||
|
|
37de88181b | ||
|
|
2d2fe2bf47 | ||
|
|
5f17362667 | ||
|
|
4578351488 | ||
|
|
811efa2db0 | ||
|
|
35a9eeb857 | ||
|
|
4e27e22663 | ||
|
|
ffba320a2c | ||
|
|
0464435777 | ||
|
|
fa5ea4529a | ||
|
|
e57b6be85f | ||
|
|
516e9054de | ||
|
|
5e204df0bf | ||
|
|
88d3b73c6d | ||
|
|
4b71a94450 | ||
|
|
c9dfc35dfd | ||
|
|
9215ff0615 | ||
|
|
7c25af83e4 | ||
|
|
03aea06321 | ||
|
|
a301e2ef87 | ||
|
|
961d8eb095 | ||
|
|
5e9ae0bfd4 | ||
|
|
4948760c65 | ||
|
|
1c66a050c2 | ||
|
|
f007082e06 | ||
|
|
eecb36eff4 | ||
|
|
1420b3bad7 | ||
|
|
1c470c2736 | ||
|
|
d305a80acd | ||
|
|
bc23db501b | ||
|
|
6115a9498c | ||
|
|
8e8f8d0745 | ||
|
|
d8458a1481 | ||
|
|
fcec417d7d | ||
|
|
d2ca915a7f | ||
|
|
88ab29f492 | ||
|
|
534b0c663e | ||
|
|
f66c9b829e | ||
|
|
4f5f1fa724 | ||
|
|
b8af2c65e5 | ||
|
|
4ca1ae8046 | ||
|
|
c7875f193b | ||
|
|
e3f410efb5 | ||
|
|
3fb6e3e91f | ||
|
|
17c0026c04 | ||
|
|
9289f967df | ||
|
|
e76a16dfa5 | ||
|
|
e697fa5e75 | ||
|
|
2156bf0210 | ||
|
|
0ad2da060e | ||
|
|
cc62fd38f6 | ||
|
|
a8302e8eab | ||
|
|
323ad51eb8 | ||
|
|
8be2dea382 | ||
|
|
b27fd7cc49 | ||
|
|
0c95e3f073 | ||
|
|
e5d2181403 | ||
|
|
45a6f769bb | ||
|
|
6eca4e0136 | ||
|
|
24a4ed1013 | ||
|
|
eea069bdc3 | ||
|
|
e063f67ac0 | ||
|
|
26b7260bf4 | ||
|
|
e9cbdc7439 | ||
|
|
d5c6e7af0f | ||
|
|
1f660bf930 | ||
|
|
5c3dc40794 | ||
|
|
28b8e019f7 | ||
|
|
df18f4c517 | ||
|
|
5eb3341db1 | ||
|
|
5e365a8ec4 | ||
|
|
045010a2a5 | ||
|
|
72b8025107 | ||
|
|
4c5c361db7 | ||
|
|
956e746da1 | ||
|
|
7d691a3ce3 | ||
|
|
5c6dca78d9 | ||
|
|
53f8c2047a | ||
|
|
d20e3d5691 | ||
|
|
a89cb679a2 | ||
|
|
13bc70397a | ||
|
|
5c4551458f | ||
|
|
181bd6327f | ||
|
|
42ffe86fc7 | ||
|
|
03a43fe231 | ||
|
|
856592cf00 | ||
|
|
ab96520bba | ||
|
|
c52df32878 | ||
|
|
7fb58afb41 | ||
|
|
7be2d361de | ||
|
|
abc3f27ba9 | ||
|
|
0ba93afda9 | ||
|
|
b7b53b29e8 | ||
|
|
d9e59f7329 | ||
|
|
54479220f5 | ||
|
|
ea4265a820 | ||
|
|
8fc684cb55 | ||
|
|
5d20c73e05 | ||
|
|
e588a363f9 | ||
|
|
4bbd67c21e | ||
|
|
de49b26bb1 | ||
|
|
91a3554cd7 | ||
|
|
3fd27211b1 | ||
|
|
7eed2e2911 | ||
|
|
7439479047 | ||
|
|
3edfc494df | ||
|
|
613393621c | ||
|
|
5888e44745 | ||
|
|
6739c28718 | ||
|
|
1d1a8264ec | ||
|
|
50e1eb56d7 | ||
|
|
3ddf745f97 | ||
|
|
dc306013e1 | ||
|
|
0769590f03 | ||
|
|
f115aba582 | ||
|
|
638e831bca | ||
|
|
379c329f81 | ||
|
|
4edf6a2c7d | ||
|
|
4afa720a0c | ||
|
|
d0a43cf8c0 | ||
|
|
acfa09679c | ||
|
|
b929a4c27d | ||
|
|
9f049cb1d8 | ||
|
|
f77054eaee | ||
|
|
35256d6f1d | ||
|
|
17060ca124 | ||
|
|
53612fa128 | ||
|
|
fc61e8d280 | ||
|
|
e37c0da23a | ||
|
|
42ebc8c170 | ||
|
|
e6a9408c3b | ||
|
|
ddd250d130 | ||
|
|
04f59a7227 | ||
|
|
636a23b73e | ||
|
|
47b8be7116 | ||
|
|
7e3b48c254 | ||
|
|
1bee69f79b | ||
|
|
d0d5b34b44 | ||
|
|
2c5f244554 | ||
|
|
865fa2ba72 | ||
|
|
42e1d489fd | ||
|
|
022a24ec48 | ||
|
|
c4eaa30ee7 | ||
|
|
e88f4fe5f4 | ||
|
|
a7dd6036a0 | ||
|
|
68edc53090 | ||
|
|
f36ed7105f | ||
|
|
14c863dc4a | ||
|
|
fb9be1fcb6 | ||
|
|
1c16c6a94a | ||
|
|
d6e89f96d6 | ||
|
|
646e271c72 | ||
|
|
71de4adcce | ||
|
|
f93f76dcc4 | ||
|
|
22fd61e483 | ||
|
|
ebdade0efc | ||
|
|
230c61885d | ||
|
|
7ad72281f7 | ||
|
|
bee60a479b | ||
|
|
5fbef0f914 | ||
|
|
be9db66533 | ||
|
|
0c575f37fd | ||
|
|
db593440c4 | ||
|
|
136f177cb3 | ||
|
|
7fd9e40960 | ||
|
|
c742963fd9 | ||
|
|
97558f2325 | ||
|
|
54b269b2cb | ||
|
|
e0580e6863 | ||
|
|
2981cce130 | ||
|
|
ff68fd3060 | ||
|
|
39a16c600f | ||
|
|
f881eb066c | ||
|
|
514b37e185 | ||
|
|
feed4007fe | ||
|
|
dd42154e45 | ||
|
|
dd35b97398 | ||
|
|
7836c9a6c2 | ||
|
|
ae359c0c8b | ||
|
|
5e69d7e75b | ||
|
|
6ba490ec7b | ||
|
|
0b69119f1b | ||
|
|
f575bc2bfe | ||
|
|
b871707628 | ||
|
|
50d85dcd59 | ||
|
|
c5a45eb274 | ||
|
|
3c07b126ed | ||
|
|
f1911274aa | ||
|
|
d5c42a07ef | ||
|
|
54cd0859d3 | ||
|
|
911e3974f7 | ||
|
|
9e6de5ece4 | ||
|
|
f076e97a3c | ||
|
|
b2a9e0d7a7 | ||
|
|
2c0967150d | ||
|
|
eec6f59a77 | ||
|
|
745f1c9812 | ||
|
|
ddaef48421 | ||
|
|
51f6bc4940 | ||
|
|
5edefc4d5b | ||
|
|
ec01cd0ceb | ||
|
|
c4f40c3f7d | ||
|
|
824ff335c6 | ||
|
|
958cebcc87 | ||
|
|
0e07c8973e | ||
|
|
efbb9a1296 | ||
|
|
36ed66cce2 | ||
|
|
54e8790ad7 | ||
|
|
33d54e93b8 | ||
|
|
21002a60be | ||
|
|
21a87eaf2b | ||
|
|
06b2e8b79a | ||
|
|
91572df932 | ||
|
|
a5e725a3b8 | ||
|
|
b63a175d3d | ||
|
|
b473e056a0 | ||
|
|
408dac8d21 | ||
|
|
bc46c1dacc | ||
|
|
7f4dd21227 | ||
|
|
eb497a89cd | ||
|
|
b118efea80 | ||
|
|
d74d47443e | ||
|
|
2e041c8b66 | ||
|
|
1512fab782 | ||
|
|
e263b5d7b6 | ||
|
|
6686ef0b3a | ||
|
|
d21ae7173f | ||
|
|
2a19771c3c | ||
|
|
a1cad12139 | ||
|
|
c22f2a0cab | ||
|
|
570ed4285e | ||
|
|
226e2389f6 | ||
|
|
09f66b0073 | ||
|
|
c7a947dc0a | ||
|
|
d59d236a90 | ||
|
|
6ac5806a39 | ||
|
|
fb8048a188 | ||
|
|
2b900b576c | ||
|
|
9dba944c42 | ||
|
|
cf4d3c4daf | ||
|
|
4c969391fe | ||
|
|
832810a5bb | ||
|
|
8ffd1c0973 | ||
|
|
1f509e0e04 | ||
|
|
56f8de0cf9 | ||
|
|
6389498a7c | ||
|
|
632a10cddc | ||
|
|
a2836e6db6 | ||
|
|
da5c6ac2b6 | ||
|
|
0b4cdfc53e | ||
|
|
7ed789d67d | ||
|
|
fb4127082a | ||
|
|
9fbf501d5a | ||
|
|
2766a3409c | ||
|
|
664265fc66 | ||
|
|
2a1a7ea6f9 | ||
|
|
7e2b26f77b | ||
|
|
0b74755894 | ||
|
|
8541a5c3fa | ||
|
|
88dac32623 | ||
|
|
0324055d09 | ||
|
|
5bafa6edcf | ||
|
|
c563cdc901 | ||
|
|
6f5e71fdbc | ||
|
|
2fa3a09137 | ||
|
|
1877a2ea26 | ||
|
|
d75fa152b9 | ||
|
|
38f76a1f8f | ||
|
|
20250653ce | ||
|
|
192f880a0b | ||
|
|
998810a6a3 | ||
|
|
ce784b62f5 | ||
|
|
5dd6189a2a | ||
|
|
5b176c8cc5 | ||
|
|
4919a8871b | ||
|
|
a3227e58d2 | ||
|
|
db76dbc546 | ||
|
|
b53dcb9380 | ||
|
|
da120962b9 | ||
|
|
383eea86dc | ||
|
|
3c50139285 | ||
|
|
590655472b | ||
|
|
9c4ea016d9 | ||
|
|
e1143fb95f | ||
|
|
24da2c39f3 | ||
|
|
27a8ef1284 | ||
|
|
71b5a7c35b | ||
|
|
e9e7033ea1 | ||
|
|
0af1d0ddb2 | ||
|
|
eb2cd09b72 | ||
|
|
3cce39cae2 | ||
|
|
a65ff4de9f | ||
|
|
6299a5fbfe | ||
|
|
5ff72867bf | ||
|
|
dcc3467a2b | ||
|
|
8bd3067e69 | ||
|
|
57ee3d9673 | ||
|
|
33248980d9 | ||
|
|
deb70e7e25 | ||
|
|
ebb4e9f0a6 | ||
|
|
75b66403be | ||
|
|
335b472c37 | ||
|
|
a78dba4396 | ||
|
|
bf3d1f85b8 | ||
|
|
5ae346427f | ||
|
|
ccdd33545b | ||
|
|
652f273a0f | ||
|
|
48fe2fd8be | ||
|
|
9cf7b92e0d | ||
|
|
ac7e1f7c6c | ||
|
|
6be5d34f2f | ||
|
|
18891b1806 | ||
|
|
08560c1f48 | ||
|
|
192c02cd92 | ||
|
|
2d4428bcbb | ||
|
|
2afa169250 | ||
|
|
0126653783 | ||
|
|
36c8282795 | ||
|
|
58f1044ec0 | ||
|
|
23422ccb68 | ||
|
|
94340fdbae | ||
|
|
42786afc64 | ||
|
|
768ec2a712 | ||
|
|
0875c2e370 | ||
|
|
50069bcb59 | ||
|
|
4b79ae7ad8 | ||
|
|
14ff2c30d1 | ||
|
|
25e9ff01cf | ||
|
|
19493b681d | ||
|
|
8d5c11d31b | ||
|
|
54af005f59 | ||
|
|
9951f22766 | ||
|
|
9a6dda1b66 | ||
|
|
cc57bcfe2f | ||
|
|
9919e978ca | ||
|
|
49563843dc | ||
|
|
c593ed0055 | ||
|
|
4499d572fa | ||
|
|
24edb82ece | ||
|
|
d6ad92c1a0 | ||
|
|
33e6a6724d | ||
|
|
7c738ad036 | ||
|
|
3d799ba004 | ||
|
|
f49d8f665c | ||
|
|
8805c7b55b | ||
|
|
d114b4e033 | ||
|
|
b7f524abaa | ||
|
|
bf6bd7432a | ||
|
|
c1741abc3c | ||
|
|
b51214ec3e | ||
|
|
27ced5c1d3 | ||
|
|
f02f3b925d | ||
|
|
d1883470e7 | ||
|
|
bd4f745833 | ||
|
|
62b736a8c2 | ||
|
|
d9dbce4093 | ||
|
|
69d018ce4f | ||
|
|
4107a5a4f0 | ||
|
|
41ce3269f4 | ||
|
|
eb3481fca9 | ||
|
|
a6a200ebc2 | ||
|
|
b1747d8b1c | ||
|
|
846bfaa045 | ||
|
|
3aac90fc85 | ||
|
|
9a88a933cf | ||
|
|
35541377d1 | ||
|
|
9b8c892ff4 | ||
|
|
5e7ebd098e | ||
|
|
e7cb9dec43 | ||
|
|
6e3203a728 | ||
|
|
4615ddf89b | ||
|
|
6d6060d3ec | ||
|
|
4528f8779e | ||
|
|
a5b23f17fb | ||
|
|
557a07bd5b | ||
|
|
f41a67b118 | ||
|
|
2e520d112d | ||
|
|
625201bddc | ||
|
|
4b93000d11 | ||
|
|
6a28756e98 | ||
|
|
e67be773d6 | ||
|
|
3d8a039149 | ||
|
|
904d9db132 | ||
|
|
f2204cb35a | ||
|
|
d755709ddd | ||
|
|
b1026a0b28 | ||
|
|
c6b8109bd8 | ||
|
|
8b6c224554 | ||
|
|
c8318754b5 | ||
|
|
7c6eba4634 | ||
|
|
f1f6b98639 | ||
|
|
79aa212789 | ||
|
|
dae0400a8f | ||
|
|
745aa26420 | ||
|
|
ed297eb8b9 | ||
|
|
af835acd00 | ||
|
|
ade6b61358 | ||
|
|
f71ef47288 | ||
|
|
a592cd67cb | ||
|
|
1dfcdbdf91 | ||
|
|
e3fea41b59 | ||
|
|
c71df2f4b0 | ||
|
|
2e779a1b20 | ||
|
|
0f129c87ba | ||
|
|
a3541a1cce | ||
|
|
30c0dc3d47 | ||
|
|
4e3b2781fb | ||
|
|
93f136cbed | ||
|
|
2da3b45ce7 | ||
|
|
8e9607c064 | ||
|
|
f08a1c34dd | ||
|
|
b50b85a5db | ||
|
|
702a200844 | ||
|
|
78c390ea86 | ||
|
|
5ad2c61c9a | ||
|
|
cee5f960b5 | ||
|
|
93d514f816 | ||
|
|
0eb9416d9c | ||
|
|
30fc29c9b0 | ||
|
|
ca68d57dc2 | ||
|
|
9e6da1e70a | ||
|
|
6366010884 | ||
|
|
ad8870ae28 | ||
|
|
a6816cb59c | ||
|
|
25a187568f | ||
|
|
b98ee01814 | ||
|
|
f5276ed38b | ||
|
|
de952c036a | ||
|
|
bd8d29c2b1 | ||
|
|
6363094e93 | ||
|
|
1f0c4a624b | ||
|
|
11dbcdc46d | ||
|
|
b721f5e48a | ||
|
|
a4efe7c028 | ||
|
|
12fa700579 | ||
|
|
fc8ab82aab | ||
|
|
88b1c00b39 | ||
|
|
7a9ad3820e | ||
|
|
e9a1f7818c | ||
|
|
fbd361d338 | ||
|
|
3257136160 | ||
|
|
5ccf6d229b | ||
|
|
759c81ceb8 | ||
|
|
35cf7d0340 | ||
|
|
b6dd7ac232 | ||
|
|
f9f0a593e4 | ||
|
|
a028e16eaa | ||
|
|
35aa6c6126 | ||
|
|
756cf847e0 | ||
|
|
279ee5e842 | ||
|
|
e9f82ac752 | ||
|
|
76ff144037 | ||
|
|
66825c0969 | ||
|
|
24e10e6e45 | ||
|
|
38a4b2b14c | ||
|
|
5ce7aee33b | ||
|
|
e9acbdb111 | ||
|
|
8ffeadd8f9 | ||
|
|
cd38eba316 | ||
|
|
975d2ddce2 | ||
|
|
3b69b8e3c4 | ||
|
|
c013b9cdf3 | ||
|
|
316978700e | ||
|
|
316da43dd7 | ||
|
|
23d8a979b3 | ||
|
|
1556490ee7 | ||
|
|
cddb34ba6a | ||
|
|
0d7d573cd6 | ||
|
|
06ed0eaad5 | ||
|
|
b40d4b63f6 | ||
|
|
8f5f78bbe8 | ||
|
|
cedc8bdebb | ||
|
|
4e9629d60c | ||
|
|
d375cd727e | ||
|
|
549e0bb268 | ||
|
|
690c58baa2 | ||
|
|
0ecf84524d | ||
|
|
78fb352506 | ||
|
|
52008e2e60 | ||
|
|
a9a057d1eb | ||
|
|
ac20eed335 | ||
|
|
ed166ba338 | ||
|
|
799c6f40aa | ||
|
|
f328e7f4a6 | ||
|
|
6395336454 | ||
|
|
1a23627e32 | ||
|
|
c2e93c76bd | ||
|
|
883a35a38c | ||
|
|
cef0f36931 | ||
|
|
4a0905b94b | ||
|
|
4a81771290 | ||
|
|
e26a590f7a | ||
|
|
a61408737f | ||
|
|
d338299dc7 | ||
|
|
909895c471 | ||
|
|
d7e4fad872 | ||
|
|
52717ee399 | ||
|
|
3cd9aac6bb | ||
|
|
62b1fe0b85 | ||
|
|
8d0bed458e | ||
|
|
1125cd3b97 | ||
|
|
efeee6f921 | ||
|
|
4b2c7404e5 | ||
|
|
5341d483d1 | ||
|
|
0dad4072b4 | ||
|
|
96e8352bda | ||
|
|
0c0d84fbd9 | ||
|
|
ab57d24f79 | ||
|
|
b9ede82cc2 | ||
|
|
eb4b6f7024 | ||
|
|
c1d68f213d | ||
|
|
d2427c19e0 | ||
|
|
9b83e462cf | ||
|
|
9aab672a69 | ||
|
|
bc137951e9 | ||
|
|
cd08facd7a | ||
|
|
f7d24c1ed5 | ||
|
|
2ad69083d2 | ||
|
|
1ad9fe0b52 | ||
|
|
f6e99bd514 | ||
|
|
b48b528b02 | ||
|
|
8b5e80fcaa | ||
|
|
6f9b4b52f8 | ||
|
|
3b358414d3 | ||
|
|
4798e125f4 | ||
|
|
875c3813aa | ||
|
|
f904a49568 | ||
|
|
020093bf4b | ||
|
|
597416fdd9 | ||
|
|
173bb0aea0 | ||
|
|
fa9e1e3d8e | ||
|
|
e0e5df25e6 | ||
|
|
8962a8fb73 | ||
|
|
bab14fb8f1 | ||
|
|
bb25a8050c | ||
|
|
8fabfa5d1c | ||
|
|
ba7297ff21 | ||
|
|
3b727d2433 | ||
|
|
a898cd464a | ||
|
|
a764b8f8cd | ||
|
|
73073a91bb | ||
|
|
7bd6bb91c4 | ||
|
|
6510ecafb0 | ||
|
|
85bd5b3ce7 | ||
|
|
e43a362ad9 | ||
|
|
3a2667b5fc | ||
|
|
bbbfbd2db4 | ||
|
|
91618438bc | ||
|
|
6d05607cd9 | ||
|
|
a884ad3cf2 | ||
|
|
35d890b5ef | ||
|
|
88bc6d852f | ||
|
|
1a75fc9e05 | ||
|
|
39361d13be | ||
|
|
5e3a3c42ca | ||
|
|
b08d58c917 | ||
|
|
0891253012 | ||
|
|
a42f000b53 | ||
|
|
df115822b9 | ||
|
|
4f4aa46d00 | ||
|
|
da1980b923 | ||
|
|
a3b9ae8fab | ||
|
|
8735dd7d19 | ||
|
|
7ede711cae | ||
|
|
01a163c7f3 | ||
|
|
851de3554e | ||
|
|
349d3e8289 | ||
|
|
2fff6be4c3 | ||
|
|
2a54a7f7cd | ||
|
|
77c04ec29c | ||
|
|
56ead96f48 | ||
|
|
027a544d8f | ||
|
|
710c63edad | ||
|
|
319aa2f1fe | ||
|
|
899fe6ffa5 | ||
|
|
0ca2a91213 | ||
|
|
a05daaa832 | ||
|
|
0a61138e77 | ||
|
|
e4cc8cd975 | ||
|
|
ee39ec29d1 | ||
|
|
344717a2d5 | ||
|
|
65cddd79eb | ||
|
|
beb108cfaa | ||
|
|
49936f6066 | ||
|
|
a0dbba1626 | ||
|
|
568859e1fb | ||
|
|
2a04d5c16f | ||
|
|
a3cadfd51d | ||
|
|
7c41b9fca9 | ||
|
|
7fa0c76ffc | ||
|
|
f0a4423271 | ||
|
|
a3f34a8f77 | ||
|
|
c186644662 | ||
|
|
e4abd34466 | ||
|
|
4e60653959 | ||
|
|
69da71c0ce | ||
|
|
cd81e0a07b | ||
|
|
5184522f2f | ||
|
|
3a68414569 | ||
|
|
99397254a1 | ||
|
|
d2dae50a75 | ||
|
|
9245b9e2f4 | ||
|
|
f59d0eac68 | ||
|
|
4846ebce12 | ||
|
|
feca4aa49e | ||
|
|
fbb2537774 | ||
|
|
1118d032ca | ||
|
|
87abcfd6a6 | ||
|
|
e116e7d584 | ||
|
|
dc312a4c76 | ||
|
|
685ef52284 | ||
|
|
71a54d0c95 | ||
|
|
688eb8435b | ||
|
|
2734d06ade | ||
|
|
a8040ab9d9 | ||
|
|
b925f6d46c | ||
|
|
fbc4fa6ac3 | ||
|
|
d888ce242b | ||
|
|
0d938748a5 | ||
|
|
a0c6ea5aba | ||
|
|
a2077b28ef | ||
|
|
bd1e78ea34 | ||
|
|
82fca281b6 | ||
|
|
b410c5434c | ||
|
|
d9aa88dd6c | ||
|
|
9bd05d3841 | ||
|
|
57999f9965 | ||
|
|
19dbe00763 | ||
|
|
6845b8061c | ||
|
|
9ef5d85e40 | ||
|
|
c80c1cf56f | ||
|
|
d21d859ded | ||
|
|
11c6202ec0 | ||
|
|
9a53c3d772 | ||
|
|
6e3eb34a90 | ||
|
|
36a233ff98 | ||
|
|
51d6d7013f | ||
|
|
79da4a46b4 | ||
|
|
dc6e041cfe | ||
|
|
2ec9d3d58b | ||
|
|
69e0fbd95e | ||
|
|
13412c0c50 | ||
|
|
afa78a5b13 | ||
|
|
de1d0f4fae | ||
|
|
37ab4b7fdc | ||
|
|
d39e4dff6a | ||
|
|
f4393791eb | ||
|
|
1dd88c6288 | ||
|
|
1337be3063 | ||
|
|
db6d149f75 | ||
|
|
0a2a1ff778 | ||
|
|
5021b12ac1 | ||
|
|
045d590542 | ||
|
|
fac89d403b | ||
|
|
d3310f4837 | ||
|
|
e2e1197fa9 | ||
|
|
4e22e75697 | ||
|
|
225431665a | ||
|
|
05df7f802b | ||
|
|
566fc72106 | ||
|
|
53504b3662 | ||
|
|
2c7eea8f10 | ||
|
|
a6649201b7 | ||
|
|
d921784718 | ||
|
|
a57766bad0 | ||
|
|
50f4bffbb6 | ||
|
|
32ebaa3757 | ||
|
|
67d87abf7c | ||
|
|
bb3ea2137b | ||
|
|
e0c4458a2f | ||
|
|
52225db134 | ||
|
|
5400980305 | ||
|
|
1c26e806ff | ||
|
|
16ca1f4d74 | ||
|
|
b406b7d2e4 | ||
|
|
71f8c0344a | ||
|
|
1a5787137f | ||
|
|
b53ab34d04 | ||
|
|
a5eb8e08ad | ||
|
|
c0a8d07fce | ||
|
|
fb0d82ba9f | ||
|
|
2766c27b2a | ||
|
|
182bec5091 | ||
|
|
d55e580307 | ||
|
|
e18611188d | ||
|
|
d68840ef40 | ||
|
|
e414e51761 | ||
|
|
80c5764482 | ||
|
|
122e6f0f79 | ||
|
|
ddd1c77b49 | ||
|
|
1fabc96acc | ||
|
|
403e0e6521 | ||
|
|
61f13173c2 | ||
|
|
55e43cbc7f | ||
|
|
64755c52f2 | ||
|
|
2bfbddb81f | ||
|
|
355dc7f3a8 | ||
|
|
0657cfbb34 | ||
|
|
6e2b46d666 | ||
|
|
0710cfaa71 | ||
|
|
294d425ae4 | ||
|
|
86ff57518f | ||
|
|
3a4fd62135 | ||
|
|
c8223606ca | ||
|
|
dfb423532b | ||
|
|
726bfd3434 | ||
|
|
1bba19decb | ||
|
|
8a58a18a0a | ||
|
|
2ca97a7d48 | ||
|
|
f2d7a825b1 | ||
|
|
ad6fdf1e3c | ||
|
|
1a68e55f47 | ||
|
|
5e0decd9b5 | ||
|
|
b55ac9e64d | ||
|
|
e1093a3177 | ||
|
|
4bfa9260ce | ||
|
|
f29c139a7a | ||
|
|
7bf0496dd8 | ||
|
|
ddb7e4cc34 | ||
|
|
87b7bb1d14 | ||
|
|
f5c3b409ea | ||
|
|
9e58a0892b | ||
|
|
b9897eec7c | ||
|
|
fadd627467 | ||
|
|
a1d07796fc | ||
|
|
a2b53522eb | ||
|
|
84add47525 | ||
|
|
acd5734aa9 | ||
|
|
c6f95a0c37 | ||
|
|
f9785c63e7 | ||
|
|
61fef8c689 | ||
|
|
44f1887f25 | ||
|
|
9771d84c56 | ||
|
|
97c542a67b | ||
|
|
de2d4ecd9e | ||
|
|
cb7f74b5eb | ||
|
|
84970d325e | ||
|
|
23719dd513 | ||
|
|
9e47c1a2c5 | ||
|
|
1849cf71c2 | ||
|
|
dc45faaf4e | ||
|
|
6f23e513cc | ||
|
|
2b28e75822 | ||
|
|
52d8dc5b56 | ||
|
|
a064da3bef | ||
|
|
051b5ddafe | ||
|
|
b66197f932 | ||
|
|
9bba2ec0ad | ||
|
|
376a042ba1 | ||
|
|
d2d9a928b1 | ||
|
|
dda53a2ff8 | ||
|
|
9978d2276b | ||
|
|
6e94b047e2 | ||
|
|
0aa98a8e3b | ||
|
|
2b9981bd06 | ||
|
|
251fa72798 | ||
|
|
9b80344e58 | ||
|
|
a2b6fdc3df | ||
|
|
d5ea5f27ac | ||
|
|
98137f7f80 | ||
|
|
e3674bcc04 | ||
|
|
ffd34f8896 | ||
|
|
847faa3d04 | ||
|
|
2f013b68f8 | ||
|
|
3a7555a27b | ||
|
|
0e0ad692b5 | ||
|
|
a5d6e519eb | ||
|
|
c001c090b9 | ||
|
|
dd080b6fb0 | ||
|
|
985533efbc | ||
|
|
5f6e3499f3 | ||
|
|
7c7098fd1d | ||
|
|
0fdb55c4e4 | ||
|
|
ebc9784f26 | ||
|
|
78a842d055 | ||
|
|
1efa923ab8 | ||
|
|
9e2cbf9a30 | ||
|
|
3a91a4f8d4 | ||
|
|
b894ca6702 | ||
|
|
739ed1bf29 | ||
|
|
ed8d5b3797 | ||
|
|
5f4077cc7d | ||
|
|
64581c655d | ||
|
|
bff6025bde | ||
|
|
15b005489d | ||
|
|
49f67f54f4 | ||
|
|
6f67347e00 | ||
|
|
04cf29f613 | ||
|
|
694d12a90b | ||
|
|
35e1605147 | ||
|
|
d4d2d9e479 | ||
|
|
658f0c5d2d | ||
|
|
dbfb13b93a | ||
|
|
883df8c6a8 | ||
|
|
193fdd6e3b | ||
|
|
9f85595d80 | ||
|
|
d34bca3ce6 | ||
|
|
be4be5e783 | ||
|
|
d631326c5e | ||
|
|
f32a5b30db |
@@ -29,9 +29,10 @@ Use this skill for Parallels guest workflows and smoke interpretation. Do not lo
|
||||
- Preferred entrypoint: `pnpm test:parallels:npm-update`
|
||||
- Flow: fresh snapshot -> install npm package baseline -> smoke -> install current main tgz on the same guest -> smoke again.
|
||||
- Same-guest update verification should set the default model explicitly to `openai/gpt-5.4` before the agent turn and use a fresh explicit `--session-id` so old session model state does not leak into the check.
|
||||
- The aggregate npm-update wrapper must resolve the Linux VM with the same Ubuntu fallback policy as `parallels-linux-smoke.sh` before both fresh and update lanes. On Peter's current host, missing `Ubuntu 24.04.3 ARM64` should fall back to `Ubuntu 25.10`.
|
||||
- The aggregate npm-update wrapper must resolve the Linux VM with the same Ubuntu fallback policy as `parallels-linux-smoke.sh` before both fresh and update lanes. Treat any Ubuntu guest with major version `>= 24` as acceptable when the exact default VM is missing, preferring the closest version match. On Peter's current host today, missing `Ubuntu 24.04.3 ARM64` should fall back to `Ubuntu 25.10`.
|
||||
- On Windows same-guest update checks, restart the gateway after the npm upgrade before `gateway status` / `agent`; in-place global npm updates can otherwise leave stale hashed `dist/*` module imports alive in the running service.
|
||||
- For Windows same-guest update checks, prefer the done-file/log-drain PowerShell runner pattern over one long-lived `prlctl exec ... powershell -EncodedCommand ...` transport. The guest can finish successfully while the outer `prlctl exec` still hangs.
|
||||
- The Windows same-guest update helper should write stage markers to its log before long steps like tgz download and `npm install -g` so the outer progress monitor does not sit on `waiting for first log line` during healthy but quiet installs.
|
||||
- Linux same-guest update verification should also export `HOME=/root`, pass `OPENAI_API_KEY` via `prlctl exec ... /usr/bin/env`, and use `openclaw agent --local`; the fresh Linux baseline does not rely on persisted gateway credentials.
|
||||
- The npm-update wrapper now prints per-lane progress from the nested log files. If a lane still looks stuck, inspect the nested logs in `runDir` first (`macos-fresh.log`, `windows-fresh.log`, `linux-fresh.log`, `macos-update.log`, `windows-update.log`, `linux-update.log`) instead of assuming the outer wrapper hung.
|
||||
- If the wrapper fails a lane, read the auto-dumped tail first, then the full nested lane log under `/tmp/openclaw-parallels-npm-update.*`.
|
||||
@@ -75,7 +76,7 @@ Use this skill for Parallels guest workflows and smoke interpretation. Do not lo
|
||||
|
||||
- Preferred entrypoint: `pnpm test:parallels:linux`
|
||||
- Use the snapshot closest to fresh `Ubuntu 24.04.3 ARM64`.
|
||||
- If that exact VM is missing on the host, fall back to the closest Ubuntu guest with a fresh poweroff snapshot. On Peter's host today, that is `Ubuntu 25.10`.
|
||||
- If that exact VM is missing on the host, any Ubuntu guest with major version `>= 24` is acceptable; prefer the closest versioned Ubuntu guest with a fresh poweroff snapshot. On Peter's host today, that is `Ubuntu 25.10`.
|
||||
- Use plain `prlctl exec`; `--current-user` is not the right transport on this snapshot.
|
||||
- Fresh snapshots may be missing `curl`, and `apt-get update` can fail on clock skew. Bootstrap with `apt-get -o Acquire::Check-Date=false update` and install `curl ca-certificates`.
|
||||
- Fresh `main` tgz smoke still needs the latest-release installer first because the snapshot has no Node or npm before bootstrap.
|
||||
|
||||
@@ -116,6 +116,10 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
|
||||
## Use the right auth flow
|
||||
|
||||
- OpenClaw publish uses GitHub trusted publishing.
|
||||
- Stable npm promotion from `beta` to `latest` is an explicit mode on
|
||||
`.github/workflows/openclaw-npm-release.yml`, but it still needs a valid
|
||||
`NPM_TOKEN` because `npm dist-tag` management is separate from trusted
|
||||
publishing.
|
||||
- The publish run must be started manually with `workflow_dispatch`.
|
||||
- The npm workflow and the private mac publish workflow accept
|
||||
`preflight_only=true` to run validation/build/package steps without uploading
|
||||
@@ -240,9 +244,10 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
|
||||
preflight run, and pass the successful npm `preflight_run_id`.
|
||||
15. Wait for `npm-release` approval from `@openclaw/openclaw-release-managers`.
|
||||
16. If the stable release was published to `beta`, start
|
||||
`.github/workflows/openclaw-npm-promote-beta.yml` with the exact stable
|
||||
version after beta validation passes, then verify `latest` now points at
|
||||
that version.
|
||||
`.github/workflows/openclaw-npm-release.yml` again after beta validation
|
||||
passes with the same stable tag, `promote_beta_to_latest=true`,
|
||||
`preflight_only=false`, empty `preflight_run_id`, and `npm_dist_tag=beta`,
|
||||
then verify `latest` now points at that version.
|
||||
17. Start
|
||||
`openclaw/releases-private/.github/workflows/openclaw-macos-publish.yml`
|
||||
for the real publish with the successful private mac `preflight_run_id` and
|
||||
|
||||
@@ -33,6 +33,8 @@ node_modules
|
||||
**/.next
|
||||
coverage
|
||||
**/coverage
|
||||
docs/.generated
|
||||
**/.generated
|
||||
*.log
|
||||
tmp
|
||||
**/tmp
|
||||
|
||||
4
.github/labeler.yml
vendored
4
.github/labeler.yml
vendored
@@ -246,6 +246,10 @@
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/deepseek/**"
|
||||
"extensions: stepfun":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/stepfun/**"
|
||||
"extensions: anthropic":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
|
||||
8
.github/pull_request_template.md
vendored
8
.github/pull_request_template.md
vendored
@@ -35,19 +35,17 @@ If this PR fixes a plugin beta-release blocker, title it `fix(<plugin-id>): beta
|
||||
- Related #
|
||||
- [ ] This PR fixes a bug or regression
|
||||
|
||||
## Root Cause / Regression History (if applicable)
|
||||
## Root Cause (if applicable)
|
||||
|
||||
For bug fixes or regressions, explain why this happened, not just what changed. Otherwise write `N/A`. If the cause is unclear, write `Unknown`.
|
||||
|
||||
- Root cause:
|
||||
- Missing detection / guardrail:
|
||||
- Prior context (`git blame`, prior PR, issue, or refactor if known):
|
||||
- Why this regressed now:
|
||||
- If unknown, what was ruled out:
|
||||
- Contributing context (if known):
|
||||
|
||||
## Regression Test Plan (if applicable)
|
||||
|
||||
For bug fixes or regressions, name the smallest reliable test coverage that should have caught this. Otherwise write `N/A`.
|
||||
For bug fixes or regressions, name the smallest reliable test coverage that should catch this. Otherwise write `N/A`.
|
||||
|
||||
- Coverage level that should have caught this:
|
||||
- [ ] Unit test
|
||||
|
||||
246
.github/workflows/ci.yml
vendored
246
.github/workflows/ci.yml
vendored
@@ -17,8 +17,8 @@ env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
|
||||
jobs:
|
||||
# Preflight: establish routing truth and planner-owned matrices once, then let
|
||||
# real work fan out from a single source of truth.
|
||||
# Preflight: establish routing truth and job matrices once, then let real
|
||||
# work fan out from a single source of truth.
|
||||
preflight:
|
||||
if: github.event_name != 'pull_request' || !github.event.pull_request.draft
|
||||
runs-on: blacksmith-16vcpu-ubuntu-2404
|
||||
@@ -36,7 +36,8 @@ jobs:
|
||||
changed_extensions_matrix: ${{ steps.manifest.outputs.changed_extensions_matrix }}
|
||||
run_build_artifacts: ${{ steps.manifest.outputs.run_build_artifacts }}
|
||||
run_checks_fast: ${{ steps.manifest.outputs.run_checks_fast }}
|
||||
checks_fast_matrix: ${{ steps.manifest.outputs.checks_fast_matrix }}
|
||||
checks_fast_core_matrix: ${{ steps.manifest.outputs.checks_fast_core_matrix }}
|
||||
checks_fast_extensions_matrix: ${{ steps.manifest.outputs.checks_fast_extensions_matrix }}
|
||||
run_checks: ${{ steps.manifest.outputs.run_checks }}
|
||||
checks_matrix: ${{ steps.manifest.outputs.checks_matrix }}
|
||||
run_extension_fast: ${{ steps.manifest.outputs.run_extension_fast }}
|
||||
@@ -103,7 +104,7 @@ jobs:
|
||||
run: |
|
||||
node --input-type=module <<'EOF'
|
||||
import { appendFileSync } from "node:fs";
|
||||
import { listChangedExtensionIds } from "./scripts/test-extension.mjs";
|
||||
import { listChangedExtensionIds } from "./scripts/lib/changed-extensions.mjs";
|
||||
|
||||
const extensionIds = listChangedExtensionIds({
|
||||
base: process.env.BASE_SHA,
|
||||
@@ -129,7 +130,150 @@ jobs:
|
||||
OPENCLAW_CI_RUN_SKILLS_PYTHON: ${{ steps.changed_scope.outputs.run_skills_python || '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":[]}' }}
|
||||
run: node scripts/ci-write-manifest-outputs.mjs --workflow ci
|
||||
run: |
|
||||
node --input-type=module <<'EOF'
|
||||
import { appendFileSync } from "node:fs";
|
||||
import {
|
||||
createExtensionTestShards,
|
||||
DEFAULT_EXTENSION_TEST_SHARD_COUNT,
|
||||
} from "./scripts/lib/extension-test-plan.mjs";
|
||||
|
||||
const parseBoolean = (value, fallback = false) => {
|
||||
if (value === undefined) return fallback;
|
||||
const normalized = value.trim().toLowerCase();
|
||||
if (normalized === "true" || normalized === "1") return true;
|
||||
if (normalized === "false" || normalized === "0" || normalized === "") return false;
|
||||
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 docsOnly = parseBoolean(process.env.OPENCLAW_CI_DOCS_ONLY);
|
||||
const docsChanged = parseBoolean(process.env.OPENCLAW_CI_DOCS_CHANGED);
|
||||
const runNode = parseBoolean(process.env.OPENCLAW_CI_RUN_NODE) && !docsOnly;
|
||||
const runMacos = parseBoolean(process.env.OPENCLAW_CI_RUN_MACOS) && !docsOnly;
|
||||
const runAndroid = parseBoolean(process.env.OPENCLAW_CI_RUN_ANDROID) && !docsOnly;
|
||||
const runWindows = parseBoolean(process.env.OPENCLAW_CI_RUN_WINDOWS) && !docsOnly;
|
||||
const runSkillsPython = parseBoolean(process.env.OPENCLAW_CI_RUN_SKILLS_PYTHON) && !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 extensionShardMatrix = createMatrix(
|
||||
runNode
|
||||
? createExtensionTestShards({
|
||||
shardCount: DEFAULT_EXTENSION_TEST_SHARD_COUNT,
|
||||
}).map((shard) => ({
|
||||
check_name: shard.checkName,
|
||||
extensions_csv: shard.extensionIds.join(","),
|
||||
shard_index: shard.index + 1,
|
||||
task: "extensions-batch",
|
||||
}))
|
||||
: [],
|
||||
);
|
||||
|
||||
const manifest = {
|
||||
docs_only: docsOnly,
|
||||
docs_changed: docsChanged,
|
||||
run_node: runNode,
|
||||
run_macos: runMacos,
|
||||
run_android: runAndroid,
|
||||
run_skills_python: runSkillsPython,
|
||||
run_windows: runWindows,
|
||||
has_changed_extensions: hasChangedExtensions,
|
||||
changed_extensions_matrix: changedExtensionsMatrix,
|
||||
run_build_artifacts: runNode,
|
||||
run_checks_fast: runNode,
|
||||
checks_fast_core_matrix: createMatrix(
|
||||
runNode
|
||||
? [
|
||||
{ check_name: "checks-fast-bundled", runtime: "node", task: "bundled" },
|
||||
{
|
||||
check_name: "checks-fast-contracts-protocol",
|
||||
runtime: "node",
|
||||
task: "contracts-protocol",
|
||||
},
|
||||
]
|
||||
: [],
|
||||
),
|
||||
checks_fast_extensions_matrix: extensionShardMatrix,
|
||||
run_checks: runNode,
|
||||
checks_matrix: createMatrix(
|
||||
runNode
|
||||
? [
|
||||
{ check_name: "checks-node-test", runtime: "node", task: "test" },
|
||||
{ check_name: "checks-node-channels", runtime: "node", task: "channels" },
|
||||
...(isPush
|
||||
? [
|
||||
{
|
||||
check_name: "checks-node-compat-node22",
|
||||
runtime: "node",
|
||||
task: "compat-node22",
|
||||
node_version: "22.x",
|
||||
cache_key_suffix: "node22",
|
||||
},
|
||||
]
|
||||
: []),
|
||||
]
|
||||
: [],
|
||||
),
|
||||
run_extension_fast: hasChangedExtensions,
|
||||
extension_fast_matrix: createMatrix(
|
||||
hasChangedExtensions
|
||||
? (changedExtensionsMatrix.include ?? []).map((entry) => ({
|
||||
check_name: `extension-fast-${entry.extension}`,
|
||||
extension: entry.extension,
|
||||
}))
|
||||
: [],
|
||||
),
|
||||
run_check: runNode,
|
||||
run_check_additional: runNode,
|
||||
run_build_smoke: runNode,
|
||||
run_check_docs: docsChanged,
|
||||
run_skills_python_job: runSkillsPython,
|
||||
run_checks_windows: runWindows,
|
||||
checks_windows_matrix: createMatrix(
|
||||
runWindows
|
||||
? [{ check_name: "checks-windows-node-test", runtime: "node", task: "test" }]
|
||||
: [],
|
||||
),
|
||||
run_macos_node: runMacos,
|
||||
macos_node_matrix: createMatrix(
|
||||
runMacos ? [{ check_name: "macos-node", runtime: "node", task: "test" }] : [],
|
||||
),
|
||||
run_macos_swift: runMacos,
|
||||
run_android_job: runAndroid,
|
||||
android_matrix: createMatrix(
|
||||
runAndroid
|
||||
? [
|
||||
{ check_name: "android-test-play", task: "test-play" },
|
||||
{ check_name: "android-test-third-party", task: "test-third-party" },
|
||||
{ check_name: "android-build-play", task: "build-play" },
|
||||
{ check_name: "android-build-third-party", task: "build-third-party" },
|
||||
]
|
||||
: [],
|
||||
),
|
||||
};
|
||||
|
||||
for (const [key, value] of Object.entries(manifest)) {
|
||||
appendFileSync(
|
||||
outputPath,
|
||||
`${key}=${typeof value === "string" ? value : JSON.stringify(value)}\n`,
|
||||
"utf8",
|
||||
);
|
||||
}
|
||||
EOF
|
||||
|
||||
# Run the fast security/SCM checks in parallel with scope detection so the
|
||||
# main Node jobs do not have to wait for Python/pre-commit setup.
|
||||
@@ -277,7 +421,7 @@ jobs:
|
||||
include-hidden-files: true
|
||||
retention-days: 1
|
||||
|
||||
checks-fast:
|
||||
checks-fast-core:
|
||||
name: ${{ matrix.check_name }}
|
||||
needs: [preflight]
|
||||
if: needs.preflight.outputs.run_checks_fast == 'true'
|
||||
@@ -285,7 +429,7 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.preflight.outputs.checks_fast_matrix) }}
|
||||
matrix: ${{ fromJson(needs.preflight.outputs.checks_fast_core_matrix) }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
@@ -302,18 +446,12 @@ jobs:
|
||||
- name: Run ${{ matrix.task }} (${{ matrix.runtime }})
|
||||
env:
|
||||
TASK: ${{ matrix.task }}
|
||||
SHARD_COUNT: ${{ matrix.shard_count || '' }}
|
||||
SHARD_INDEX: ${{ matrix.shard_index || '' }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
case "$TASK" in
|
||||
extensions)
|
||||
if [ -n "$SHARD_COUNT" ] && [ -n "$SHARD_INDEX" ]; then
|
||||
export OPENCLAW_TEST_SHARDS="$SHARD_COUNT"
|
||||
export OPENCLAW_TEST_SHARD_INDEX="$SHARD_INDEX"
|
||||
fi
|
||||
pnpm test:extensions
|
||||
bundled)
|
||||
pnpm test:bundled
|
||||
;;
|
||||
contracts|contracts-protocol)
|
||||
pnpm build
|
||||
@@ -326,6 +464,49 @@ jobs:
|
||||
;;
|
||||
esac
|
||||
|
||||
checks-fast-extensions-shard:
|
||||
name: ${{ matrix.check_name }}
|
||||
needs: [preflight]
|
||||
if: needs.preflight.outputs.run_checks_fast == 'true'
|
||||
runs-on: blacksmith-16vcpu-ubuntu-2404
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.preflight.outputs.checks_fast_extensions_matrix) }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: false
|
||||
|
||||
- name: Setup Node environment
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
install-bun: "false"
|
||||
use-sticky-disk: "false"
|
||||
|
||||
- name: Run extension shard
|
||||
env:
|
||||
OPENCLAW_EXTENSION_BATCH: ${{ matrix.extensions_csv }}
|
||||
run: pnpm test:extensions:batch -- "$OPENCLAW_EXTENSION_BATCH"
|
||||
|
||||
checks-fast-extensions:
|
||||
name: checks-fast-extensions
|
||||
needs: [preflight, checks-fast-extensions-shard]
|
||||
if: always() && needs.preflight.outputs.run_checks_fast == 'true'
|
||||
runs-on: blacksmith-16vcpu-ubuntu-2404
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Verify extension shards
|
||||
env:
|
||||
SHARD_RESULT: ${{ needs.checks-fast-extensions-shard.result }}
|
||||
run: |
|
||||
if [ "$SHARD_RESULT" != "success" ]; then
|
||||
echo "Extension shard checks failed: $SHARD_RESULT" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
checks:
|
||||
name: ${{ matrix.check_name }}
|
||||
needs: [preflight, build-artifacts]
|
||||
@@ -360,21 +541,12 @@ jobs:
|
||||
if: (github.event_name != 'pull_request' || matrix.task != 'compat-node22') && matrix.runtime == 'node' && (matrix.task == 'test' || matrix.task == 'channels' || matrix.task == 'compat-node22')
|
||||
env:
|
||||
TASK: ${{ matrix.task }}
|
||||
SHARD_COUNT: ${{ matrix.shard_count || '' }}
|
||||
SHARD_INDEX: ${{ matrix.shard_index || '' }}
|
||||
run: |
|
||||
# `pnpm test` runs `scripts/test-parallel.mjs`, which spawns multiple Node processes.
|
||||
# Default heap limits have been too low on Linux CI (V8 OOM near 4GB).
|
||||
echo "OPENCLAW_TEST_WORKERS=2" >> "$GITHUB_ENV"
|
||||
echo "OPENCLAW_TEST_MAX_OLD_SPACE_SIZE_MB=6144" >> "$GITHUB_ENV"
|
||||
echo "OPENCLAW_VITEST_MAX_WORKERS=2" >> "$GITHUB_ENV"
|
||||
if [ "$TASK" = "channels" ]; then
|
||||
echo "OPENCLAW_TEST_WORKERS=1" >> "$GITHUB_ENV"
|
||||
echo "OPENCLAW_VITEST_MAX_WORKERS=1" >> "$GITHUB_ENV"
|
||||
echo "OPENCLAW_TEST_ISOLATE=1" >> "$GITHUB_ENV"
|
||||
fi
|
||||
if [ -n "$SHARD_COUNT" ] && [ -n "$SHARD_INDEX" ]; then
|
||||
echo "OPENCLAW_TEST_SHARDS=$SHARD_COUNT" >> "$GITHUB_ENV"
|
||||
echo "OPENCLAW_TEST_SHARD_INDEX=$SHARD_INDEX" >> "$GITHUB_ENV"
|
||||
fi
|
||||
|
||||
- name: Download dist artifact
|
||||
if: matrix.task == 'test'
|
||||
@@ -394,6 +566,7 @@ jobs:
|
||||
if: github.event_name != 'pull_request' || matrix.task != 'compat-node22'
|
||||
env:
|
||||
TASK: ${{ matrix.task }}
|
||||
NODE_OPTIONS: --max-old-space-size=6144
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@@ -735,8 +908,7 @@ jobs:
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=6144
|
||||
# Keep total concurrency predictable on the 32 vCPU runner.
|
||||
# Windows shard 2 has shown intermittent instability at 2 workers.
|
||||
OPENCLAW_TEST_WORKERS: 1
|
||||
OPENCLAW_VITEST_MAX_WORKERS: 1
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
@@ -809,15 +981,6 @@ jobs:
|
||||
# caches can skip repeated rebuild/download work on later shards/runs.
|
||||
pnpm install --frozen-lockfile --prefer-offline --ignore-scripts=false --config.engine-strict=false --config.enable-pre-post-scripts=true --config.side-effects-cache=true || pnpm install --frozen-lockfile --prefer-offline --ignore-scripts=false --config.engine-strict=false --config.enable-pre-post-scripts=true --config.side-effects-cache=true
|
||||
|
||||
- name: Configure test shard (Windows)
|
||||
if: matrix.task == 'test'
|
||||
env:
|
||||
SHARD_COUNT: ${{ matrix.shard_count }}
|
||||
SHARD_INDEX: ${{ matrix.shard_index }}
|
||||
run: |
|
||||
echo "OPENCLAW_TEST_SHARDS=$SHARD_COUNT" >> "$GITHUB_ENV"
|
||||
echo "OPENCLAW_TEST_SHARD_INDEX=$SHARD_INDEX" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Download dist artifact
|
||||
if: matrix.task == 'test'
|
||||
uses: actions/download-artifact@v8
|
||||
@@ -881,17 +1044,10 @@ jobs:
|
||||
name: canvas-a2ui-bundle
|
||||
path: src/canvas-host/a2ui/
|
||||
|
||||
- name: Configure test shard (macOS)
|
||||
env:
|
||||
SHARD_COUNT: ${{ matrix.shard_count }}
|
||||
SHARD_INDEX: ${{ matrix.shard_index }}
|
||||
run: |
|
||||
echo "OPENCLAW_TEST_SHARDS=$SHARD_COUNT" >> "$GITHUB_ENV"
|
||||
echo "OPENCLAW_TEST_SHARD_INDEX=$SHARD_INDEX" >> "$GITHUB_ENV"
|
||||
|
||||
- name: TS tests (macOS)
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=4096
|
||||
OPENCLAW_VITEST_MAX_WORKERS: 2
|
||||
TASK: ${{ matrix.task }}
|
||||
shell: bash
|
||||
run: |
|
||||
|
||||
20
.github/workflows/install-smoke.yml
vendored
20
.github/workflows/install-smoke.yml
vendored
@@ -67,16 +67,18 @@ jobs:
|
||||
id: manifest
|
||||
env:
|
||||
OPENCLAW_CI_DOCS_ONLY: ${{ steps.docs_scope.outputs.docs_only }}
|
||||
OPENCLAW_CI_DOCS_CHANGED: "false"
|
||||
OPENCLAW_CI_RUN_NODE: "false"
|
||||
OPENCLAW_CI_RUN_MACOS: "false"
|
||||
OPENCLAW_CI_RUN_ANDROID: "false"
|
||||
OPENCLAW_CI_RUN_WINDOWS: "false"
|
||||
OPENCLAW_CI_RUN_SKILLS_PYTHON: "false"
|
||||
OPENCLAW_CI_HAS_CHANGED_EXTENSIONS: "false"
|
||||
OPENCLAW_CI_CHANGED_EXTENSIONS_MATRIX: '{"include":[]}'
|
||||
OPENCLAW_CI_RUN_CHANGED_SMOKE: ${{ steps.changed_scope.outputs.run_changed_smoke || 'false' }}
|
||||
run: node scripts/ci-write-manifest-outputs.mjs --workflow install-smoke
|
||||
run: |
|
||||
docs_only="${OPENCLAW_CI_DOCS_ONLY:-false}"
|
||||
run_changed_smoke="${OPENCLAW_CI_RUN_CHANGED_SMOKE:-false}"
|
||||
run_install_smoke=false
|
||||
if [ "$docs_only" != "true" ] && [ "$run_changed_smoke" = "true" ]; then
|
||||
run_install_smoke=true
|
||||
fi
|
||||
{
|
||||
echo "docs_only=$docs_only"
|
||||
echo "run_install_smoke=$run_install_smoke"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
install-smoke:
|
||||
needs: [preflight]
|
||||
|
||||
89
.github/workflows/openclaw-npm-promote-beta.yml
vendored
89
.github/workflows/openclaw-npm-promote-beta.yml
vendored
@@ -1,89 +0,0 @@
|
||||
name: OpenClaw NPM Promote Beta
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: Stable version currently on npm beta to promote to latest (for example 2026.4.2 or 2026.4.2-1)
|
||||
required: true
|
||||
type: string
|
||||
|
||||
concurrency:
|
||||
group: openclaw-npm-promote-beta-${{ inputs.version }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
NODE_VERSION: "24.x"
|
||||
PNPM_VERSION: "10.23.0"
|
||||
|
||||
jobs:
|
||||
promote_beta_to_latest:
|
||||
runs-on: ubuntu-latest
|
||||
environment: npm-release
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Validate version input format
|
||||
env:
|
||||
RELEASE_VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ ! "${RELEASE_VERSION}" =~ ^[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*(-[1-9][0-9]*)?$ ]]; then
|
||||
echo "Invalid stable release version format: ${RELEASE_VERSION}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Node environment
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
pnpm-version: ${{ env.PNPM_VERSION }}
|
||||
install-bun: "false"
|
||||
use-sticky-disk: "false"
|
||||
install-deps: "false"
|
||||
|
||||
- name: Validate npm dist-tags
|
||||
env:
|
||||
RELEASE_VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
beta_version="$(npm view openclaw dist-tags.beta)"
|
||||
latest_version="$(npm view openclaw dist-tags.latest)"
|
||||
|
||||
echo "Current beta dist-tag: ${beta_version}"
|
||||
echo "Current latest dist-tag: ${latest_version}"
|
||||
|
||||
if [[ "${beta_version}" != "${RELEASE_VERSION}" ]]; then
|
||||
echo "npm beta points at ${beta_version}, expected ${RELEASE_VERSION}." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! npm view "openclaw@${RELEASE_VERSION}" version >/dev/null 2>&1; then
|
||||
echo "openclaw@${RELEASE_VERSION} is not published on npm." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Promote beta to latest
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
RELEASE_VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
userconfig="$(mktemp)"
|
||||
trap 'rm -f "${userconfig}"' EXIT
|
||||
chmod 0600 "${userconfig}"
|
||||
printf '%s\n' "//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}" > "${userconfig}"
|
||||
|
||||
NPM_CONFIG_USERCONFIG="${userconfig}" npm whoami >/dev/null
|
||||
NPM_CONFIG_USERCONFIG="${userconfig}" \
|
||||
npm dist-tag add "openclaw@${RELEASE_VERSION}" latest
|
||||
promoted_latest="$(npm view openclaw dist-tags.latest)"
|
||||
if [[ "${promoted_latest}" != "${RELEASE_VERSION}" ]]; then
|
||||
echo "npm latest points at ${promoted_latest}, expected ${RELEASE_VERSION} after promotion." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "Promoted openclaw@${RELEASE_VERSION} from beta to latest."
|
||||
109
.github/workflows/openclaw-npm-release.yml
vendored
109
.github/workflows/openclaw-npm-release.yml
vendored
@@ -24,9 +24,14 @@ on:
|
||||
options:
|
||||
- beta
|
||||
- latest
|
||||
promote_beta_to_latest:
|
||||
description: Skip publish and promote the stable version already on npm beta to latest
|
||||
required: true
|
||||
default: false
|
||||
type: boolean
|
||||
|
||||
concurrency:
|
||||
group: openclaw-npm-release-${{ github.event_name == 'workflow_dispatch' && format('{0}-{1}', inputs.tag, inputs.npm_dist_tag) || github.ref }}
|
||||
group: openclaw-npm-release-${{ github.event_name == 'workflow_dispatch' && format('{0}-{1}-{2}', inputs.tag, inputs.npm_dist_tag, inputs.promote_beta_to_latest) || github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
@@ -36,7 +41,7 @@ env:
|
||||
|
||||
jobs:
|
||||
preflight_openclaw_npm:
|
||||
if: ${{ inputs.preflight_only }}
|
||||
if: ${{ inputs.preflight_only && !inputs.promote_beta_to_latest }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -157,7 +162,7 @@ jobs:
|
||||
if-no-files-found: error
|
||||
|
||||
validate_publish_request:
|
||||
if: ${{ !inputs.preflight_only }}
|
||||
if: ${{ !inputs.preflight_only && !inputs.promote_beta_to_latest }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -185,7 +190,7 @@ jobs:
|
||||
publish_openclaw_npm:
|
||||
# npm trusted publishing + provenance requires a GitHub-hosted runner.
|
||||
needs: [validate_publish_request]
|
||||
if: ${{ !inputs.preflight_only }}
|
||||
if: ${{ !inputs.preflight_only && !inputs.promote_beta_to_latest }}
|
||||
runs-on: ubuntu-latest
|
||||
environment: npm-release
|
||||
permissions:
|
||||
@@ -321,3 +326,99 @@ jobs:
|
||||
publish_target="./${publish_target}"
|
||||
fi
|
||||
bash scripts/openclaw-npm-publish.sh --publish "${publish_target}"
|
||||
|
||||
promote_beta_to_latest:
|
||||
if: ${{ inputs.promote_beta_to_latest }}
|
||||
runs-on: ubuntu-latest
|
||||
environment: npm-release
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Require main workflow ref for promotion
|
||||
env:
|
||||
WORKFLOW_REF: ${{ github.ref }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ "${WORKFLOW_REF}" != "refs/heads/main" ]]; then
|
||||
echo "Promotion runs must be dispatched from main."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Validate promotion inputs
|
||||
env:
|
||||
PREFLIGHT_ONLY: ${{ inputs.preflight_only }}
|
||||
PREFLIGHT_RUN_ID: ${{ inputs.preflight_run_id }}
|
||||
RELEASE_NPM_DIST_TAG: ${{ inputs.npm_dist_tag }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ "${PREFLIGHT_ONLY}" == "true" ]]; then
|
||||
echo "Promotion mode cannot run with preflight_only=true."
|
||||
exit 1
|
||||
fi
|
||||
if [[ -n "${PREFLIGHT_RUN_ID}" ]]; then
|
||||
echo "Promotion mode does not use preflight_run_id."
|
||||
exit 1
|
||||
fi
|
||||
if [[ "${RELEASE_NPM_DIST_TAG}" != "beta" ]]; then
|
||||
echo "Promotion mode expects npm_dist_tag=beta because it moves beta to latest without publishing."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Validate stable tag input format
|
||||
env:
|
||||
RELEASE_TAG: ${{ inputs.tag }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ ! "${RELEASE_TAG}" =~ ^v[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*(-[1-9][0-9]*)?$ ]]; then
|
||||
echo "Invalid stable release tag format: ${RELEASE_TAG}" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "RELEASE_VERSION=${RELEASE_TAG#v}" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Node environment
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
pnpm-version: ${{ env.PNPM_VERSION }}
|
||||
install-bun: "false"
|
||||
use-sticky-disk: "false"
|
||||
install-deps: "false"
|
||||
|
||||
- name: Validate npm dist-tags
|
||||
env:
|
||||
RELEASE_VERSION: ${{ env.RELEASE_VERSION }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
beta_version="$(npm view openclaw dist-tags.beta)"
|
||||
latest_version="$(npm view openclaw dist-tags.latest)"
|
||||
|
||||
echo "Current beta dist-tag: ${beta_version}"
|
||||
echo "Current latest dist-tag: ${latest_version}"
|
||||
|
||||
if [[ "${beta_version}" != "${RELEASE_VERSION}" ]]; then
|
||||
echo "npm beta points at ${beta_version}, expected ${RELEASE_VERSION}." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! npm view "openclaw@${RELEASE_VERSION}" version >/dev/null 2>&1; then
|
||||
echo "openclaw@${RELEASE_VERSION} is not published on npm." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Promote beta to latest
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
RELEASE_VERSION: ${{ env.RELEASE_VERSION }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
npm whoami >/dev/null
|
||||
npm dist-tag add "openclaw@${RELEASE_VERSION}" latest
|
||||
promoted_latest="$(npm view openclaw dist-tags.latest)"
|
||||
if [[ "${promoted_latest}" != "${RELEASE_VERSION}" ]]; then
|
||||
echo "npm latest points at ${promoted_latest}, expected ${RELEASE_VERSION} after promotion." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "Promoted openclaw@${RELEASE_VERSION} from beta to latest."
|
||||
|
||||
276
.github/workflows/plugin-clawhub-release.yml
vendored
Normal file
276
.github/workflows/plugin-clawhub-release.yml
vendored
Normal file
@@ -0,0 +1,276 @@
|
||||
name: Plugin ClawHub Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
publish_scope:
|
||||
description: Publish the selected plugins or all ClawHub-publishable plugins from the workflow ref
|
||||
required: true
|
||||
default: selected
|
||||
type: choice
|
||||
options:
|
||||
- selected
|
||||
- all-publishable
|
||||
plugins:
|
||||
description: Comma-separated plugin package names to publish when publish_scope=selected
|
||||
required: false
|
||||
type: string
|
||||
|
||||
concurrency:
|
||||
group: plugin-clawhub-release-${{ github.sha }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
NODE_VERSION: "24.x"
|
||||
PNPM_VERSION: "10.23.0"
|
||||
CLAWHUB_REGISTRY: "https://clawhub.ai"
|
||||
CLAWHUB_REPOSITORY: "openclaw/clawhub"
|
||||
# Pinned to a reviewed ClawHub commit so release behavior stays reproducible.
|
||||
CLAWHUB_REF: "4af2bd50a71465683dbf8aa269af764b9d39bdf5"
|
||||
|
||||
jobs:
|
||||
preview_plugins_clawhub:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
outputs:
|
||||
ref_sha: ${{ steps.ref.outputs.sha }}
|
||||
has_candidates: ${{ steps.plan.outputs.has_candidates }}
|
||||
candidate_count: ${{ steps.plan.outputs.candidate_count }}
|
||||
skipped_published_count: ${{ steps.plan.outputs.skipped_published_count }}
|
||||
matrix: ${{ steps.plan.outputs.matrix }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ github.sha }}
|
||||
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: "false"
|
||||
use-sticky-disk: "false"
|
||||
|
||||
- name: Resolve checked-out ref
|
||||
id: ref
|
||||
run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Validate ref is on main
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main
|
||||
git merge-base --is-ancestor HEAD origin/main
|
||||
|
||||
- name: Validate publishable plugin metadata
|
||||
env:
|
||||
PUBLISH_SCOPE: ${{ github.event_name == 'workflow_dispatch' && inputs.publish_scope || '' }}
|
||||
RELEASE_PLUGINS: ${{ github.event_name == 'workflow_dispatch' && inputs.plugins || '' }}
|
||||
BASE_REF: ${{ github.event_name != 'workflow_dispatch' && github.event.before || '' }}
|
||||
HEAD_REF: ${{ steps.ref.outputs.sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ -n "${PUBLISH_SCOPE}" ]]; then
|
||||
release_args=(--selection-mode "${PUBLISH_SCOPE}")
|
||||
if [[ -n "${RELEASE_PLUGINS}" ]]; then
|
||||
release_args+=(--plugins "${RELEASE_PLUGINS}")
|
||||
fi
|
||||
pnpm release:plugins:clawhub:check -- "${release_args[@]}"
|
||||
elif [[ -n "${BASE_REF}" ]]; then
|
||||
pnpm release:plugins:clawhub:check -- --base-ref "${BASE_REF}" --head-ref "${HEAD_REF}"
|
||||
else
|
||||
pnpm release:plugins:clawhub:check
|
||||
fi
|
||||
|
||||
- name: Resolve plugin release plan
|
||||
id: plan
|
||||
env:
|
||||
PUBLISH_SCOPE: ${{ github.event_name == 'workflow_dispatch' && inputs.publish_scope || '' }}
|
||||
RELEASE_PLUGINS: ${{ github.event_name == 'workflow_dispatch' && inputs.plugins || '' }}
|
||||
BASE_REF: ${{ github.event_name != 'workflow_dispatch' && github.event.before || '' }}
|
||||
HEAD_REF: ${{ steps.ref.outputs.sha }}
|
||||
CLAWHUB_REGISTRY: ${{ env.CLAWHUB_REGISTRY }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p .local
|
||||
if [[ -n "${PUBLISH_SCOPE}" ]]; then
|
||||
plan_args=(--selection-mode "${PUBLISH_SCOPE}")
|
||||
if [[ -n "${RELEASE_PLUGINS}" ]]; then
|
||||
plan_args+=(--plugins "${RELEASE_PLUGINS}")
|
||||
fi
|
||||
node --import tsx scripts/plugin-clawhub-release-plan.ts "${plan_args[@]}" > .local/plugin-clawhub-release-plan.json
|
||||
elif [[ -n "${BASE_REF}" ]]; then
|
||||
node --import tsx scripts/plugin-clawhub-release-plan.ts --base-ref "${BASE_REF}" --head-ref "${HEAD_REF}" > .local/plugin-clawhub-release-plan.json
|
||||
else
|
||||
node --import tsx scripts/plugin-clawhub-release-plan.ts > .local/plugin-clawhub-release-plan.json
|
||||
fi
|
||||
|
||||
cat .local/plugin-clawhub-release-plan.json
|
||||
|
||||
candidate_count="$(jq -r '.candidates | length' .local/plugin-clawhub-release-plan.json)"
|
||||
skipped_published_count="$(jq -r '.skippedPublished | length' .local/plugin-clawhub-release-plan.json)"
|
||||
has_candidates="false"
|
||||
if [[ "${candidate_count}" != "0" ]]; then
|
||||
has_candidates="true"
|
||||
fi
|
||||
matrix_json="$(jq -c '.candidates' .local/plugin-clawhub-release-plan.json)"
|
||||
|
||||
{
|
||||
echo "candidate_count=${candidate_count}"
|
||||
echo "skipped_published_count=${skipped_published_count}"
|
||||
echo "has_candidates=${has_candidates}"
|
||||
echo "matrix=${matrix_json}"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
echo "Plugin release candidates:"
|
||||
jq -r '.candidates[]? | "- \(.packageName)@\(.version) [\(.publishTag)] from \(.packageDir)"' .local/plugin-clawhub-release-plan.json
|
||||
|
||||
echo "Already published / skipped:"
|
||||
jq -r '.skippedPublished[]? | "- \(.packageName)@\(.version)"' .local/plugin-clawhub-release-plan.json
|
||||
|
||||
- name: Fail manual publish when target versions already exist
|
||||
if: github.event_name == 'workflow_dispatch' && inputs.publish_scope == 'selected' && steps.plan.outputs.skipped_published_count != '0'
|
||||
run: |
|
||||
echo "::error::One or more selected plugin versions already exist on ClawHub. Bump the version before running a real publish."
|
||||
exit 1
|
||||
|
||||
preview_plugin_pack:
|
||||
needs: preview_plugins_clawhub
|
||||
if: needs.preview_plugins_clawhub.outputs.has_candidates == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
plugin: ${{ fromJson(needs.preview_plugins_clawhub.outputs.matrix) }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ needs.preview_plugins_clawhub.outputs.ref_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"
|
||||
use-sticky-disk: "false"
|
||||
install-deps: "false"
|
||||
|
||||
- name: Checkout ClawHub CLI source
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
repository: ${{ env.CLAWHUB_REPOSITORY }}
|
||||
ref: ${{ env.CLAWHUB_REF }}
|
||||
path: clawhub-source
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Install ClawHub CLI dependencies
|
||||
working-directory: clawhub-source
|
||||
run: bun install --frozen-lockfile
|
||||
|
||||
- name: Bootstrap ClawHub CLI
|
||||
run: |
|
||||
cat > "$RUNNER_TEMP/clawhub" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
exec bun "$GITHUB_WORKSPACE/clawhub-source/packages/clawhub/src/cli.ts" "$@"
|
||||
EOF
|
||||
chmod +x "$RUNNER_TEMP/clawhub"
|
||||
echo "$RUNNER_TEMP" >> "$GITHUB_PATH"
|
||||
|
||||
- name: Preview publish command
|
||||
env:
|
||||
CLAWHUB_REGISTRY: ${{ env.CLAWHUB_REGISTRY }}
|
||||
SOURCE_REPO: ${{ github.repository }}
|
||||
SOURCE_COMMIT: ${{ needs.preview_plugins_clawhub.outputs.ref_sha }}
|
||||
SOURCE_REF: ${{ github.ref }}
|
||||
PACKAGE_TAG: ${{ matrix.plugin.publishTag }}
|
||||
PACKAGE_DIR: ${{ matrix.plugin.packageDir }}
|
||||
run: bash scripts/plugin-clawhub-publish.sh --dry-run "${PACKAGE_DIR}"
|
||||
|
||||
publish_plugins_clawhub:
|
||||
needs: [preview_plugins_clawhub, preview_plugin_pack]
|
||||
if: github.event_name == 'workflow_dispatch' && needs.preview_plugins_clawhub.outputs.has_candidates == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
environment: clawhub-plugin-release
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
plugin: ${{ fromJson(needs.preview_plugins_clawhub.outputs.matrix) }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ needs.preview_plugins_clawhub.outputs.ref_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"
|
||||
use-sticky-disk: "false"
|
||||
install-deps: "false"
|
||||
|
||||
- name: Checkout ClawHub CLI source
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
repository: ${{ env.CLAWHUB_REPOSITORY }}
|
||||
ref: ${{ env.CLAWHUB_REF }}
|
||||
path: clawhub-source
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Install ClawHub CLI dependencies
|
||||
working-directory: clawhub-source
|
||||
run: bun install --frozen-lockfile
|
||||
|
||||
- name: Bootstrap ClawHub CLI
|
||||
run: |
|
||||
cat > "$RUNNER_TEMP/clawhub" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
exec bun "$GITHUB_WORKSPACE/clawhub-source/packages/clawhub/src/cli.ts" "$@"
|
||||
EOF
|
||||
chmod +x "$RUNNER_TEMP/clawhub"
|
||||
echo "$RUNNER_TEMP" >> "$GITHUB_PATH"
|
||||
|
||||
- name: Ensure version is not already published
|
||||
env:
|
||||
PACKAGE_NAME: ${{ matrix.plugin.packageName }}
|
||||
PACKAGE_VERSION: ${{ matrix.plugin.version }}
|
||||
CLAWHUB_REGISTRY: ${{ env.CLAWHUB_REGISTRY }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
encoded_name="$(node -e 'console.log(encodeURIComponent(process.env.PACKAGE_NAME ?? ""))')"
|
||||
encoded_version="$(node -e 'console.log(encodeURIComponent(process.env.PACKAGE_VERSION ?? ""))')"
|
||||
url="${CLAWHUB_REGISTRY%/}/api/v1/packages/${encoded_name}/versions/${encoded_version}"
|
||||
status="$(curl --silent --show-error --output /dev/null --write-out '%{http_code}' "${url}")"
|
||||
if [[ "${status}" =~ ^2 ]]; then
|
||||
echo "${PACKAGE_NAME}@${PACKAGE_VERSION} is already published on ClawHub."
|
||||
exit 1
|
||||
fi
|
||||
if [[ "${status}" != "404" ]]; then
|
||||
echo "Unexpected ClawHub response (${status}) for ${PACKAGE_NAME}@${PACKAGE_VERSION}."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Publish
|
||||
env:
|
||||
CLAWHUB_REGISTRY: ${{ env.CLAWHUB_REGISTRY }}
|
||||
SOURCE_REPO: ${{ github.repository }}
|
||||
SOURCE_COMMIT: ${{ needs.preview_plugins_clawhub.outputs.ref_sha }}
|
||||
SOURCE_REF: ${{ github.ref }}
|
||||
PACKAGE_TAG: ${{ matrix.plugin.publishTag }}
|
||||
PACKAGE_DIR: ${{ matrix.plugin.packageDir }}
|
||||
run: bash scripts/plugin-clawhub-publish.sh --publish "${PACKAGE_DIR}"
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -141,3 +141,5 @@ changelog/fragments/
|
||||
|
||||
# Local scratch workspace
|
||||
.tmp/
|
||||
test/fixtures/openclaw-vitest-unit-report.json
|
||||
analysis/
|
||||
|
||||
@@ -185,6 +185,11 @@
|
||||
- Test performance guardrail: inside an extension package, prefer a thin local seam (`./api.ts`, `./runtime-api.ts`, or a narrower local `*.runtime-api.ts`) over direct `openclaw/plugin-sdk/*` imports for internal production code. Keep local seams curated and lightweight; only reach for direct `plugin-sdk/*` imports when you are crossing a real package boundary or when no suitable local seam exists yet.
|
||||
- Test performance guardrail: keep expensive runtime fallback work such as snapshotting, migration, installs, or bootstrap behind dedicated `*.runtime.ts` boundaries so tests can mock the seam instead of accidentally invoking real work.
|
||||
- Test performance guardrail: for import-only/runtime-wrapper tests, keep the wrapper lazy. Do not eagerly load heavy verification/bootstrap/runtime modules at module top level if the exported function can import them on demand.
|
||||
- Test performance guardrail: prefer explicit mock factories over `importOriginal()` for broad modules. Reserve `importOriginal()` for narrow modules where partial-real behavior is genuinely needed.
|
||||
- Test performance guardrail: do not partial-mock broad `openclaw/plugin-sdk/*` barrels in hot tests. Add a plugin-local `*.runtime.ts` seam and mock that seam instead.
|
||||
- Test performance guardrail: when production code already accepts `deps`, callbacks, or runtime injection, use that seam in tests before adding module-level mocks.
|
||||
- Test performance guardrail: prefer narrow public SDK subpaths such as `models-provider-runtime`, `skill-commands-runtime`, and `reply-dispatch-runtime` over older broad helper barrels when both expose the needed helper.
|
||||
- Test performance guardrail: treat import-dominated test time as a boundary bug. Refactor the import surface before adding more cases to the slow file.
|
||||
- Agents MUST NOT modify baseline, inventory, ignore, snapshot, or expected-failure files to silence failing checks without explicit approval in this chat.
|
||||
- For targeted/local debugging, keep using the wrapper: `pnpm test -- <path-or-filter> [vitest args...]` (for example `pnpm test -- src/commands/onboard-search.test.ts -t "shows registered plugin providers"`); do not default to raw `pnpm vitest run ...` because it bypasses wrapper config/profile/pool routing.
|
||||
- Do not set test workers above 16; tried already.
|
||||
|
||||
152
CHANGELOG.md
152
CHANGELOG.md
@@ -6,14 +6,56 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Changes
|
||||
|
||||
- Android/assistant: auto-send Google Assistant App Actions prompts once chat is healthy and idle, while keeping bare assistant launches as open-only. (#59721) Thanks @obviyus.
|
||||
- Channels/context visibility: add configurable `contextVisibility` per channel (`all`, `allowlist`, `allowlist_quote`) so supplemental quote, thread, and fetched history context can be filtered by sender allowlists instead of always passing through as received.
|
||||
- Matrix/exec approvals: add Matrix-native exec approval prompts with account-scoped approvers, channel-or-DM delivery, and room-thread aware resolution handling. (#58635) Thanks @gumadeiras.
|
||||
- Providers/StepFun: add the bundled StepFun provider plugin with standard and Step Plan endpoints, China/global onboarding choices, `step-3.5-flash` on both catalogs, and `step-3.5-flash-2603` currently exposed on Step Plan. (#60032) Thanks @hengm3467.
|
||||
- Providers/config: add full `models.providers.*.request` transport overrides for model-provider paths, including headers, auth, proxy, and TLS, and keep media provider HTTP request transport overrides aligned with the same request-policy surface. (#60200) Thanks @vincentkoc.
|
||||
- Control UI/skills: add ClawHub search, detail, and install flows directly in the Skills panel. (#60134) Thanks @samzong.
|
||||
- Outbound/runtime seams: split delivery, target-resolution, and session/transcript helper loading into narrower runtime seams so outbound hot paths and their owner tests avoid broader setup fan-out. (#60311) Thanks @shakkernerd.
|
||||
- Plugins/browser seams: split browser and WhatsApp plugin-sdk seams into narrower browser, approval-auth, and target-helper facades so hot paths and owner tests avoid broader runtime fan-out. (#60376) Thanks @shakkernerd.
|
||||
- Tests/runtime: trim local unit-test import/runtime fan-out across browser, WhatsApp, cron, task, and reply flows so owner suites start faster with lower shared-worker overhead while preserving the same focused behavior coverage. (#60249) Thanks @shakkernerd.
|
||||
- Tests/secrets runtime: restore split secrets suite cache and env isolation cleanup so broader runs do not leak stale plugin or provider snapshot state. (#60395) Thanks @shakkernerd.
|
||||
- Providers/Ollama: add bundled Ollama Web Search provider for key-free web_search via your configured Ollama host and `ollama signin`. (#59318) Thanks @BruceMacD.
|
||||
- Plugins/install: add `openclaw plugins install --force` to overwrite existing plugin and hook-pack install targets without using the dangerous-code override flag. (#60544) Thanks @gumadeiras.
|
||||
- Providers/transport: add shared proxy/TLS/auth-aware request transport support across model-provider paths, including Anthropic and Google native transport runtimes, so provider request overrides work beyond OpenAI-family traffic.
|
||||
|
||||
### Fixes
|
||||
|
||||
- Plugins/OpenAI: enable reference-image edits for `gpt-image-1` by routing edit calls to `/images/edits` with multipart image uploads, and update image-generation capability/docs metadata accordingly.
|
||||
- Skills/uv install: block workspace `.env` from overriding `UV_PYTHON` and strip related interpreter override keys from uv skill-install subprocesses so repository-controlled env files cannot steer the selected Python runtime. (#59178) Thanks @pgondhi987.
|
||||
- Telegram/reactions: preserve `reactionNotifications: "own"` across gateway restarts by persisting sent-message ownership state instead of treating cold cache as a permissive fallback. (#59207) Thanks @samzong.
|
||||
- Gateway/startup: detect PID recycling in gateway lock files on Windows and macOS, and add startup progress so stale lock conflicts no longer block healthy restarts. (#59843) Thanks @TonyDerek-dot.
|
||||
- MS Teams/DM media: download inline images in 1:1 chats via Graph API so Teams DM image attachments stop failing to load. (#52212) Thanks @Ted-developer.
|
||||
- MS Teams/threading: preserve channel reply threading in proactive fallback so replies stay in the original thread instead of dropping into the channel root. (#55198) Thanks @hyojin.
|
||||
- Telegram/media: preserve `<media:...>` placeholders and `file_id` in captioned messages when Bot API downloads fail, so agents still receive media context. (#59948) Thanks @v1p0r.
|
||||
- Telegram/media: keep inbound image attachments readable on upgraded installs where legacy state roots still differ from the managed config-dir media cache. (#59971) Thanks @neeravmakwana.
|
||||
- Telegram/local Bot API: thread `channels.telegram.apiRoot` through buffered reply-media and album downloads so self-hosted Bot API file paths stop falling back to `api.telegram.org` and 404ing. (#59544) Thanks @SARAMALI15792.
|
||||
- Telegram/replies: preserve explicit topic targets when `replyTo` is present while still inheriting the current topic for same-chat replies without an explicit topic. (#59634) Thanks @dashhuang.
|
||||
- Telegram/native commands: clean up metadata-driven progress placeholders when replies fall back, edits fail, or local exec approval prompts are suppressed. (#59300) Thanks @jalehman.
|
||||
- Telegram/models: compare full provider/model refs in the Telegram picker so same-id models from other providers no longer show the wrong current-model checkmark. (#60384) Thanks @sfuminya.
|
||||
- Media/request overrides: resolve shared and capability-filtered media request SecretRefs correctly and expose media transport override fields to schema-driven config consumers. (#59848) Thanks @vincentkoc.
|
||||
- Providers/request overrides: stop advertising unsupported proxy and TLS transport settings on `models.providers.*.request`, and fail closed if unvalidated config tries to route LLM model-provider traffic through dead transport fields. (#59682) Thanks @vincentkoc.
|
||||
- Discord/mentions: treat `@everyone` and `@here` as valid mention-gate triggers in guild preflight so mention-required bots still respond to those broadcasts. (#60343) Thanks @geekhuashan.
|
||||
- Matrix: allow secret-storage recreation during automatic repair bootstrap so clients that lose their recovery key can recover and persist new cross-signing keys. (#59846) Thanks @al3mart.
|
||||
- Matrix/crypto persistence: capture and write the IndexedDB snapshot while holding the snapshot file lock so concurrent gateway and CLI persists cannot overwrite newer crypto state. (#59851) Thanks @al3mart.
|
||||
- Ollama/auth: prefer real cloud auth over local marker during model auth resolution so cloud-backed Ollama auth does not get shadowed by stale local-only markers.
|
||||
- Plugins/Kimi Coding: parse tagged Kimi tool-call text into structured tool calls on the provider stream path so tools execute instead of echoing raw markup. (#60051) Thanks @obviyus.
|
||||
- Channels/passive hooks: emit passive message hooks for mention-skipped Telegram and Signal group messages when `ingest` is enabled, including wildcard/default fallback and per-group override handling. (#60018) Thanks @obviyus.
|
||||
- Providers/compat: stop forcing OpenAI-only payload defaults on proxy and custom OpenAI-compatible routes, and preserve native vendor-specific reasoning, tool, and streaming behavior for Anthropic-compatible, Moonshot, Mistral, ModelStudio, OpenRouter, xAI, Z.ai, and other routed provider paths.
|
||||
- Plugins/manifest registry: stop warning when an explicit manifest `id` intentionally differs from the discovery hint. (#59185) Thanks @samzong.
|
||||
- WhatsApp/streaming: honor `channels.whatsapp.blockStreaming` again for inbound auto-replies so progressive block replies can be enabled explicitly instead of being forced to final-only delivery. Thanks @mcaxtr.
|
||||
- Auth/failover: shorten `auth_permanent` lockouts, add dedicated config knobs for permanent-auth backoff, and downgrade ambiguous auth-ish upstream incidents to retryable auth failures so providers recover automatically after transient outages. (#60404) Thanks @extrasmall0.
|
||||
- Providers/GitHub Copilot: route Claude models through Anthropic Messages with Copilot-compatible headers and Anthropic prompt-cache markers instead of forcing the OpenAI Responses transport.
|
||||
- Plugins/runtime: reuse compatible active registries for `web_search` and `web_fetch` provider snapshot resolution so repeated runtime reads do not re-import the same bundled plugin set on each agent message. Related #48380.
|
||||
- Infra/tailscale: ignore `OPENCLAW_TEST_TAILSCALE_BINARY` outside explicit test environments and block it from workspace `.env`, so test-only binary overrides cannot be injected through trusted repository state. (#58468) Thanks @eleqtrizit.
|
||||
- Plugins/OpenAI: enable reference-image edits for `gpt-image-1` by routing edit calls to `/images/edits` with multipart image uploads, and update image-generation capability/docs metadata accordingly. Thanks @steipete.
|
||||
- Cache/context guard: compact newest tool results first so the cached prompt prefix stays byte-identical and avoids full re-tokenization every turn past the 75% context threshold. (#58036) Thanks @bcherny.
|
||||
- Agents/tools: include value-shape hints in missing-parameter tool errors so dropped, empty-string, and wrong-type write payloads are easier to diagnose from logs. (#55317) Thanks @priyansh19.
|
||||
- Android/assistant: keep queued App Actions prompts pending when auto-send enqueue is rejected, so transient chat-health drops do not silently lose the assistant request. Thanks @obviyus.
|
||||
- Plugins/startup: migrate legacy `tools.web.search.<provider>` config before strict startup validation, and record plugin failure phase/timestamp so degraded plugin startup is easier to diagnose from logs and `plugins list`.
|
||||
- Plugins/Google: separate OAuth CSRF state from PKCE code verifier during Gemini browser sign-in so state validation and token exchange use independent values. (#59116) Thanks @eleqtrizit.
|
||||
- Agents/subagents: honor `agents.defaults.subagents.allowAgents` for `sessions_spawn` and `agents_list`, so default cross-agent allowlists work without duplicating per-agent config. (#59944) Thanks @hclsys.
|
||||
- Agents/tools: normalize only truly empty MCP tool schemas to `{ type: "object", properties: {} }` so OpenAI accepts parameter-free tools without rewriting unrelated conditional schemas. (#60176) Thanks @Bartok9.
|
||||
- Update/npm: prefer the npm binary that owns the installed global OpenClaw prefix during package self-update, so mixed Homebrew-plus-nvm setups update the right install. (#60153) Thanks @jayeshp19.
|
||||
- Plugins/browser: block SSRF redirect bypass by installing a real-time Playwright route handler before `page.goto()` so navigation to private/internal IPs is intercepted and aborted mid-redirect instead of checked post-hoc. (#58771) Thanks @pgondhi987.
|
||||
- Android/gateway: require TLS for non-loopback remote gateway endpoints while still allowing local loopback and emulator cleartext setup flows. (#58475) Thanks @eleqtrizit.
|
||||
- Exec/Windows: hide transient console windows for `runExec` and `runCommandWithTimeout` child-process launches, matching other Windows exec paths and stopping visible shell flashes during tool runs. (#59466) Thanks @lawrence3699.
|
||||
@@ -25,6 +67,41 @@ Docs: https://docs.openclaw.ai
|
||||
- Exec/Windows: prefer strict-inline-eval denial over generic allowlist prompts for interpreter carriers, while keeping persisted Windows allow-always approvals argv-bound. (#59780) Thanks @luoyanglang.
|
||||
- Gateway/connect: omit admin-scoped config and auth metadata from lower-privilege `hello-ok` snapshots while preserving those fields for admin reconnects. (#58469) Thanks @eleqtrizit.
|
||||
- iOS/canvas: restrict A2UI bridge trust to the bundled scaffold and exact capability-backed remote canvas URLs, so generic `canvas.navigate` and `canvas.present` loads no longer gain action-dispatch authority. (#58471) Thanks @eleqtrizit.
|
||||
- Agents/tool policy: preserve restrictive plugin-only allowlists instead of silently widening access to core tools, and keep allowlist warnings aligned with the enforced policy. (#58476) Thanks @eleqtrizit.
|
||||
- Hooks/session_end: preserve deterministic reason metadata for custom reset aliases and overlapping idle-plus-daily rollovers so plugins can rely on lifecycle reason reporting. (#59715) Thanks @jalehman.
|
||||
- Tools/image generation: stop inferring unsupported resolution overrides for OpenAI reference-image edits when no explicit `size` or `resolution` is provided, so default edit flows no longer fail before the provider request is sent.
|
||||
- Agents/sessions: release embedded runner session locks even when teardown cleanup throws, so timed-out or failed cleanup paths no longer leave sessions wedged until the stale-lock watchdog recovers them. (#59194) Thanks @samzong.
|
||||
- Slack/app manifest: add the missing `groups:read` scope to the onboarding and example Slack app manifest so apps copied from the OpenClaw templates can resolve private group conversations reliably.
|
||||
- Mobile pairing/Android: stop generating Tailscale and public mobile setup codes that point at unusable cleartext remote gateways, keep private LAN pairing allowed, and make Android reject insecure remote endpoints with clearer guidance while mixed bootstrap approvals honor operator scopes correctly. (#60128) Thanks @obviyus.
|
||||
- Telegram/media: add `channels.telegram.network.dangerouslyAllowPrivateNetwork` for trusted fake-IP or transparent-proxy environments where Telegram media downloads resolve `api.telegram.org` to private/internal/special-use addresses.
|
||||
- Discord/proxy: keep Carbon REST, monitor startup, and webhook sends on the configured Discord proxy while falling back cleanly when the proxy URL is invalid, so Discord replies and deploys do not hard-fail on malformed proxy config. (#57465) Thanks @geekhuashan.
|
||||
- Discord/components: keep modal-trigger and spoiler-file component messages on the component path when sending media, so classic-message fallback does not silently drop component-only behavior. (#60361) Thanks @geekhuashan.
|
||||
- Mobile pairing/device approval: mint both node and operator device tokens when one approval grants merged roles, so mixed mobile bootstrap pairings stop reconnecting as operator-only and showing the node offline. (#60208) Thanks @obviyus.
|
||||
- Agents/tool policy: stop `tools.profile` warnings from flagging runtime-gated baseline core tools as unknown when the coding profile is missing tools like `code_execution`, `x_search`, `image`, or `image_generate`, while still warning on explicit extra allowlist entries. Thanks @vincentkoc.
|
||||
- Sessions/resolution: collapse alias-duplicate session-id matches before scoring, keep distinct structural ties ambiguous, and prefer current-store reuse when resolving equal cross-store duplicates so follow-up turns stop dropping or duplicating sessions on timestamp ties.
|
||||
- Mobile pairing/bootstrap: keep setup bootstrap tokens alive through the initial node auto-pair so the same QR bootstrap token can finish operator approval, then revoke it after the full issued profile connects successfully. (#60221) Thanks @obviyus.
|
||||
- Plugins/allowlists: let explicit bundled chat channel enablement bypass `plugins.allow`, while keeping auto-enabled channel activation and startup sidecars behind restrictive allowlists. (#60233) Thanks @dorukardahan.
|
||||
- Allowlist/commands: require owner access for `/allowlist add` and `/allowlist remove` so command-authorized non-owners cannot mutate persisted allowlists. (#59836) Thanks @eleqtrizit.
|
||||
- Control UI/skills: clear stale ClawHub results immediately when the search query changes, so debounced searches cannot keep outdated install targets visible. Related #60134.
|
||||
- Fetch/redirects: normalize guarded redirect method rewriting and loop detection so SSRF-guarded requests match platform redirect behavior without missing loops back to the original URL. (#59121) Thanks @eleqtrizit.
|
||||
- Discord/ack reactions: keep automatic ACK reaction auth on the active hydrated Discord account so SecretRef-backed and non-default-account reactions stop falling back to stale default config resolution. (#60081) Thanks @FunJim.
|
||||
- Telegram/model switching: render non-default `/model` callback confirmations with HTML formatting so Telegram shows the selected model in bold instead of raw `**...**` markers. (#60042) Thanks @GitZhangChi.
|
||||
- Plugins/update: allow `openclaw plugins update` to use `--dangerously-force-unsafe-install` for built-in dangerous-code false positives during plugin updates. (#60066) Thanks @huntharo.
|
||||
- Gateway/auth: disconnect shared-auth websocket sessions only for effective auth rotations on restart-capable config writes, and keep `config.set` auth edits from dropping still-valid live sessions. (#60387) Thanks @mappel-nv.
|
||||
- Control UI/chat: keep the Stop button visible during tool-only execution so abortable runs do not fall back to Send while tools are still running. (#54528) thanks @chziyue.
|
||||
- Discord/voice: make READY auto-join fire-and-forget while keeping the shorter initial voice-connect timeout separate from the longer playback-start wait. (#60345) Thanks @geekhuashan.
|
||||
- Agents/skills: add inherited `agents.defaults.skills` allowlists, make per-agent `agents.list[].skills` replace defaults instead of merging, and scope embedded, session, sandbox, and cron skill snapshots through the effective runtime agent. (#59992) Thanks @gumadeiras.
|
||||
- Matrix/Telegram exec approvals: recover stored same-channel account bindings even when session reply state drifted to another channel, so foreign-channel approvals route to the bound account instead of fanning out or being rejected as ambiguous. (#60417) thanks @gumadeiras.
|
||||
- Slack/app manifest: set `bot_user.always_online` to `true` in the onboarding and example Slack app manifest so the Slack app appears ready to respond.
|
||||
- Gateway/websocket auth: refresh auth on new websocket connects after secrets reload so rotated gateway tokens take effect immediately without requiring a restart. (#60323) Thanks @mappel-nv.
|
||||
- Onboarding/plugins: keep non-interactive auth-choice inference scoped to bundled and already-trusted plugins so untrusted workspace manifests cannot hijack built-in provider API-key flows. (#59120) Thanks @eleqtrizit.
|
||||
- Agents/workspace: respect `agents.defaults.workspace` for non-default agents by resolving them under the configured base path instead of falling back to `workspace-<id>`. (#59858) Thanks @joelnishanth.
|
||||
- Config/All Settings: keep the raw config view intact when sensitive fields are blank instead of corrupting or dropping the snapshot during redaction. (#28214) thanks @solodmd.
|
||||
- Plugins/runtime: honor explicit capability allowlists during fallback speech, media-understanding, and image-generation provider loading so bundled capability plugins do not bypass restrictive `plugins.allow` config. (#52262) Thanks @PerfectPan.
|
||||
- Hooks/tool policy: block tool calls when a `before_tool_call` hook crashes so hook failures fail closed instead of silently allowing execution. (#59822) Thanks @pgondhi987.
|
||||
- Matrix/media: surface a dedicated `[matrix <kind> attachment too large]` marker for oversized inbound media instead of the generic unavailable marker, and classify size-limit failures with a typed Matrix error. (#60289) Thanks @efe-arv.
|
||||
- WhatsApp/watchdog: reset watchdog timeout after reconnect so quiet channels no longer enter a tight reconnect loop from stale message timestamps carried across connection runs. (#60007) Thanks @MonkeyLeeT.
|
||||
- Agents/fallback: persist selected fallback overrides before retry attempts start, prefer persisted overrides during live-session reconciliation, and keep provider-scoped auth-profile failover from snapping retries back to stale primary selections.
|
||||
|
||||
## 2026.4.2
|
||||
|
||||
@@ -35,7 +112,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Changes
|
||||
|
||||
- Tasks/Task Flow: restore the core Task Flow substrate with managed-vs-mirrored sync modes, durable flow state/revision tracking, and `openclaw flows` inspection/recovery primitives so background orchestration can persist and be operated separately from plugin authoring layers. (#58930) Thanks @mbelinky.
|
||||
- Tasks/Task Flow: restore the core Task Flow substrate with managed-vs-mirrored sync modes, durable flow state/revision tracking, and `openclaw tasks flow` inspection/recovery primitives so background orchestration can persist and be operated separately from plugin authoring layers. (#58930) Thanks @mbelinky.
|
||||
- Tasks/Task Flow: add managed child task spawning plus sticky cancel intent, so external orchestrators can stop scheduling immediately and let parent Task Flows settle to `cancelled` once active child tasks finish. (#59610) Thanks @mbelinky.
|
||||
- Plugins/Task Flow: add a bound `api.runtime.taskFlow` seam so plugins and trusted authoring layers can create and drive managed Task Flows from host-resolved OpenClaw context without passing owner identifiers on each call. (#59622) Thanks @mbelinky.
|
||||
- Android/assistant: add assistant-role entrypoints plus Google Assistant App Actions metadata so Android can launch OpenClaw from the assistant trigger and hand prompts into the chat composer. (#59596) Thanks @obviyus.
|
||||
@@ -50,10 +127,16 @@ Docs: https://docs.openclaw.ai
|
||||
- Agents/compaction: add `agents.defaults.compaction.notifyUser` so the `🧹 Compacting context...` start notice is opt-in instead of always being shown. (#54251) Thanks @oguricap0327.
|
||||
- WhatsApp/reactions: add `reactionLevel` guidance for agent reactions. Thanks @mcaxtr.
|
||||
- Exec approvals/channels: auto-enable DM-first native chat approvals when supported channels can infer approvers from existing owner config, while keeping channel fanout explicit and clarifying forwarding versus native approval client config.
|
||||
- Android/assistant: auto-send Google Assistant App Actions prompts once chat is healthy and idle, while keeping bare assistant launches as open-only. (#59721) Thanks @obviyus.
|
||||
|
||||
### Fixes
|
||||
|
||||
- Sandbox/security: block credential-path binds even when sandbox home paths resolve through canonical aliases, so agent containers cannot mount user secret stores through alternate home-directory paths. (#59157) Thanks @eleqtrizit.
|
||||
|
||||
## 2026.4.1-beta.1
|
||||
|
||||
- Providers/transport policy: centralize request auth, proxy, TLS, and header shaping across shared HTTP, stream, and websocket paths, block insecure TLS/runtime transport overrides, and keep proxy-hop TLS separate from target mTLS settings. (#59682) Thanks @vincentkoc.
|
||||
- Providers/OpenRouter: gate documented OpenRouter attribution to native OpenRouter endpoints or the default route so custom proxy base URLs do not inherit OpenRouter request headers.
|
||||
- Providers/Copilot: classify native GitHub Copilot API hosts in the shared provider endpoint resolver and harden token-derived proxy endpoint parsing so Copilot base URL routing stays centralized and fails closed on malformed hints. (#59644) Thanks @vincentkoc.
|
||||
- Providers/streaming headers: centralize default and attribution header merging across OpenAI websocket, embedded-runner, and proxy stream paths so provider-specific headers stay consistent and caller overrides only win where intended. (#59542) Thanks @vincentkoc.
|
||||
- Providers/media HTTP: centralize base URL normalization, default auth/header injection, and explicit header override handling across shared OpenAI-compatible audio, Deepgram audio, Gemini media/image, and Moonshot video request paths. (#59469) Thanks @vincentkoc.
|
||||
@@ -102,9 +185,10 @@ Docs: https://docs.openclaw.ai
|
||||
- Telegram/exec approvals: fall back to the origin session key for async approval followups and keep resume-failure status delivery sanitized so Telegram followups still land without leaking raw exec metadata. (#59351) Thanks @seonang.
|
||||
- Node-host/exec approvals: bind `pnpm dlx` invocations through the approval planner's mutable-script path so the effective runtime command is resolved for approval instead of being left unbound. (#58374)
|
||||
- Exec/node hosts: stop forwarding the gateway workspace cwd to remote node exec when no workdir was explicitly requested, so cross-platform node approvals fall back to the node default cwd instead of failing with `SYSTEM_RUN_DENIED`. (#58977) Thanks @Starhappysh.
|
||||
- TUI/chat: keep pending local sends visible and reconciled across history reloads, make busy/error recovery clearer through fallback and terminal-error paths, and reclaim transcript width for long links and paths. (#59800) Thanks @vincentkoc.
|
||||
- Exec approvals/channels: decouple initiating-surface approval availability from native delivery enablement so Telegram, Slack, and Discord still expose approvals when approvers exist and native target routing is configured separately. (#59776) Thanks @joelnishanth.
|
||||
|
||||
## 2026.4.2
|
||||
## 2026.4.1
|
||||
|
||||
### Changes
|
||||
|
||||
@@ -131,62 +215,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Telegram/local Bot API: preserve media MIME types for absolute-path downloads so local audio files still trigger transcription and other MIME-based handling. (#54603) Thanks @jzakirov
|
||||
- Channels/WhatsApp: pass inbound message timestamp to model context so the AI can see when WhatsApp messages were sent. (#58590) Thanks @Maninae
|
||||
- QQBot/voice: lazy-load `silk-wasm` in `audio-convert.ts` so qqbot still starts when the optional voice dependency is missing, while voice encode/decode degrades gracefully instead of crashing at module load time. (#58829) Thanks @WideLee.
|
||||
|
||||
## 2026.4.1
|
||||
|
||||
- Plugins/runtime: stop ambient core helper and setup paths from loading non-selected bundled plugins, keep channel-setup snapshot scoping safe for custom channel plugins, and honor env-scoped plugin auth paths. (#59136) Thanks @vincentkoc.
|
||||
- Matrix/multi-account: keep room-level `account` scoping, inherited room overrides, and implicit account selection consistent across top-level default auth, named accounts, and cached-credential env setups. (#58449) thanks @Daanvdplas and @gumadeiras.
|
||||
- Gateway/pairing: prefer explicit QR bootstrap auth over earlier Tailscale auth classification so iOS `/pair qr` silent bootstrap pairing does not fall through to `pairing required`. (#59232) Thanks @ngutman.
|
||||
- Config/Discord: coerce safe integer numeric Discord IDs to strings during config validation, keep unsafe or precision-losing numeric snowflakes rejected, and align `openclaw doctor` repair guidance with the same fail-closed behavior. (#45125) Thanks @moliendocode.
|
||||
- Gateway/sessions: scope bare `sessions.create` aliases like `main` to the requested agent while preserving the canonical `global` and `unknown` sentinel keys. (#58207) thanks @jalehman.
|
||||
- `/context detail` now compares the tracked prompt estimate with cached context usage and surfaces untracked provider/runtime overhead when present. (#28391) thanks @ImLukeF.
|
||||
- Gateway/session reset: emit the typed `before_reset` hook for gateway `/new` and `/reset`, preserving reset-hook behavior even when the previous transcript has already been archived. (#53872) thanks @VACInc
|
||||
- Plugins/commands: pass the active host `sessionKey` into plugin command contexts, and include `sessionId` when it is already available from the active session entry, so bundled and third-party commands can resolve the current conversation reliably. (#59044) Thanks @jalehman.
|
||||
- Agents/auth: honor `models.providers.*.authHeader` for pi embedded runner model requests by injecting `Authorization: Bearer <apiKey>` when requested. (#54390) Thanks @lndyzwdxhs.
|
||||
- UI/compaction: keep the compaction indicator in a retry-pending state until the run actually finishes, so the UI does not show `Context compacted` before compaction actually finishes. (#55132) Thanks @mpz4life.
|
||||
- Cron/tool schemas: keep cron tool schemas strict-model-friendly while still preserving `failureAlert=false`, nullable `agentId`/`sessionKey`, and flattened add/update recovery for the newly exposed cron job fields. (#55043) Thanks @brunolorente.
|
||||
- BlueBubbles/config: accept `enrichGroupParticipantsFromContacts` in the core strict config schema so gateways no longer fail validation or startup when the BlueBubbles plugin writes that field. (#56889) Thanks @zqchris.
|
||||
- Agents/failover: classify AbortError and stream-abort messages as timeout so Ollama NDJSON stream aborts stop showing `reason=unknown` in model fallback logs. (#58324) Thanks @yelog
|
||||
- Exec approvals: route Slack, Discord, and Telegram approvals through the shared channel approval-capability path so native approval auth, delivery, and `/approve` handling stay aligned across channels while preserving Telegram session-key agent filtering. (#58634) thanks @gumadeiras
|
||||
- Matrix/runtime: resolve the verification/bootstrap runtime from a distinct packaged Matrix entry so global npm installs stop failing on crypto bootstrap with missing-module or recursive runtime alias errors. (#59249) Thanks @gumadeiras.
|
||||
- Matrix/streaming: preserve ordered block flushes before tool, message, and agent boundaries, add explicit `channels.matrix.blockStreaming` opt-in so Matrix `streaming: "off"` stays final-only by default, and move MiniMax plain-text final handling into the MiniMax provider runtime instead of the shared core heuristic. (#59266) thanks @gumadeiras
|
||||
- Agents/compaction: resolve compaction wait before final reply/channel flush completion so slow end-of-run delivery drains no longer delay compaction completion. (#59308) thanks @gumadeiras
|
||||
- Exec approvals: align approval UX, effective-policy reporting, and `allow-always` availability with the host policy so CLI, doctor, and approval surfaces explain the real host-effective decision path. (#59283) Thanks @gumadeiras.
|
||||
- Config/Telegram: migrate removed `channels.telegram.groupMentionsOnly` into `channels.telegram.groups["*"].requireMention` on load so legacy configs no longer crash at startup. (#55336) thanks @jameslcowan.
|
||||
- Ollama/model picker: show only Ollama models after provider selection in the CLI picker. (#55290) Thanks @Luckymingxuan.
|
||||
- MiniMax/plugins: auto-enable the bundled MiniMax plugin for API-key auth/config so MiniMax image generation and other plugin-owned capabilities load without manual plugin allowlisting. (#57127) Thanks @tars90percent.
|
||||
- Plugins/bundled runtimes: restore externalized bundled plugin runtime dependency staging across packed installs, Docker builds, and local runtime staging so bundled plugins keep their declared runtime deps after the 2026.3.31 externalization change. (#58782)
|
||||
- LINE/runtime: resolve the packaged runtime contract from the built `dist/plugins/runtime` layout so LINE channels start correctly again after global npm installs on `2026.3.31`. (#58799) Thanks @vincentkoc.
|
||||
- Tasks/status: hide stale completed background tasks from `/status` and `session_status`, prefer live task context, and show recent failures only when no active work remains. (#58661) Thanks @vincentkoc
|
||||
- Tasks/gateway: keep the task registry maintenance sweep from stalling the gateway event loop under synchronous SQLite pressure, so upgraded gateways stop hanging about a minute after startup. (#58670) Thanks @openperf
|
||||
- Tasks/gateway: re-check the current task record before maintenance marks runs lost or prunes them, so a task heartbeat or cleanup update that lands during a sweep no longer gets overwritten by stale snapshot state.
|
||||
- Subagents/tasks: keep subagent completion and cleanup from crashing when task-registry writes fail, so a corrupt or missing task row no longer takes down the gateway during lifecycle finalization. Thanks @vincentkoc.
|
||||
- Gateway/reload: ignore startup config writes by persisted hash in the config reloader so generated auth tokens and seeded Control UI origins do not trigger a restart loop, while real `gateway.auth.*` edits still require restart. (#58678) Thanks @yelog
|
||||
- Exec/approvals: honor `exec-approvals.json` security defaults when inline or configured tool policy is unset, and keep Slack and Discord native approval handling aligned with inferred approvers and real channel enablement so remote exec stops falling into false approval timeouts and disabled states. Thanks @scoootscooob and @vincentkoc.
|
||||
- Exec/approvals: make `allow-always` persist as durable user-approved trust instead of behaving like `allow-once`, reuse exact-command trust on shell-wrapper paths that cannot safely persist an executable allowlist entry, keep static allowlist entries from silently bypassing `ask:"always"`, and require explicit approval when Windows cannot build an allowlist execution plan instead of hard-dead-ending remote exec. Thanks @scoootscooob and @vincentkoc.
|
||||
- Exec/cron: resolve isolated cron no-route approval dead-ends from the effective host fallback policy when trusted automation is allowed, and make `openclaw doctor` warn when `tools.exec` is broader than `~/.openclaw/exec-approvals.json` so stricter host-policy conflicts are explicit. Thanks @scoootscooob and @vincentkoc.
|
||||
- Gateway/HTTP: skip failing HTTP request stages so one broken facade no longer forces every HTTP endpoint to return 500. (#58746) Thanks @yelog
|
||||
- Gateway/nodes: stop pinning live node commands to the approved node-pair record. Node pairing remains a trust/token flow, while per-node `system.run` policy stays in that node's exec approvals config. Fixes #58824.
|
||||
- WebChat/exec approvals: use native approval UI guidance in agent system prompts instead of telling agents to paste manual `/approve` commands in webchat sessions. Thanks @vincentkoc.
|
||||
- Channels/QQ Bot: keep `/bot-logs` export gated behind a truly explicit QQBot allowlist, rejecting wildcard and mixed wildcard entries while preserving the real framework command path. Thanks @vincentkoc.
|
||||
- Channels/plugins: keep bundled channel plugins loadable from legacy `channels.<id>` config even under restrictive plugin allowlists, and make `openclaw doctor` warn only on real plugin blockers instead of misleading setup guidance. (#58873) Thanks @obviyus
|
||||
- CDP/profiles: prefer `cdpPort` over stale WebSocket URLs so browser automation reconnects cleanly. (#58499) Thanks @Mlightsnow.
|
||||
- Media/paths: resolve relative `MEDIA` paths against the agent workspace so local attachment references keep working. (#58624) Thanks @aquaright1.
|
||||
- Memory/session indexing: keep full reindexes from skipping session transcripts when sync is triggered by `session-start` or `watch`, so restart-driven reindexes preserve session memory. (#39732) Thanks @upupc
|
||||
- Memory/QMD: prefer `--mask` over `--glob` when creating QMD collections so default memory collections keep their intended patterns and stop colliding on restart. (#58643) Thanks @GitZhangChi.
|
||||
- Sandbox/browser: compare browser runtime inspection against `agents.defaults.sandbox.browser.image` so `openclaw sandbox list --browser` stops reporting healthy browser containers as image mismatches. (#58759) Thanks @sandpile.
|
||||
- Plugins/install: forward `--dangerously-force-unsafe-install` through archive and npm-spec plugin installs so the documented override reaches the security scanner on those install paths. (#58879) Thanks @ryanlee-gemini.
|
||||
- Auto-reply/commands: strip inbound metadata before slash command detection so wrapped `/model`, `/new`, and `/status` commands are recognized. (#58725) Thanks @Mlightsnow.
|
||||
- Agents/Anthropic: preserve thinking blocks and signatures across replay, cache-control patching, and context pruning so compacted Anthropic sessions continue working instead of failing on later turns. (#58916) Thanks @obviyus
|
||||
- Agents/Anthropic: recover cleanly after a crash leaves the latest assistant turn with incomplete thinking blocks, dropping or retrying the corrupted turn instead of getting stuck on later Anthropic requests. Thanks @explainanalyze. Maintainer refresh: vincentkoc.
|
||||
- Agents/failover: unify structured and raw provider error classification so provider-specific `400`/`422` payloads no longer get forced into generic format failures before retry, billing, or compaction logic can inspect them. (#58856) Thanks @aaron-he-zhu.
|
||||
- Auth profiles/store: coerce misplaced SecretRef objects out of plaintext `key` and `token` fields during store load so agents without ACP runtime stop crashing on `.trim()` after upgrade. (#58923) Thanks @openperf.
|
||||
- ACPX/runtime: repair `queue owner unavailable` session recovery by replacing dead named sessions and resuming the backend session when ACPX exposes a stable session id, so the first ACP prompt no longer inherits a dead handle. (#58669) Thanks @neeravmakwana
|
||||
- ACPX/runtime: retry dead-session queue-owner repair without `--resume-session` when the reported ACPX session id is stale, so recovery still creates a fresh named session instead of failing session init. Thanks @obviyus.
|
||||
- Auth/OpenAI Codex: persist plugin-refreshed OAuth credentials to `auth-profiles.json` before returning them, so rotated Codex refresh tokens survive restart and stop falling into `refresh_token_reused` loops. (#53082)
|
||||
- Agents/Anthropic: honor explicit `cacheRetention` for custom providers using `anthropic-messages`, so Anthropic-compatible proxy providers can reuse prompt caching when they opt in. (#59049) Thanks @wwerst and @vincentkoc.
|
||||
- Discord/gateway: hand reconnect ownership back to Carbon, keep runtime status aligned with close/reconnect state, and force-stop sockets that open without reaching READY so Discord monitors recover promptly instead of waiting on stale health timeouts. (#59019) Thanks @obviyus
|
||||
- Control UI/build: stop `pnpm ui:build` from reinstalling the UI with production-only dependencies, so fresh self-healing UI builds keep `vite` available instead of failing before asset generation. (#59267) Thanks @juliabush.
|
||||
- WhatsApp/groups: fix bot waking up on self-number quoted replies in groups with `selfChatMode` enabled. (#60148) Thanks @lurebat
|
||||
|
||||
## 2026.3.31
|
||||
|
||||
@@ -935,6 +964,9 @@ Docs: https://docs.openclaw.ai
|
||||
- Feishu/topic threads: fetch full thread context, including prior bot replies, when starting a topic-thread session so follow-up turns in Feishu topics keep the right conversation state. (#45254) Thanks @Coobiw.
|
||||
- Feishu/media: keep native image, file, audio, and video/media handling aligned across outbound sends, inbound downloads, thread replies, directory/action aliases, and capability docs so unsupported areas are explicit instead of implied. (#47968) Thanks @Takhoffman.
|
||||
- Feishu/webhooks: harden signed webhook verification to use constant-time signature comparison and keep malformed short signatures fail-closed in webhook E2E coverage.
|
||||
- Ollama/tool replay: deserialize stringified tool-call arguments on native and OpenAI-compatible Ollama paths while preserving unsafe integer ids across round-trips. (#52253) Thanks @Adam-Researchh.
|
||||
- WhatsApp/reconnect: restore the append recency filter in the extension inbox monitor and handle protobuf `Long` timestamps correctly, so fresh post-reconnect append messages are processed while stale history sync stays suppressed. (#42588) Thanks @MonkeyLeeT.
|
||||
- WhatsApp/login: wait for pending creds writes before reopening after Baileys `515` pairing restarts in both QR login and `channels login` flows, and keep the restart coverage pinned to the real wrapped error shape plus per-account creds queues. (#27910) Thanks @asyncjason.
|
||||
- Telegram/message send: forward `--force-document` through the `sendPayload` path as well as `sendMedia`, so Telegram payload sends with `channelData` keep uploading images as documents instead of silently falling back to compressed photo sends. (#47119) Thanks @thepagent.
|
||||
- Telegram/message chunking: preserve spaces, paragraph separators, and word boundaries when HTML overflow rechunking splits formatted replies. (#47274) Thanks @obviyus.
|
||||
- Z.AI/onboarding: detect a working default model even for explicit `zai-coding-*` endpoint choices, so Coding Plan setup can keep the selected endpoint while defaulting to `glm-5` when available or `glm-4.7` as fallback. (#45969) Thanks @obviyus.
|
||||
|
||||
19
SECURITY.md
19
SECURITY.md
@@ -56,6 +56,7 @@ These are frequently reported but are typically closed with no code change:
|
||||
- Authorized user-triggered local actions presented as privilege escalation. Example: an allowlisted/owner sender running `/export-session /absolute/path.html` to write on the host. In this trust model, authorized user actions are trusted host actions unless you demonstrate an auth/sandbox/boundary bypass.
|
||||
- Reports that only show a malicious plugin executing privileged actions after a trusted operator installs/enables it.
|
||||
- Reports that assume per-user multi-tenant authorization on a shared gateway host/config.
|
||||
- Reports that only show quoted/replied/thread/forwarded supplemental context from non-allowlisted senders being visible to the model, without demonstrating an auth, policy, approval, or sandbox boundary bypass.
|
||||
- Reports that treat the Gateway HTTP compatibility endpoints (`POST /v1/chat/completions`, `POST /v1/responses`) as if they implemented scoped operator auth (`operator.write` vs `operator.admin`). These endpoints authenticate the shared Gateway bearer secret/password and are documented full operator-access surfaces, not per-user/per-scope boundaries.
|
||||
- Reports that assume `x-openclaw-scopes` can reduce or redefine shared-secret bearer auth on the OpenAI-compatible HTTP endpoints. For shared-secret auth (`gateway.auth.mode="token"` or `"password"`), those endpoints ignore narrower bearer-declared scopes and restore the full default operator scope set plus owner semantics.
|
||||
- Reports that treat `POST /tools/invoke` under shared-secret bearer auth (`gateway.auth.mode="token"` or `"password"`) as a narrower per-request/per-scope authorization surface. That endpoint is designed as the same trusted-operator HTTP boundary: shared-secret bearer auth is full operator access there, narrower `x-openclaw-scopes` values do not reduce that path, and owner-only tool policy follows the shared-secret operator contract.
|
||||
@@ -167,6 +168,24 @@ OpenClaw's security model is "personal assistant" (one trusted operator, potenti
|
||||
- For company-shared setups, use a dedicated machine/VM/container and dedicated accounts; avoid mixing personal data on that runtime.
|
||||
- If that host/browser profile is logged into personal accounts (for example Apple/Google/personal password manager), you have collapsed the boundary and increased personal-data exposure risk.
|
||||
|
||||
## Context Visibility and Allowlists
|
||||
|
||||
OpenClaw distinguishes:
|
||||
|
||||
- **Trigger authorization**: who can trigger the agent (`dmPolicy`, `groupPolicy`, allowlists, mention gates)
|
||||
- **Context visibility**: what supplemental context is provided to the model (reply body, quoted text, thread history, forwarded metadata)
|
||||
|
||||
In current releases, allowlists primarily gate triggering and owner-style command access. They do not guarantee universal supplemental-context redaction across every channel/surface.
|
||||
|
||||
Current channel behavior is not fully uniform:
|
||||
|
||||
- some channels already filter parts of supplemental context by sender allowlist
|
||||
- other channels still pass supplemental context as received
|
||||
|
||||
Reports that only show supplemental-context visibility differences are typically hardening/consistency findings unless they also demonstrate a documented boundary bypass (auth, policy, approvals, sandbox, or equivalent).
|
||||
|
||||
Hardening roadmap may add explicit visibility modes (for example `all`, `allowlist`, `allowlist_quote`) so operators can opt into stricter context filtering with predictable tradeoffs.
|
||||
|
||||
## Agent and Model Assumptions
|
||||
|
||||
- The model/agent is **not** a trusted principal. Assume prompt/content injection can manipulate behavior.
|
||||
|
||||
255
appcast.xml
255
appcast.xml
@@ -2,6 +2,120 @@
|
||||
<rss xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle" version="2.0">
|
||||
<channel>
|
||||
<title>OpenClaw</title>
|
||||
<item>
|
||||
<title>2026.4.2</title>
|
||||
<pubDate>Thu, 02 Apr 2026 18:57:54 +0000</pubDate>
|
||||
<link>https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml</link>
|
||||
<sparkle:version>2026040290</sparkle:version>
|
||||
<sparkle:shortVersionString>2026.4.2</sparkle:shortVersionString>
|
||||
<sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion>
|
||||
<description><![CDATA[<h2>OpenClaw 2026.4.2</h2>
|
||||
<h3>Breaking</h3>
|
||||
<ul>
|
||||
<li>Plugins/xAI: move <code>x_search</code> settings from the legacy core <code>tools.web.x_search.*</code> path to the plugin-owned <code>plugins.entries.xai.config.xSearch.*</code> path, standardize <code>x_search</code> auth on <code>plugins.entries.xai.config.webSearch.apiKey</code> / <code>XAI_API_KEY</code>, and migrate legacy config with <code>openclaw doctor --fix</code>. (#59674) Thanks @vincentkoc.</li>
|
||||
<li>Plugins/web fetch: move Firecrawl <code>web_fetch</code> config from the legacy core <code>tools.web.fetch.firecrawl.*</code> path to the plugin-owned <code>plugins.entries.firecrawl.config.webFetch.*</code> path, route <code>web_fetch</code> fallback through the new fetch-provider boundary instead of a Firecrawl-only core branch, and migrate legacy config with <code>openclaw doctor --fix</code>. (#59465) Thanks @vincentkoc.</li>
|
||||
</ul>
|
||||
<h3>Changes</h3>
|
||||
<ul>
|
||||
<li>Tasks/Task Flow: restore the core Task Flow substrate with managed-vs-mirrored sync modes, durable flow state/revision tracking, and <code>openclaw flows</code> inspection/recovery primitives so background orchestration can persist and be operated separately from plugin authoring layers. (#58930) Thanks @mbelinky.</li>
|
||||
<li>Tasks/Task Flow: add managed child task spawning plus sticky cancel intent, so external orchestrators can stop scheduling immediately and let parent Task Flows settle to <code>cancelled</code> once active child tasks finish. (#59610) Thanks @mbelinky.</li>
|
||||
<li>Plugins/Task Flow: add a bound <code>api.runtime.taskFlow</code> seam so plugins and trusted authoring layers can create and drive managed Task Flows from host-resolved OpenClaw context without passing owner identifiers on each call. (#59622) Thanks @mbelinky.</li>
|
||||
<li>Android/assistant: add assistant-role entrypoints plus Google Assistant App Actions metadata so Android can launch OpenClaw from the assistant trigger and hand prompts into the chat composer. (#59596) Thanks @obviyus.</li>
|
||||
<li>Exec defaults: make gateway/node host exec default to YOLO mode by requesting <code>security=full</code> with <code>ask=off</code>, and align host approval-file fallbacks plus docs/doctor reporting with that no-prompt default.</li>
|
||||
<li>Providers/runtime: add provider-owned replay hook surfaces for transcript policy, replay cleanup, and reasoning-mode dispatch. (#59143) Thanks @jalehman.</li>
|
||||
<li>Plugins/hooks: add <code>before_agent_reply</code> so plugins can short-circuit the LLM with synthetic replies after inline actions. (#20067) Thanks @JoshuaLelon.</li>
|
||||
<li>Channels/session routing: move provider-specific session conversation grammar into plugin-owned session-key surfaces, preserving Telegram topic routing and Feishu scoped inheritance across bootstrap, model override, restart, and tool-policy paths.</li>
|
||||
<li>Feishu/comments: add a dedicated Drive comment-event flow with comment-thread context resolution, in-thread replies, and <code>feishu_drive</code> comment actions for document collaboration workflows. (#58497) Thanks @wittam-01.</li>
|
||||
<li>Matrix/plugin: emit spec-compliant <code>m.mentions</code> metadata across text sends, media captions, edits, poll fallback text, and action-driven edits so Matrix mentions notify reliably in clients like Element. (#59323) Thanks @gumadeiras.</li>
|
||||
<li>Diffs: add plugin-owned <code>viewerBaseUrl</code> so viewer links can use a stable proxy/public origin without passing <code>baseUrl</code> on every tool call. (#59341) Related #59227. Thanks @gumadeiras.</li>
|
||||
<li>Agents/compaction: resolve <code>agents.defaults.compaction.model</code> consistently for manual <code>/compact</code> and other context-engine compaction paths, so engine-owned compaction uses the configured override model across runtime entrypoints. (#56710) Thanks @oliviareid-svg.</li>
|
||||
<li>Agents/compaction: add <code>agents.defaults.compaction.notifyUser</code> so the <code>🧹 Compacting context...</code> start notice is opt-in instead of always being shown. (#54251) Thanks @oguricap0327.</li>
|
||||
<li>WhatsApp/reactions: add <code>reactionLevel</code> guidance for agent reactions. Thanks @mcaxtr.</li>
|
||||
<li>Exec approvals/channels: auto-enable DM-first native chat approvals when supported channels can infer approvers from existing owner config, while keeping channel fanout explicit and clarifying forwarding versus native approval client config.</li>
|
||||
</ul>
|
||||
<h3>Fixes</h3>
|
||||
<ul>
|
||||
<li>Providers/transport policy: centralize request auth, proxy, TLS, and header shaping across shared HTTP, stream, and websocket paths, block insecure TLS/runtime transport overrides, and keep proxy-hop TLS separate from target mTLS settings. (#59682) Thanks @vincentkoc.</li>
|
||||
<li>Providers/Copilot: classify native GitHub Copilot API hosts in the shared provider endpoint resolver and harden token-derived proxy endpoint parsing so Copilot base URL routing stays centralized and fails closed on malformed hints. (#59644) Thanks @vincentkoc.</li>
|
||||
<li>Providers/streaming headers: centralize default and attribution header merging across OpenAI websocket, embedded-runner, and proxy stream paths so provider-specific headers stay consistent and caller overrides only win where intended. (#59542) Thanks @vincentkoc.</li>
|
||||
<li>Providers/media HTTP: centralize base URL normalization, default auth/header injection, and explicit header override handling across shared OpenAI-compatible audio, Deepgram audio, Gemini media/image, and Moonshot video request paths. (#59469) Thanks @vincentkoc.</li>
|
||||
<li>Providers/OpenAI-compatible routing: centralize native-vs-proxy request policy so hidden attribution and related OpenAI-family defaults only apply on verified native endpoints across stream, websocket, and shared audio HTTP paths. (#59433) Thanks @vincentkoc.</li>
|
||||
<li>Providers/Anthropic routing: centralize native-vs-proxy endpoint classification for direct Anthropic <code>service_tier</code> handling so spoofed or proxied hosts do not inherit native Anthropic defaults. (#59608) Thanks @vincentkoc.</li>
|
||||
<li>Gateway/exec loopback: restore legacy-role fallback for empty paired-device token maps and allow silent local role upgrades so local exec and node clients stop failing with pairing-required errors after <code>2026.3.31</code>. (#59092) Thanks @openperf.</li>
|
||||
<li>Agents/subagents: pin admin-only subagent gateway calls to <code>operator.admin</code> while keeping <code>agent</code> at least privilege, so <code>sessions_spawn</code> no longer dies on loopback scope-upgrade pairing with <code>close(1008) "pairing required"</code>. (#59555) Thanks @openperf.</li>
|
||||
<li>Exec approvals/config: strip invalid <code>security</code>, <code>ask</code>, and <code>askFallback</code> values from <code>~/.openclaw/exec-approvals.json</code> during normalization so malformed policy enums fall back cleanly to the documented defaults instead of corrupting runtime policy resolution. (#59112) Thanks @openperf.</li>
|
||||
<li>Exec approvals/doctor: report host policy sources from the real approvals file path and ignore malformed host override values when attributing effective policy conflicts. (#59367) Thanks @gumadeiras.</li>
|
||||
<li>Exec/runtime: treat <code>tools.exec.host=auto</code> as routing-only, keep implicit no-config exec on sandbox when available or gateway otherwise, and reject per-call host overrides that would bypass the configured sandbox or host target. (#58897) Thanks @vincentkoc.</li>
|
||||
<li>Slack/mrkdwn formatting: add built-in Slack mrkdwn guidance in inbound context so Slack replies stop falling back to generic Markdown patterns that render poorly in Slack. (#59100) Thanks @jadewon.</li>
|
||||
<li>WhatsApp/presence: send <code>unavailable</code> presence on connect in self-chat mode so personal-phone users stop losing all push notifications while the gateway is running. (#59410) Thanks @mcaxtr.</li>
|
||||
<li>WhatsApp/media: add HTML, XML, and CSS to the MIME map and fall back gracefully for unknown media types instead of dropping the attachment. (#51562) Thanks @bobbyt74.</li>
|
||||
<li>Matrix/onboarding: restore guided setup in <code>openclaw channels add</code> and <code>openclaw configure --section channels</code>, while keeping custom plugin wizards on the shared <code>setupWizard</code> seam. (#59462) Thanks @gumadeiras.</li>
|
||||
<li>Matrix/streaming: keep live partial previews for the current assistant block while preserving completed block updates as separate messages when <code>channels.matrix.blockStreaming</code> is enabled. (#59384) Thanks @gumadeiras.</li>
|
||||
<li>Feishu/comment threads: harden document comment-thread delivery so whole-document comments fall back to <code>add_comment</code>, delayed reply lookups retry more reliably, and user-visible replies avoid reasoning/planning spillover. (#59129) Thanks @wittam-01.</li>
|
||||
<li>MS Teams/streaming: strip already-streamed text from fallback block delivery when replies exceed the 4000-character streaming limit so long responses stop duplicating content. (#59297) Thanks @bradgroux.</li>
|
||||
<li>Slack/thread context: filter thread starter and history by the effective conversation allowlist without dropping valid open-room, DM, or group DM context. (#58380) Thanks @jacobtomlinson.</li>
|
||||
<li>Mattermost/probes: route status probes through the SSRF guard and honor <code>allowPrivateNetwork</code> so connectivity checks stay safe for self-hosted Mattermost deployments. (#58529) Thanks @mappel-nv.</li>
|
||||
<li>Zalo/webhook replay: scope replay dedupe key by chat and sender so reused message IDs across different chats or senders no longer collide, and harden metadata reads for partially missing payloads. (#58444)</li>
|
||||
<li>QQBot/structured payloads: restrict local file paths to QQ Bot-owned media storage, block traversal outside that root, reduce path leakage in logs, and keep inline image data URLs working. (#58453) Thanks @jacobtomlinson.</li>
|
||||
<li>Image generation/providers: route OpenAI, MiniMax, and fal image requests through the shared provider HTTP transport path so custom base URLs, guarded private-network routing, and provider request defaults stay aligned with the rest of provider HTTP. Thanks @vincentkoc.</li>
|
||||
<li>Image generation/providers: stop inferring private-network access from configured OpenAI, MiniMax, and fal image base URLs, and cap shared HTTP error-body reads so hostile or misconfigured endpoints fail closed without relaxing SSRF policy or buffering unbounded error payloads. Thanks @vincentkoc.</li>
|
||||
<li>Browser/host inspection: keep static Chrome inspection helpers out of the activated browser runtime so <code>openclaw doctor browser</code> and related checks do not eagerly load the bundled browser plugin. (#59471) Thanks @vincentkoc.</li>
|
||||
<li>Browser/CDP: normalize trailing-dot localhost absolute-form hosts before loopback checks so remote CDP websocket URLs like <code>ws://localhost.:...</code> rewrite back to the configured remote host. (#59236) Thanks @mappel-nv.</li>
|
||||
<li>Agents/output sanitization: strip namespaced <code>antml:thinking</code> blocks from user-visible text so Anthropic-style internal monologue tags do not leak into replies. (#59550) Thanks @obviyus.</li>
|
||||
<li>Kimi Coding/tools: normalize Anthropic tool payloads into the OpenAI-compatible function shape Kimi Coding expects so tool calls stop losing required arguments. (#59440) Thanks @obviyus.</li>
|
||||
<li>Image tool/paths: resolve relative local media paths against the agent <code>workspaceDir</code> instead of <code>process.cwd()</code> so inputs like <code>inbox/receipt.png</code> pass the local-path allowlist reliably. (#57222) Thanks Priyansh Gupta.</li>
|
||||
<li>Podman/launch: remove noisy container output from <code>scripts/run-openclaw-podman.sh</code> and align the Podman install guidance with the quieter startup flow. (#59368) Thanks @sallyom.</li>
|
||||
<li>Plugins/runtime: keep LINE reply directives and browser-backed cleanup/reset flows working even when those plugins are disabled while tightening bundled plugin activation guards. (#59412) Thanks @vincentkoc.</li>
|
||||
<li>ACP/gateway reconnects: keep ACP prompts alive across transient websocket drops while still failing boundedly when reconnect recovery does not complete. (#59473) Thanks @obviyus.</li>
|
||||
<li>ACP/gateway reconnects: reject stale pre-ack ACP prompts after reconnect grace expiry so callers fail cleanly instead of hanging indefinitely when the gateway never confirms the run.</li>
|
||||
<li>Gateway/session kill: enforce HTTP operator scopes on session kill requests and gate authorization before session lookup so unauthenticated callers cannot probe session existence. (#59128) Thanks @jacobtomlinson.</li>
|
||||
<li>MS Teams/logging: format non-<code>Error</code> failures with the shared unknown-error helper so logs stop collapsing caught SDK or Axios objects into <code>[object Object]</code>. (#59321) Thanks @bradgroux.</li>
|
||||
<li>Channels/setup: ignore untrusted workspace channel plugins during setup resolution so a shadowing workspace plugin cannot override built-in channel setup/login flows unless explicitly trusted in config. (#59158) Thanks @mappel-nv.</li>
|
||||
<li>Exec/Windows: restore allowlist enforcement with quote-aware <code>argPattern</code> matching across gateway and node exec, and surface accurate dynamic pre-approved executable hints in the exec tool description. (#56285) Thanks @kpngr.</li>
|
||||
<li>Gateway: prune empty <code>node-pending-work</code> state entries after explicit acknowledgments and natural expiry so the per-node state map no longer grows indefinitely. (#58179) Thanks @gavyngong.</li>
|
||||
<li>Webhooks/secret comparison: replace ad-hoc timing-safe secret comparisons across BlueBubbles, Feishu, Mattermost, Telegram, Twilio, and Zalo webhook handlers with the shared <code>safeEqualSecret</code> helper and reject empty auth tokens in BlueBubbles. (#58432) Thanks @eleqtrizit.</li>
|
||||
<li>OpenShell/mirror: constrain <code>remoteWorkspaceDir</code> and <code>remoteAgentWorkspaceDir</code> to the managed <code>/sandbox</code> and <code>/agent</code> roots, and keep mirror sync from overwriting or removing user-added shell roots during config synchronization. (#58515) Thanks @eleqtrizit.</li>
|
||||
<li>Plugins/activation: preserve explicit, auto-enabled, and default activation provenance plus reason metadata across CLI, gateway bootstrap, and status surfaces so plugin enablement state stays accurate after auto-enable resolution. (#59641) Thanks @vincentkoc.</li>
|
||||
<li>Exec/env: block additional host environment override pivots for package roots, language runtimes, compiler include paths, and credential/config locations so request-scoped exec cannot redirect trusted toolchains or config lookups. (#59233) Thanks @drobison00.</li>
|
||||
<li>Dotenv/workspace overrides: block workspace <code>.env</code> files from overriding <code>OPENCLAW_PINNED_PYTHON</code> and <code>OPENCLAW_PINNED_WRITE_PYTHON</code> so trusted helper interpreters cannot be redirected by repo-local env injection. (#58473) Thanks @eleqtrizit.</li>
|
||||
<li>Plugins/install: accept JSON5 syntax in <code>openclaw.plugin.json</code> and bundle <code>plugin.json</code> manifests during install/validation, so third-party plugins with trailing commas, comments, or unquoted keys no longer fail to install. (#59084) Thanks @singleGanghood.</li>
|
||||
<li>Telegram/exec approvals: rewrite shared <code>/approve … allow-always</code> callback payloads to <code>/approve … always</code> before Telegram button rendering so plugin approval IDs still fit Telegram's <code>callback_data</code> limit and keep the Allow Always action visible. (#59217) Thanks @jameslcowan.</li>
|
||||
<li>Cron/exec timeouts: surface timed-out <code>exec</code> and <code>bash</code> failures in isolated cron runs even when <code>verbose: off</code>, including custom session-target cron jobs, so scheduled runs stop failing silently. (#58247) Thanks @skainguyen1412.</li>
|
||||
<li>Telegram/exec approvals: fall back to the origin session key for async approval followups and keep resume-failure status delivery sanitized so Telegram followups still land without leaking raw exec metadata. (#59351) Thanks @seonang.</li>
|
||||
<li>Node-host/exec approvals: bind <code>pnpm dlx</code> invocations through the approval planner's mutable-script path so the effective runtime command is resolved for approval instead of being left unbound. (#58374)</li>
|
||||
<li>Exec/node hosts: stop forwarding the gateway workspace cwd to remote node exec when no workdir was explicitly requested, so cross-platform node approvals fall back to the node default cwd instead of failing with <code>SYSTEM_RUN_DENIED</code>. (#58977) Thanks @Starhappysh.</li>
|
||||
<li>Exec approvals/channels: decouple initiating-surface approval availability from native delivery enablement so Telegram, Slack, and Discord still expose approvals when approvers exist and native target routing is configured separately. (#59776) Thanks @joelnishanth.</li>
|
||||
</ul>
|
||||
<h3>Changes</h3>
|
||||
<ul>
|
||||
<li>macOS/Voice Wake: add the Voice Wake option to trigger Talk Mode. (#58490) Thanks @SmoothExec.</li>
|
||||
<li>Tasks/chat: add <code>/tasks</code> as a chat-native background task board for the current session, with recent task details and agent-local fallback counts when no linked tasks are visible. Related #54226. Thanks @vincentkoc.</li>
|
||||
<li>Web search/SearXNG: add the bundled SearXNG provider plugin for <code>web_search</code> with configurable host support. (#57317) Thanks @cgdusek.</li>
|
||||
<li>Telegram/errors: add configurable <code>errorPolicy</code> and <code>errorCooldownMs</code> controls so Telegram can suppress repeated delivery errors per account, chat, and topic without muting distinct failures. (#51914) Thanks @chinar-amrutkar</li>
|
||||
<li>Gateway/webchat: make <code>chat.history</code> text truncation configurable with <code>gateway.webchat.chatHistoryMaxChars</code> and per-request <code>maxChars</code>, while preserving silent-reply filtering and existing default payload limits. (#58900)</li>
|
||||
<li>Amazon Bedrock/Guardrails: add Bedrock Guardrails support to the bundled provider. (#58588) Thanks @MikeORed.</li>
|
||||
<li>ZAI/models: add <code>glm-5.1</code> and <code>glm-5v-turbo</code> to the bundled Z.AI provider catalog. (#58793) Thanks @tomsun28</li>
|
||||
<li>Agents/default params: add <code>agents.defaults.params</code> for global default provider parameters. (#58548) Thanks @lpender.</li>
|
||||
<li>Agents/failover: cap prompt-side and assistant-side same-provider auth-profile retries for rate-limit failures before cross-provider model fallback, add the <code>auth.cooldowns.rateLimitedProfileRotations</code> knob, and document the new fallback behavior. (#58707) Thanks @Forgely3D</li>
|
||||
<li>Agents/compaction: resolve <code>agents.defaults.compaction.model</code> consistently for manual <code>/compact</code> and other context-engine compaction paths, so engine-owned compaction uses the configured override model across runtime entrypoints. (#56710) Thanks @oliviareid-svg</li>
|
||||
<li>Cron/tools allowlist: add <code>openclaw cron --tools</code> for per-job tool allowlists. (#58504) Thanks @andyk-ms.</li>
|
||||
</ul>
|
||||
<h3>Fixes</h3>
|
||||
<ul>
|
||||
<li>Chat/error replies: stop leaking raw provider/runtime failures into external chat channels, return a friendly retry message instead, and add a specific <code>/new</code> hint for Bedrock toolResult/toolUse session mismatches. (#58831) Thanks @ImLukeF.</li>
|
||||
<li>Sessions/model switching: keep <code>/model</code> changes queued behind busy runs instead of interrupting the active turn, and retarget queued followups so later work picks up the new model as soon as the current turn finishes.</li>
|
||||
<li>Web UI/OpenResponses: preserve rewritten stream snapshots in webchat and keep OpenResponses final streamed text aligned when models rewind earlier output. (#58641) Thanks @neeravmakwana</li>
|
||||
<li>Discord/inbound media: pass Discord attachment and sticker downloads through the shared idle-timeout and worker-abort path so slow or stuck inbound media fetches stop hanging message processing. (#58593) Thanks @aquaright1</li>
|
||||
<li>Telegram/retries: keep non-idempotent sends on the strict safe-send path, retry wrapped pre-connect failures, and preserve <code>429</code> / <code>retry_after</code> backoff for safe delivery retries. (#51895) Thanks @chinar-amrutkar</li>
|
||||
<li>Telegram/exec approvals: route topic-aware exec approval followups through Telegram-owned threading and approval-target parsing, so forum-topic approvals stay in the originating topic instead of falling back to the root chat. (#58783)</li>
|
||||
<li>Telegram/local Bot API: preserve media MIME types for absolute-path downloads so local audio files still trigger transcription and other MIME-based handling. (#54603) Thanks @jzakirov</li>
|
||||
<li>Channels/WhatsApp: pass inbound message timestamp to model context so the AI can see when WhatsApp messages were sent. (#58590) Thanks @Maninae</li>
|
||||
<li>QQBot/voice: lazy-load <code>silk-wasm</code> in <code>audio-convert.ts</code> so qqbot still starts when the optional voice dependency is missing, while voice encode/decode degrades gracefully instead of crashing at module load time. (#58829) Thanks @WideLee.</li>
|
||||
</ul>
|
||||
<p><a href="https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md">View full changelog</a></p>
|
||||
]]></description>
|
||||
<enclosure url="https://github.com/openclaw/openclaw/releases/download/v2026.4.2/OpenClaw-2026.4.2.zip" length="25843797" type="application/octet-stream" sparkle:edSignature="bNNXr4BJEU8W7ghXOujLJTYHZL2PL/r/p4llGBw0BFL+46mJ2Bir+IK8XQaCj5zp+O5JSuh5mY+Y/Nrq6TR7Cg=="/>
|
||||
</item>
|
||||
<item>
|
||||
<title>2026.4.1</title>
|
||||
<pubDate>Wed, 01 Apr 2026 17:14:12 +0000</pubDate>
|
||||
@@ -189,146 +303,5 @@
|
||||
]]></description>
|
||||
<enclosure url="https://github.com/openclaw/openclaw/releases/download/v2026.3.31/OpenClaw-2026.3.31.zip" length="25820093" type="application/octet-stream" sparkle:edSignature="NjpuH/j7OaNASEatBTpQ4uQy6+oUNq/lIwjrY69rJfkgGSk3/kU8vgxo9osjSgx034m7TpuZvWyulu57OBsQCg=="/>
|
||||
</item>
|
||||
<item>
|
||||
<title>2026.3.28</title>
|
||||
<pubDate>Sun, 29 Mar 2026 02:10:40 +0000</pubDate>
|
||||
<link>https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml</link>
|
||||
<sparkle:version>2026032890</sparkle:version>
|
||||
<sparkle:shortVersionString>2026.3.28</sparkle:shortVersionString>
|
||||
<sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion>
|
||||
<description><![CDATA[<h2>OpenClaw 2026.3.28</h2>
|
||||
<h3>Breaking</h3>
|
||||
<ul>
|
||||
<li>Providers/Qwen: remove the deprecated <code>qwen-portal-auth</code> OAuth integration for <code>portal.qwen.ai</code>; migrate to Model Studio with <code>openclaw onboard --auth-choice modelstudio-api-key</code>. (#52709) Thanks @pomelo-nwu.</li>
|
||||
<li>Config/Doctor: drop automatic config migrations older than two months; very old legacy keys now fail validation instead of being rewritten on load or by <code>openclaw doctor</code>.</li>
|
||||
</ul>
|
||||
<h3>Changes</h3>
|
||||
<ul>
|
||||
<li>xAI/tools: move the bundled xAI provider to the Responses API, add first-class <code>x_search</code>, and auto-enable the xAI plugin from owned web-search and tool config so bundled Grok auth/configured search flows work without manual plugin toggles. (#56048) Thanks @huntharo.</li>
|
||||
<li>xAI/onboarding: let the bundled Grok web-search plugin offer optional <code>x_search</code> setup during <code>openclaw onboard</code> and <code>openclaw configure --section web</code>, including an x_search model picker with the shared xAI key.</li>
|
||||
<li>MiniMax: add image generation provider for <code>image-01</code> model, supporting generate and image-to-image editing with aspect ratio control. (#54487) Thanks @liyuan97.</li>
|
||||
<li>Plugins/hooks: add async <code>requireApproval</code> to <code>before_tool_call</code> hooks, letting plugins pause tool execution and prompt the user for approval via the exec approval overlay, Telegram buttons, Discord interactions, or the <code>/approve</code> command on any channel. The <code>/approve</code> command now handles both exec and plugin approvals with automatic fallback. (#55339) Thanks @vaclavbelak and @joshavant.</li>
|
||||
<li>ACP/channels: add current-conversation ACP binds for Discord, BlueBubbles, and iMessage so <code>/acp spawn codex --bind here</code> can turn the current chat into a Codex-backed workspace without creating a child thread, and document the distinction between chat surface, ACP session, and runtime workspace.</li>
|
||||
<li>OpenAI/apply_patch: enable <code>apply_patch</code> by default for OpenAI and OpenAI Codex models, and align its sandbox policy access with <code>write</code> permissions.</li>
|
||||
<li>Plugins/CLI backends: move bundled Claude CLI, Codex CLI, and Gemini CLI inference defaults onto the plugin surface, add bundled Gemini CLI backend support, and replace <code>gateway run --claude-cli-logs</code> with generic <code>--cli-backend-logs</code> while keeping the old flag as a compatibility alias.</li>
|
||||
<li>Plugins/startup: auto-load bundled provider and CLI-backend plugins from explicit config refs, so bundled Claude CLI, Codex CLI, and Gemini CLI message-provider setups no longer need manual <code>plugins.allow</code> entries.</li>
|
||||
<li>Podman: simplify the container setup around the current rootless user, install the launch helper under <code>~/.local/bin</code>, and document the host-CLI <code>openclaw --container <name> ...</code> workflow instead of a dedicated <code>openclaw</code> service user.</li>
|
||||
<li>Slack/tool actions: add an explicit <code>upload-file</code> Slack action that routes file uploads through the existing Slack upload transport, with optional filename/title/comment overrides for channels and DMs.</li>
|
||||
<li>Message actions/files: start unifying file-first sends on the canonical <code>upload-file</code> action by adding explicit support for Microsoft Teams and Google Chat, and by exposing BlueBubbles file sends through <code>upload-file</code> while keeping the legacy <code>sendAttachment</code> alias.</li>
|
||||
<li>Plugins/Matrix TTS: send auto-TTS replies as native Matrix voice bubbles instead of generic audio attachments. (#37080) thanks @Matthew19990919.</li>
|
||||
<li>CLI: add <code>openclaw config schema</code> to print the generated JSON schema for <code>openclaw.json</code>. (#54523) Thanks @kvokka.</li>
|
||||
<li>Config/TTS: auto-migrate legacy speech config on normal reads and secret resolution, keep legacy diagnostics for Doctor, and remove regular-mode runtime fallback for old bundled <code>tts.<provider></code> API-key shapes.</li>
|
||||
<li>Memory/plugins: move the pre-compaction memory flush plan behind the active memory plugin contract so <code>memory-core</code> owns flush prompts and target-path policy instead of hardcoded core logic.</li>
|
||||
<li>MiniMax: trim model catalog to M2.7 only, removing legacy M2, M2.1, M2.5, and VL-01 models. (#54487) Thanks @liyuan97.</li>
|
||||
<li>Plugins/runtime: expose <code>runHeartbeatOnce</code> in the plugin runtime <code>system</code> namespace so plugins can trigger a single heartbeat cycle with an explicit delivery target override (e.g. <code>heartbeat: { target: "last" }</code>). (#40299) Thanks @loveyana.</li>
|
||||
<li>Agents/compaction: preserve the post-compaction AGENTS refresh on stale-usage preflight compaction for both immediate replies and queued followups. (#49479) Thanks @jared596.</li>
|
||||
<li>Agents/compaction: surface safeguard-specific cancel reasons and relabel benign manual <code>/compact</code> no-op cases as skipped instead of failed. (#51072) Thanks @afurm.</li>
|
||||
<li>Docs: add <code>pnpm docs:check-links:anchors</code> for Mintlify anchor validation while keeping <code>scripts/docs-link-audit.mjs</code> as the stable link-audit entrypoint. (#55912) Thanks @velvet-shark.</li>
|
||||
<li>Tavily: mark outbound API requests with <code>X-Client-Source: openclaw</code> so Tavily can attribute OpenClaw-originated traffic. (#55335) Thanks @lakshyaag-tavily.</li>
|
||||
</ul>
|
||||
<h3>Fixes</h3>
|
||||
<ul>
|
||||
<li>Agents/Anthropic: recover unhandled provider stop reasons (e.g. <code>sensitive</code>) as structured assistant errors instead of crashing the agent run. (#56639)</li>
|
||||
<li>Google/models: resolve Gemini 3.1 pro, flash, and flash-lite for all Google provider aliases by passing the actual runtime provider ID and adding a template-provider fallback; fix flash-lite prefix ordering. (#56567)</li>
|
||||
<li>OpenAI Codex/image tools: register Codex for media understanding and route image prompts through Codex instructions so image analysis no longer fails on missing provider registration or missing <code>instructions</code>. (#54829) Thanks @neeravmakwana.</li>
|
||||
<li>Agents/image tool: restore the generic image-runtime fallback when no provider-specific media-understanding provider is registered, so image analysis works again for providers like <code>openrouter</code> and <code>minimax-portal</code>. (#54858) Thanks @MonkeyLeeT.</li>
|
||||
<li>WhatsApp: fix infinite echo loop in self-chat DM mode where the bot's own outbound replies were re-processed as new inbound user messages. (#54570) Thanks @joelnishanth</li>
|
||||
<li>Telegram/splitting: replace proportional text estimate with verified HTML-length search so long messages split at word boundaries instead of mid-word; gracefully degrade when tag overhead exceeds the limit. (#56595)</li>
|
||||
<li>Telegram/delivery: skip whitespace-only and hook-blanked text replies in bot delivery to prevent GrammyError 400 empty-text crashes. (#56620)</li>
|
||||
<li>Telegram/send: validate <code>replyToMessageId</code> at all four API sinks with a shared normalizer that rejects non-numeric, NaN, and mixed-content strings. (#56587)</li>
|
||||
<li>Mistral: normalize OpenAI-compatible request flags so official Mistral API runs no longer fail with remaining <code>422 status code (no body)</code> chat errors.</li>
|
||||
<li>Control UI/config: keep sensitive raw config hidden by default, replace the blank blocked editor with an explicit reveal-to-edit state, and restore raw JSON editing without auto-exposing secrets. Fixes #55322.</li>
|
||||
<li>CLI/zsh: defer <code>compdef</code> registration until <code>compinit</code> is available so zsh completion loads cleanly with plugin managers and manual setups. (#56555)</li>
|
||||
<li>BlueBubbles/debounce: guard debounce flush against null message text by sanitizing at the enqueue boundary and adding an independent combiner guard. (#56573)</li>
|
||||
<li>Auto-reply: suppress JSON-wrapped <code>{"action":"NO_REPLY"}</code> control envelopes before channel delivery with a strict single-key detector; preserves media when text is only a silent envelope. (#56612)</li>
|
||||
<li>ACP/ACPX agent registry: align OpenClaw's ACPX built-in agent mirror with the latest <code>openclaw/acpx</code> command defaults and built-in aliases, pin versioned <code>npx</code> built-ins to exact versions, and stop unknown ACP agent ids from falling through to raw <code>--agent</code> command execution on the MCP-proxy path. (#28321) Thanks @m0nkmaster and @vincentkoc.</li>
|
||||
<li>Security/audit: extend web search key audit to recognize Gemini, Grok/xAI, Kimi, Moonshot, and OpenRouter credentials via a boundary-safe bundled-web-search registry shim. (#56540)</li>
|
||||
<li>Docs/FAQ: remove broken Xfinity SSL troubleshooting cross-links from English and zh-CN FAQ entries — both sections already contain the full workaround inline. (#56500)</li>
|
||||
<li>Telegram: deliver verbose tool summaries inside forum topic sessions again, so threaded topic chats now match DM verbose behavior. (#43236) Thanks @frankbuild.</li>
|
||||
<li>BlueBubbles/CLI agents: restore inbound prompt image refs for CLI routed turns, reapply embedded runner image size guardrails, and cover both CLI image transport paths with regression tests. (#51373)</li>
|
||||
<li>BlueBubbles/groups: optionally enrich unnamed participant lists with local macOS Contacts names after group gating passes, so group member context can show names instead of only raw phone numbers.</li>
|
||||
<li>Discord/reconnect: drain stale gateway sockets, clear cached resume state before forced fresh reconnects, and fail closed when old sockets refuse to die so Discord recovery stops looping on poisoned resume state. (#54697) Thanks @ngutman.</li>
|
||||
<li>iMessage: stop leaking inline <code>[[reply_to:...]]</code> tags into delivered text by sending <code>reply_to</code> as RPC metadata and stripping stray directive tags from outbound messages. (#39512) Thanks @mvanhorn.</li>
|
||||
<li>CLI/plugins: make routed commands use the same auto-enabled bundled-channel snapshot as gateway startup, so configured bundled channels like Slack load without requiring a prior config rewrite. (#54809) Thanks @neeravmakwana.</li>
|
||||
<li>CLI/message send: write manual <code>openclaw message send</code> deliveries into the resolved agent session transcript again by always threading the default CLI agent through outbound mirroring. (#54187) Thanks @KevInTheCloud5617.</li>
|
||||
<li>CLI/onboarding: show the Kimi Code API key option again in the Moonshot setup menu so the interactive picker includes all Kimi setup paths together. Fixes #54412 Thanks @sparkyrider</li>
|
||||
<li>Agents/status: use provider-aware context window lookup for fresh Anthropic 4.6 model overrides so <code>/status</code> shows the correct 1.0m window instead of an underreported shared-cache minimum. (#54796) Thanks @neeravmakwana.</li>
|
||||
<li>OpenAI/WebSocket: preserve reasoning replay metadata and tool-call item ids on WebSocket tool turns, and start a fresh response chain when full-context resend is required. (#53856) Thanks @xujingchen1996.</li>
|
||||
<li>OpenAI/WS: restore reasoning blocks for Responses WebSocket runs and keep reasoning/tool-call replay metadata intact so resumed sessions do not lose or break follow-up reasoning-capable turns. (#53856) Thanks @xujingchen1996.</li>
|
||||
<li>Agents/errors: surface provider quota/reset details when available, but keep HTML/Cloudflare rate-limit pages on the generic fallback so raw error pages are not shown to users. (#54512) Thanks @bugkill3r.</li>
|
||||
<li>Claude CLI: switch the bundled Claude CLI backend to <code>stream-json</code> output so watchdogs see progress on long runs, and keep session/usage metadata even when Claude finishes with an empty result line. (#49698) Thanks @felear2022.</li>
|
||||
<li>Claude CLI/MCP: always pass a strict generated <code>--mcp-config</code> overlay for background Claude CLI runs, including the empty-server case, so Claude does not inherit ambient user/global MCP servers. (#54961) Thanks @markojak.</li>
|
||||
<li>Agents/embedded replies: surface mid-turn 429 and overload failures when embedded runs end without a user-visible reply, while preserving successful media-only replies that still use legacy <code>mediaUrl</code>. (#50930) Thanks @infichen.</li>
|
||||
<li>Chat/UI: move the chat send button onto the shared ghost-button theme styling, while keeping the stop button icon readable on the danger state. (#55075) Thanks @bottenbenny.</li>
|
||||
<li>WhatsApp/allowFrom: show a specific allowFrom policy error for valid blocked targets instead of the misleading <code><E.164|group JID></code> format hint. Thanks @mcaxtr.</li>
|
||||
<li>Agents/cooldowns: scope rate-limit cooldowns per model so one 429 no longer blocks every model on the same auth profile, replace the exponential 1 min -> 1 h escalation with a stepped 30 s / 1 min / 5 min ladder, and surface a user-facing countdown message when all models are rate-limited. (#49834) Thanks @kiranvk-2011.</li>
|
||||
<li>Agents/embedded transport errors: distinguish common network failures like connection refused, DNS lookup failure, and interrupted sockets from true timeouts in embedded-run user messaging and lifecycle diagnostics. (#51419) Thanks @scoootscooob.</li>
|
||||
<li>Telegram/pairing: ignore self-authored DM <code>message</code> updates so bot-pinned status cards and similar service updates do not trigger bogus pairing requests or re-enter inbound dispatch. (#54530) thanks @huntharo</li>
|
||||
<li>Mattermost/replies: keep pairing replies, slash-command fallback replies, and model-picker messages on the resolved config path so <code>exec:</code> SecretRef bot tokens work across all outbound reply branches. (#48347) thanks @mathiasnagler.</li>
|
||||
<li>Microsoft Teams/config: accept the existing <code>welcomeCard</code>, <code>groupWelcomeCard</code>, <code>promptStarters</code>, and feedback/reflection keys in strict config validation so already-supported Teams runtime settings stop failing schema checks. (#54679) Thanks @gumclaw.</li>
|
||||
<li>MCP/channels: add a Gateway-backed channel MCP bridge with Codex/Claude-facing conversation tools, Claude channel notifications, and safer stdio bridge lifecycle handling for reconnects and routed session discovery.</li>
|
||||
<li>Plugins/SDK: thread <code>moduleUrl</code> through plugin-sdk alias resolution so user-installed plugins outside the openclaw directory correctly resolve <code>openclaw/plugin-sdk/*</code> subpath imports, and gate <code>plugin-sdk:check-exports</code> in <code>release:check</code>. (#54283) Thanks @xieyongliang.</li>
|
||||
<li>Config/web fetch: allow the documented <code>tools.web.fetch.maxResponseBytes</code> setting in runtime schema validation so valid configs no longer fail with unrecognized-key errors. (#53401) Thanks @erhhung.</li>
|
||||
<li>Message tool/buttons: keep the shared <code>buttons</code> schema optional in merged tool definitions so plain <code>action=send</code> calls stop failing validation when no buttons are provided. (#54418) Thanks @adzendo.</li>
|
||||
<li>Agents/openai-compatible tool calls: deduplicate repeated tool call ids across live assistant messages and replayed history so OpenAI-compatible backends no longer reject duplicate <code>tool_call_id</code> values with HTTP 400. (#40996) Thanks @xaeon2026.</li>
|
||||
<li>Models/openai-completions: default non-native OpenAI-compatible providers to omit tool-definition <code>strict</code> fields unless users explicitly opt back in, so tool calling keeps working on providers that reject that option. (#45497) Thanks @sahancava.</li>
|
||||
<li>Plugins/context engines: retry strict legacy <code>assemble()</code> calls without the new <code>prompt</code> field when older engines reject it, preserving prompt-aware retrieval compatibility for pre-prompt plugins. (#50848) thanks @danhdoan.</li>
|
||||
<li>CLI/update status: explicitly say <code>up to date</code> when the local version already matches npm latest, while keeping the availability logic unchanged. (#51409) Thanks @dongzhenye.</li>
|
||||
<li>Daemon/Linux: stop flagging non-gateway systemd services as duplicate gateways just because their unit files mention OpenClaw, reducing false-positive doctor/log noise. (#45328) Thanks @gregretkowski.</li>
|
||||
<li>Feishu: close WebSocket connections on monitor stop/abort so ghost connections no longer persist, preventing duplicate event processing and resource leaks across restart cycles. (#52844) Thanks @schumilin.</li>
|
||||
<li>Feishu: use the original message <code>create_time</code> instead of <code>Date.now()</code> for inbound timestamps so offline-retried messages carry the correct authoring time, preventing mis-targeted agent actions on stale instructions. (#52809) Thanks @schumilin.</li>
|
||||
<li>Control UI/Skills: open skill detail dialogs with the browser modal lifecycle so clicking a skill row keeps the panel centered instead of rendering it off-screen at the bottom of the page.</li>
|
||||
<li>Matrix/replies: include quoted poll question/options in inbound reply context so the agent sees the original poll content when users reply to Matrix poll messages. (#55056) Thanks @alberthild.</li>
|
||||
<li>Matrix/plugins: keep plugin bootstrap from crashing when built runtime mixes bare and deep <code>matrix-js-sdk</code> entrypoints, so unrelated channels do not get taken down during plugin load. (#56273) Thanks @aquaright1.</li>
|
||||
<li>Agents/sandbox: honor <code>tools.sandbox.tools.alsoAllow</code>, let explicit sandbox re-allows remove matching built-in default-deny tools, and keep sandbox explain/error guidance aligned with the effective sandbox tool policy. (#54492) Thanks @ngutman.</li>
|
||||
<li>Agents/sandbox: make blocked-tool guidance glob-aware again, redact/sanitize session-specific explain hints for safer copy-paste, and avoid leaking control-character session keys in those hints. (#54684) Thanks @ngutman.</li>
|
||||
<li>Agents/compaction: trigger timeout recovery compaction before retrying high-context LLM timeouts so embedded runs stop repeating oversized requests. (#46417) thanks @joeykrug.</li>
|
||||
<li>Agents/compaction: reconcile <code>sessions.json.compactionCount</code> after a late embedded auto-compaction success so persisted session counts catch up once the handler reports completion. (#45493) Thanks @jackal092927.</li>
|
||||
<li>Agents/failover: classify Codex accountId token extraction failures as auth errors so model fallback continues to the next configured candidate. (#55206) Thanks @cosmicnet.</li>
|
||||
<li>Plugins/runtime: reuse only compatible active plugin registries across tools, providers, web search, and channel bootstrap, align <code>/tools/invoke</code> plugin loading with the session workspace, and retry outbound channel recovery when the pinned channel surface changes so plugin tools and channels stop disappearing or re-registering from mismatched runtime loads. Thanks @gumadeiras.</li>
|
||||
<li>Talk/macOS: stop direct system-voice failures from replaying system speech, use app-locale fallback for shared watchdog timing, and add regression coverage for the macOS fallback route and language-aware timeout policy. (#53511) thanks @hongsw.</li>
|
||||
<li>Discord/gateway cleanup: keep late Carbon reconnect-exhausted errors suppressed through startup/dispose cleanup so Discord monitor shutdown no longer crashes on late gateway close events. (#55373) Thanks @Takhoffman.</li>
|
||||
<li>Discord/gateway shutdown: treat expected reconnect-exhausted events during intentional lifecycle stop as clean shutdowns so startup-abort cleanup no longer surfaces false gateway failures. (#55324) Thanks @joelnishanth.</li>
|
||||
<li>Discord/gateway shutdown: suppress reconnect-exhausted events that were already buffered before teardown flips <code>lifecycleStopping</code>, so stale-socket Discord restarts no longer crash the whole gateway. Fixes #55403 and #55421. Thanks @lml2468 and @vincentkoc.</li>
|
||||
<li>GitHub Copilot/auth refresh: treat large <code>expires_at</code> values as seconds epochs and clamp far-future runtime auth refresh timers so Copilot token refresh cannot fall into a <code>setTimeout</code> overflow hot loop. (#55360) Thanks @michael-abdo.</li>
|
||||
<li>Agents/status: use the persisted runtime session model in <code>session_status</code> when no explicit override exists, and honor per-agent <code>thinkingDefault</code> in both <code>session_status</code> and <code>/status</code>. (#55425) Thanks @scoootscooob, @xaeon2026, and @ysfbsf.</li>
|
||||
<li>Heartbeat/runner: guarantee the interval timer is re-armed after heartbeat runs and unexpected runner errors so scheduled heartbeats do not silently stop after an interrupted cycle. (#52270) Thanks @MiloStack.</li>
|
||||
<li>Config/Doctor: rewrite stale bundled plugin load paths from legacy bundled-plugin locations to the packaged bundled path, including directory-name mismatches and slash-suffixed config entries. (#55054) Thanks @SnowSky1.</li>
|
||||
<li>WhatsApp/mentions: stop treating mentions embedded in quoted messages as direct mentions so replying to a message that @mentioned the bot no longer falsely triggers mention gating. (#52711) Thanks @lurebat.</li>
|
||||
<li>Matrix: keep separate 2-person rooms out of DM routing after <code>m.direct</code> seeds successfully, while still honoring explicit <code>is_direct</code> state and startup fallback recovery. (#54890) thanks @private-peter</li>
|
||||
<li>Agents/ollama fallback: surface non-2xx Ollama HTTP errors with a leading status code so HTTP 503 responses trigger model fallback again. (#55214) Thanks @bugkill3r.</li>
|
||||
<li>Feishu/tools: stop synthetic agent ids like <code>agent-spawner</code> from being treated as Feishu account ids during tool execution, so tools fall back to the configured/default Feishu account unless the contextual id is a real enabled Feishu account. (#55627) Thanks @MonkeyLeeT.</li>
|
||||
<li>Google/tools: strip empty <code>required: []</code> arrays from Gemini tool schemas so optional-only tool parameters no longer trigger Google validator 400s. (#52106) Thanks @oliviareid-svg.</li>
|
||||
<li>Onboarding/TUI/local gateways: show the resolved gateway port in setup output, clarify no-daemon local health/dashboard messaging, and preserve loopback Control UI auth on reruns and explicit local gateway URLs so local quickstart flows recover cleanly. (#55730) Thanks @shakkernerd.</li>
|
||||
<li>TUI/chat log: keep system messages as single logical entries and prune overflow at whole-message boundaries so wrapped system spacing stays intact. (#55732) Thanks @shakkernerd.</li>
|
||||
<li>TUI/activation: validate <code>/activation</code> arguments in the TUI and reject invalid values instead of silently coercing them to <code>mention</code>. (#55733) Thanks @shakkernerd.</li>
|
||||
<li>Agents/model switching: apply <code>/model</code> changes to active embedded runs at the next safe retry boundary, so overloaded or retrying turns switch to the newly selected model instead of staying pinned to the old provider.</li>
|
||||
<li>Agents/Codex fallback: classify Codex <code>server_error</code> payloads as failoverable, sanitize <code>Codex error:</code> payloads before they reach chat, preserve context-overflow guidance for prefixed <code>invalid_request_error</code> payloads, and omit provider <code>request_id</code> values from user-facing UI copy. (#42892) Thanks @xaeon2026.</li>
|
||||
<li>Memory/search: share memory embedding provider registrations across split plugin runtimes so memory search no longer fails with unknown provider errors after memory-core registers built-in adapters. (#55945) Thanks @glitch418x.</li>
|
||||
<li>Discord/Carbon beta: update <code>@buape/carbon</code> to the latest beta and pass the new <code>RateLimitError</code> request argument so Discord stays compatible with the upstream beta constructor change. (#55980) Thanks @ngutman.</li>
|
||||
<li>Plugins/inbound claims: pass full inbound attachment arrays through <code>inbound_claim</code> hook metadata while keeping the legacy singular media attachment fields for compatibility. (#55452) Thanks @huntharo.</li>
|
||||
<li>Plugins/Matrix: preserve sender filenames for inbound media by forwarding <code>originalFilename</code> to <code>saveMediaBuffer</code>. (#55692) thanks @esrehmki.</li>
|
||||
<li>Matrix/mentions: recognize <code>matrix.to</code> mentions whose visible label uses the bot's room display name, so <code>requireMention: true</code> rooms respond correctly in modern Matrix clients. (#55393) thanks @nickludlam.</li>
|
||||
<li>Ollama/thinking off: route <code>thinkingLevel=off</code> through the live Ollama extension request path so thinking-capable Ollama models now receive top-level <code>think: false</code> instead of silently generating hidden reasoning tokens. (#53200) Thanks @BruceMacD.</li>
|
||||
<li>Plugins/diffs: stage bundled <code>@pierre/diffs</code> runtime dependencies during packaged updates so the bundled diff viewer keeps loading after global installs and updates. (#56077) Thanks @gumadeiras.</li>
|
||||
<li>Plugins/diffs: load bundled Pierre themes without JSON module imports so diff rendering keeps working on newer Node builds. (#45869) thanks @NickHood1984.</li>
|
||||
<li>Plugins/uninstall: remove owned <code>channels.<id></code> config when uninstalling channel plugins, and keep the uninstall preview aligned with explicit channel ownership so built-in channels and shared keys stay intact. (#35915) Thanks @wbxl2000.</li>
|
||||
<li>Plugins/Matrix: prefer explicit DM signals when choosing outbound direct rooms and routing unmapped verification summaries, so strict 2-person fallback rooms do not outrank the real DM. (#56076) thanks @gumadeiras</li>
|
||||
<li>Plugins/Matrix: resolve env-backed <code>accessToken</code> and <code>password</code> SecretRefs against the active Matrix config env path during startup, and officially accept SecretRef <code>accessToken</code> config values. (#54980) thanks @kakahu2015.</li>
|
||||
<li>Microsoft Teams/proactive DMs: prefer the freshest personal conversation reference for <code>user:<aadObjectId></code> sends when multiple stored references exist, so replies stop targeting stale DM threads. (#54702) Thanks @gumclaw.</li>
|
||||
<li>Gateway/plugins: reuse the session workspace when building HTTP <code>/tools/invoke</code> tool lists and harden tool construction to infer the session agent workspace by default, so workspace plugins do not re-register on repeated HTTP tool calls. (#56101) thanks @neeravmakwana</li>
|
||||
<li>Brave/web search: normalize unsupported Brave <code>country</code> filters to <code>ALL</code> before request and cache-key generation so locale-derived values like <code>VN</code> stop failing with upstream 422 validation errors. (#55695) Thanks @chen-zhang-cs-code.</li>
|
||||
<li>Discord/replies: preserve leading indentation when stripping inline reply tags so reply-tagged plain text and fenced code blocks keep their formatting. (#55960) Thanks @Nanako0129.</li>
|
||||
<li>Daemon/status: surface immediate gateway close reasons from lightweight probes and prefer those concrete auth or pairing failures over generic timeouts in <code>openclaw daemon status</code>. (#56282) Thanks @mbelinky.</li>
|
||||
<li>Agents/failover: classify HTTP 410 errors as retryable timeouts by default while still preserving explicit session-expired, billing, and auth signals from the payload. (#55201) thanks @nikus-pan.</li>
|
||||
<li>Agents/subagents: restore completion announce delivery for extension channels like BlueBubbles. (#56348)</li>
|
||||
<li>Plugins/Matrix: load bundled <code>@matrix-org/matrix-sdk-crypto-nodejs</code> through <code>createRequire(...)</code> so E2EE media send and receive keep the package-local native binding lookup working in packaged ESM builds. (#54566) thanks @joelnishanth.</li>
|
||||
<li>Plugins/Matrix: encrypt E2EE image thumbnails with <code>thumbnail_file</code> while keeping unencrypted-room previews on <code>thumbnail_url</code>, so encrypted Matrix image events keep thumbnail metadata without leaking plaintext previews. (#54711) thanks @frischeDaten.</li>
|
||||
<li>Telegram/forum topics: keep native <code>/new</code> and <code>/reset</code> routed to the active topic by preserving the topic target on forum-thread command context. (#35963)</li>
|
||||
</ul>
|
||||
<p><a href="https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md">View full changelog</a></p>
|
||||
]]></description>
|
||||
<enclosure url="https://github.com/openclaw/openclaw/releases/download/v2026.3.28/OpenClaw-2026.3.28.zip" length="25811288" type="application/octet-stream" sparkle:edSignature="SJp4ptVaGlOIXRPevS89DbfN2WKP0bKMXQoaT0fmLhy7pataDfHN0kxC3zu6P0Q/HtsxaESEhJUw48SCUNNKDA=="/>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
||||
@@ -65,8 +65,8 @@ android {
|
||||
applicationId = "ai.openclaw.app"
|
||||
minSdk = 31
|
||||
targetSdk = 36
|
||||
versionCode = 2026040201
|
||||
versionName = "2026.4.2"
|
||||
versionCode = 2026040301
|
||||
versionName = "2026.4.3"
|
||||
ndk {
|
||||
// Support all major ABIs — native libs are tiny (~47 KB per ABI)
|
||||
abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
|
||||
|
||||
@@ -16,6 +16,8 @@ import ai.openclaw.app.gateway.DeviceIdentityStore
|
||||
import ai.openclaw.app.gateway.GatewayDiscovery
|
||||
import ai.openclaw.app.gateway.GatewayEndpoint
|
||||
import ai.openclaw.app.gateway.GatewaySession
|
||||
import ai.openclaw.app.gateway.GatewayTlsProbeFailure
|
||||
import ai.openclaw.app.gateway.GatewayTlsProbeResult
|
||||
import ai.openclaw.app.gateway.probeGatewayTlsFingerprint
|
||||
import ai.openclaw.app.node.*
|
||||
import ai.openclaw.app.protocol.OpenClawCanvasA2UIAction
|
||||
@@ -44,7 +46,7 @@ import java.util.concurrent.atomic.AtomicLong
|
||||
class NodeRuntime(
|
||||
context: Context,
|
||||
val prefs: SecurePrefs = SecurePrefs(context.applicationContext),
|
||||
private val tlsFingerprintProbe: suspend (String, Int) -> String? = ::probeGatewayTlsFingerprint,
|
||||
private val tlsFingerprintProbe: suspend (String, Int) -> GatewayTlsProbeResult = ::probeGatewayTlsFingerprint,
|
||||
) {
|
||||
data class GatewayConnectAuth(
|
||||
val token: String?,
|
||||
@@ -839,8 +841,9 @@ class NodeRuntime(
|
||||
// First-time TLS: capture fingerprint, ask user to verify out-of-band, then store and connect.
|
||||
_statusText.value = "Verify gateway TLS fingerprint…"
|
||||
scope.launch {
|
||||
val fp = tlsFingerprintProbe(endpoint.host, endpoint.port) ?: run {
|
||||
_statusText.value = "Failed: can't read TLS fingerprint"
|
||||
val tlsProbe = tlsFingerprintProbe(endpoint.host, endpoint.port)
|
||||
val fp = tlsProbe.fingerprintSha256 ?: run {
|
||||
_statusText.value = gatewayTlsProbeFailureMessage(tlsProbe.failure)
|
||||
return@launch
|
||||
}
|
||||
_pendingGatewayTrust.value =
|
||||
@@ -888,6 +891,15 @@ class NodeRuntime(
|
||||
_statusText.value = "Offline"
|
||||
}
|
||||
|
||||
private fun gatewayTlsProbeFailureMessage(failure: GatewayTlsProbeFailure?): String {
|
||||
return when (failure) {
|
||||
GatewayTlsProbeFailure.TLS_UNAVAILABLE ->
|
||||
"Failed: this host requires wss:// or Tailscale Serve. No TLS endpoint detected."
|
||||
GatewayTlsProbeFailure.ENDPOINT_UNREACHABLE, null ->
|
||||
"Failed: couldn't reach the secure gateway endpoint for this host."
|
||||
}
|
||||
}
|
||||
|
||||
private fun hasRecordAudioPermission(): Boolean {
|
||||
return (
|
||||
ContextCompat.checkSelfPermission(appContext, Manifest.permission.RECORD_AUDIO) ==
|
||||
|
||||
@@ -18,9 +18,7 @@ internal fun isLoopbackGatewayHost(
|
||||
host = host.dropLast(1)
|
||||
}
|
||||
val zoneIndex = host.indexOf('%')
|
||||
if (zoneIndex >= 0) {
|
||||
host = host.substring(0, zoneIndex)
|
||||
}
|
||||
if (zoneIndex >= 0) return false
|
||||
if (host.isEmpty()) return false
|
||||
if (host == "localhost") return true
|
||||
if (allowEmulatorBridgeAlias && host == "10.0.2.2") return true
|
||||
@@ -46,6 +44,52 @@ internal fun isLoopbackGatewayHost(
|
||||
return isMappedIpv4 && address[12] == 127.toByte()
|
||||
}
|
||||
|
||||
internal fun isPrivateLanGatewayHost(
|
||||
rawHost: String?,
|
||||
allowEmulatorBridgeAlias: Boolean = isAndroidEmulatorRuntime(),
|
||||
): Boolean {
|
||||
var host =
|
||||
rawHost
|
||||
?.trim()
|
||||
?.lowercase(Locale.US)
|
||||
?.trim('[', ']')
|
||||
.orEmpty()
|
||||
if (host.endsWith(".")) {
|
||||
host = host.dropLast(1)
|
||||
}
|
||||
val zoneIndex = host.indexOf('%')
|
||||
if (zoneIndex >= 0) {
|
||||
host = host.substring(0, zoneIndex)
|
||||
}
|
||||
if (host.isEmpty()) return false
|
||||
if (isLoopbackGatewayHost(host, allowEmulatorBridgeAlias = allowEmulatorBridgeAlias)) return true
|
||||
if (host.endsWith(".local")) return true
|
||||
if (!host.contains('.') && !host.contains(':')) return true
|
||||
|
||||
parseIpv4Address(host)?.let { ipv4 ->
|
||||
val first = ipv4[0].toInt() and 0xff
|
||||
val second = ipv4[1].toInt() and 0xff
|
||||
return when {
|
||||
first == 10 -> true
|
||||
first == 172 && second in 16..31 -> true
|
||||
first == 192 && second == 168 -> true
|
||||
first == 169 && second == 254 -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
if (!host.contains(':') || !host.all(::isIpv6LiteralChar)) return false
|
||||
|
||||
val address = runCatching { InetAddress.getByName(host) }.getOrNull() ?: return false
|
||||
return when {
|
||||
address.isLinkLocalAddress -> true
|
||||
address.isSiteLocalAddress -> true
|
||||
else -> {
|
||||
val bytes = address.address
|
||||
bytes.size == 16 && (bytes[0].toInt() and 0xfe) == 0xfc
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isAndroidEmulatorRuntime(): Boolean {
|
||||
val fingerprint = Build.FINGERPRINT?.lowercase(Locale.US).orEmpty()
|
||||
val model = Build.MODEL?.lowercase(Locale.US).orEmpty()
|
||||
|
||||
@@ -3,7 +3,11 @@ package ai.openclaw.app.gateway
|
||||
import android.annotation.SuppressLint
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.EOFException
|
||||
import java.net.ConnectException
|
||||
import java.net.InetSocketAddress
|
||||
import java.net.SocketTimeoutException
|
||||
import java.net.UnknownHostException
|
||||
import java.security.MessageDigest
|
||||
import java.security.SecureRandom
|
||||
import java.security.cert.CertificateException
|
||||
@@ -12,6 +16,7 @@ import java.util.Locale
|
||||
import javax.net.ssl.HttpsURLConnection
|
||||
import javax.net.ssl.HostnameVerifier
|
||||
import javax.net.ssl.SSLContext
|
||||
import javax.net.ssl.SSLException
|
||||
import javax.net.ssl.SSLParameters
|
||||
import javax.net.ssl.SSLSocketFactory
|
||||
import javax.net.ssl.SNIHostName
|
||||
@@ -32,6 +37,16 @@ data class GatewayTlsConfig(
|
||||
val hostnameVerifier: HostnameVerifier,
|
||||
)
|
||||
|
||||
enum class GatewayTlsProbeFailure {
|
||||
TLS_UNAVAILABLE,
|
||||
ENDPOINT_UNREACHABLE,
|
||||
}
|
||||
|
||||
data class GatewayTlsProbeResult(
|
||||
val fingerprintSha256: String? = null,
|
||||
val failure: GatewayTlsProbeFailure? = null,
|
||||
)
|
||||
|
||||
fun buildGatewayTlsConfig(
|
||||
params: GatewayTlsParams?,
|
||||
onStore: ((String) -> Unit)? = null,
|
||||
@@ -85,10 +100,10 @@ suspend fun probeGatewayTlsFingerprint(
|
||||
host: String,
|
||||
port: Int,
|
||||
timeoutMs: Int = 3_000,
|
||||
): String? {
|
||||
): GatewayTlsProbeResult {
|
||||
val trimmedHost = host.trim()
|
||||
if (trimmedHost.isEmpty()) return null
|
||||
if (port !in 1..65535) return null
|
||||
if (trimmedHost.isEmpty()) return GatewayTlsProbeResult(failure = GatewayTlsProbeFailure.ENDPOINT_UNREACHABLE)
|
||||
if (port !in 1..65535) return GatewayTlsProbeResult(failure = GatewayTlsProbeFailure.ENDPOINT_UNREACHABLE)
|
||||
|
||||
return withContext(Dispatchers.IO) {
|
||||
val trustAll =
|
||||
@@ -121,10 +136,21 @@ suspend fun probeGatewayTlsFingerprint(
|
||||
}
|
||||
|
||||
socket.startHandshake()
|
||||
val cert = socket.session.peerCertificates.firstOrNull() as? X509Certificate ?: return@withContext null
|
||||
sha256Hex(cert.encoded)
|
||||
} catch (_: Throwable) {
|
||||
null
|
||||
val cert =
|
||||
socket.session.peerCertificates.firstOrNull() as? X509Certificate
|
||||
?: return@withContext GatewayTlsProbeResult(failure = GatewayTlsProbeFailure.TLS_UNAVAILABLE)
|
||||
GatewayTlsProbeResult(fingerprintSha256 = sha256Hex(cert.encoded))
|
||||
} catch (err: Throwable) {
|
||||
val failure =
|
||||
when (err) {
|
||||
is SSLException,
|
||||
is EOFException -> GatewayTlsProbeFailure.TLS_UNAVAILABLE
|
||||
is ConnectException,
|
||||
is SocketTimeoutException,
|
||||
is UnknownHostException -> GatewayTlsProbeFailure.ENDPOINT_UNREACHABLE
|
||||
else -> GatewayTlsProbeFailure.ENDPOINT_UNREACHABLE
|
||||
}
|
||||
GatewayTlsProbeResult(failure = failure)
|
||||
} finally {
|
||||
try {
|
||||
socket.close()
|
||||
|
||||
@@ -34,10 +34,10 @@ class ConnectionManager(
|
||||
val stableId = endpoint.stableId
|
||||
val stored = storedFingerprint?.trim().takeIf { !it.isNullOrEmpty() }
|
||||
val isManual = stableId.startsWith("manual|")
|
||||
val isLoopback = isLoopbackGatewayHost(endpoint.host)
|
||||
val cleartextAllowedHost = isLoopbackGatewayHost(endpoint.host)
|
||||
|
||||
if (isManual) {
|
||||
if (!manualTlsEnabled && isLoopback) return null
|
||||
if (!manualTlsEnabled && cleartextAllowedHost) return null
|
||||
if (!stored.isNullOrBlank()) {
|
||||
return GatewayTlsParams(
|
||||
required = true,
|
||||
@@ -75,7 +75,7 @@ class ConnectionManager(
|
||||
)
|
||||
}
|
||||
|
||||
if (!isLoopback) {
|
||||
if (!cleartextAllowedHost) {
|
||||
return GatewayTlsParams(
|
||||
required = true,
|
||||
expectedFingerprint = null,
|
||||
|
||||
@@ -256,9 +256,23 @@ fun ConnectTabScreen(viewModel: MainViewModel) {
|
||||
if (config == null) {
|
||||
validationText =
|
||||
if (inputMode == ConnectInputMode.SetupCode) {
|
||||
"Paste a valid setup code to connect."
|
||||
val parsedSetup = decodeGatewaySetupCode(setupCode)
|
||||
if (parsedSetup == null) {
|
||||
"Paste a valid setup code to connect."
|
||||
} else {
|
||||
val parsedGateway = parseGatewayEndpointResult(parsedSetup.url)
|
||||
gatewayEndpointValidationMessage(
|
||||
parsedGateway.error ?: GatewayEndpointValidationError.INVALID_URL,
|
||||
GatewayEndpointInputSource.SETUP_CODE,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
"Enter a valid manual host and port to connect."
|
||||
val manualUrl = composeGatewayManualUrl(manualHostInput, manualPortInput, manualTlsInput)
|
||||
val parsedGateway = manualUrl?.let(::parseGatewayEndpointResult)
|
||||
gatewayEndpointValidationMessage(
|
||||
parsedGateway?.error ?: GatewayEndpointValidationError.INVALID_URL,
|
||||
GatewayEndpointInputSource.MANUAL,
|
||||
)
|
||||
}
|
||||
return@Button
|
||||
}
|
||||
@@ -386,6 +400,11 @@ fun ConnectTabScreen(viewModel: MainViewModel) {
|
||||
Text("Run these on the gateway host:", style = mobileCallout, color = mobileTextSecondary)
|
||||
CommandBlock("openclaw qr --setup-code-only")
|
||||
CommandBlock("openclaw qr --json")
|
||||
Text(
|
||||
"For Tailscale or public hosts, use wss:// or Tailscale Serve. Private LAN ws:// remains supported.",
|
||||
style = mobileCaption1,
|
||||
color = mobileTextSecondary,
|
||||
)
|
||||
|
||||
if (inputMode == ConnectInputMode.SetupCode) {
|
||||
Text("Setup Code", style = mobileCaption1.copy(fontWeight = FontWeight.SemiBold), color = mobileTextSecondary)
|
||||
@@ -468,7 +487,11 @@ fun ConnectTabScreen(viewModel: MainViewModel) {
|
||||
) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
|
||||
Text("Use TLS", style = mobileHeadline, color = mobileText)
|
||||
Text("Switch to secure websocket (`wss`).", style = mobileCallout, color = mobileTextSecondary)
|
||||
Text(
|
||||
"Turn this on for Tailscale or public hosts. Private LAN ws:// remains supported.",
|
||||
style = mobileCallout,
|
||||
color = mobileTextSecondary,
|
||||
)
|
||||
}
|
||||
Switch(
|
||||
checked = manualTlsInput,
|
||||
|
||||
@@ -33,7 +33,32 @@ internal data class GatewayConnectConfig(
|
||||
val password: String,
|
||||
)
|
||||
|
||||
internal enum class GatewayEndpointValidationError {
|
||||
INVALID_URL,
|
||||
INSECURE_REMOTE_URL,
|
||||
}
|
||||
|
||||
internal enum class GatewayEndpointInputSource {
|
||||
SETUP_CODE,
|
||||
MANUAL,
|
||||
QR_SCAN,
|
||||
}
|
||||
|
||||
internal data class GatewayEndpointParseResult(
|
||||
val config: GatewayEndpointConfig? = null,
|
||||
val error: GatewayEndpointValidationError? = null,
|
||||
)
|
||||
|
||||
internal data class GatewayScannedSetupCodeResult(
|
||||
val setupCode: String? = null,
|
||||
val error: GatewayEndpointValidationError? = null,
|
||||
)
|
||||
|
||||
private val gatewaySetupJson = Json { ignoreUnknownKeys = true }
|
||||
private const val remoteGatewaySecurityRule =
|
||||
"Non-loopback mobile nodes require wss:// or Tailscale Serve. ws:// is allowed only for localhost and the Android emulator."
|
||||
private const val remoteGatewaySecurityFix =
|
||||
"Use a private LAN host/address, or enable Tailscale Serve / expose a wss:// gateway URL."
|
||||
|
||||
internal fun resolveGatewayConnectConfig(
|
||||
useSetupCode: Boolean,
|
||||
@@ -50,7 +75,7 @@ internal fun resolveGatewayConnectConfig(
|
||||
): GatewayConnectConfig? {
|
||||
if (useSetupCode) {
|
||||
val setup = decodeGatewaySetupCode(setupCode) ?: return null
|
||||
val parsed = parseGatewayEndpoint(setup.url) ?: return null
|
||||
val parsed = parseGatewayEndpointResult(setup.url).config ?: return null
|
||||
val setupBootstrapToken = setup.bootstrapToken?.trim().orEmpty()
|
||||
val sharedToken =
|
||||
when {
|
||||
@@ -75,10 +100,10 @@ internal fun resolveGatewayConnectConfig(
|
||||
}
|
||||
|
||||
val manualUrl = composeGatewayManualUrl(manualHostInput, manualPortInput, manualTlsInput) ?: return null
|
||||
val parsed = parseGatewayEndpoint(manualUrl) ?: return null
|
||||
val parsed = parseGatewayEndpointResult(manualUrl).config ?: return null
|
||||
val savedManualEndpoint =
|
||||
composeGatewayManualUrl(savedManualHost, savedManualPort, savedManualTls)
|
||||
?.let(::parseGatewayEndpoint)
|
||||
?.let { parseGatewayEndpointResult(it).config }
|
||||
val preserveBootstrapToken =
|
||||
savedManualEndpoint != null &&
|
||||
savedManualEndpoint.host == parsed.host &&
|
||||
@@ -97,13 +122,19 @@ internal fun resolveGatewayConnectConfig(
|
||||
}
|
||||
|
||||
internal fun parseGatewayEndpoint(rawInput: String): GatewayEndpointConfig? {
|
||||
return parseGatewayEndpointResult(rawInput).config
|
||||
}
|
||||
|
||||
internal fun parseGatewayEndpointResult(rawInput: String): GatewayEndpointParseResult {
|
||||
val raw = rawInput.trim()
|
||||
if (raw.isEmpty()) return null
|
||||
if (raw.isEmpty()) return GatewayEndpointParseResult(error = GatewayEndpointValidationError.INVALID_URL)
|
||||
|
||||
val normalized = if (raw.contains("://")) raw else "https://$raw"
|
||||
val uri = runCatching { URI(normalized) }.getOrNull() ?: return null
|
||||
val uri =
|
||||
runCatching { URI(normalized) }.getOrNull()
|
||||
?: return GatewayEndpointParseResult(error = GatewayEndpointValidationError.INVALID_URL)
|
||||
val host = uri.host?.trim()?.trim('[', ']').orEmpty()
|
||||
if (host.isEmpty()) return null
|
||||
if (host.isEmpty()) return GatewayEndpointParseResult(error = GatewayEndpointValidationError.INVALID_URL)
|
||||
|
||||
val scheme = uri.scheme?.trim()?.lowercase(Locale.US).orEmpty()
|
||||
val tls =
|
||||
@@ -112,7 +143,9 @@ internal fun parseGatewayEndpoint(rawInput: String): GatewayEndpointConfig? {
|
||||
"wss", "https" -> true
|
||||
else -> true
|
||||
}
|
||||
if (!tls && !isLoopbackGatewayHost(host)) return null
|
||||
if (!tls && !isLoopbackGatewayHost(host)) {
|
||||
return GatewayEndpointParseResult(error = GatewayEndpointValidationError.INSECURE_REMOTE_URL)
|
||||
}
|
||||
val defaultPort =
|
||||
when (scheme) {
|
||||
"wss", "https" -> 443
|
||||
@@ -134,7 +167,9 @@ internal fun parseGatewayEndpoint(rawInput: String): GatewayEndpointConfig? {
|
||||
"${if (tls) "https" else "http"}://$displayHost:$port"
|
||||
}
|
||||
|
||||
return GatewayEndpointConfig(host = host, port = port, tls = tls, displayUrl = displayUrl)
|
||||
return GatewayEndpointParseResult(
|
||||
config = GatewayEndpointConfig(host = host, port = port, tls = tls, displayUrl = displayUrl),
|
||||
)
|
||||
}
|
||||
|
||||
internal fun decodeGatewaySetupCode(rawInput: String): GatewaySetupCode? {
|
||||
@@ -165,9 +200,44 @@ internal fun decodeGatewaySetupCode(rawInput: String): GatewaySetupCode? {
|
||||
}
|
||||
|
||||
internal fun resolveScannedSetupCode(rawInput: String): String? {
|
||||
val setupCode = resolveSetupCodeCandidate(rawInput) ?: return null
|
||||
val decoded = decodeGatewaySetupCode(setupCode) ?: return null
|
||||
return setupCode.takeIf { parseGatewayEndpoint(decoded.url) != null }
|
||||
return resolveScannedSetupCodeResult(rawInput).setupCode
|
||||
}
|
||||
|
||||
internal fun resolveScannedSetupCodeResult(rawInput: String): GatewayScannedSetupCodeResult {
|
||||
val setupCode =
|
||||
resolveSetupCodeCandidate(rawInput)
|
||||
?: return GatewayScannedSetupCodeResult(error = GatewayEndpointValidationError.INVALID_URL)
|
||||
val decoded =
|
||||
decodeGatewaySetupCode(setupCode)
|
||||
?: return GatewayScannedSetupCodeResult(error = GatewayEndpointValidationError.INVALID_URL)
|
||||
val parsed = parseGatewayEndpointResult(decoded.url)
|
||||
if (parsed.config == null) {
|
||||
return GatewayScannedSetupCodeResult(error = parsed.error)
|
||||
}
|
||||
return GatewayScannedSetupCodeResult(setupCode = setupCode)
|
||||
}
|
||||
|
||||
internal fun gatewayEndpointValidationMessage(
|
||||
error: GatewayEndpointValidationError,
|
||||
source: GatewayEndpointInputSource,
|
||||
): String {
|
||||
return when (error) {
|
||||
GatewayEndpointValidationError.INSECURE_REMOTE_URL ->
|
||||
when (source) {
|
||||
GatewayEndpointInputSource.SETUP_CODE ->
|
||||
"Setup code points to an insecure remote gateway. $remoteGatewaySecurityRule $remoteGatewaySecurityFix"
|
||||
GatewayEndpointInputSource.QR_SCAN ->
|
||||
"QR code points to an insecure remote gateway. $remoteGatewaySecurityRule $remoteGatewaySecurityFix"
|
||||
GatewayEndpointInputSource.MANUAL ->
|
||||
"$remoteGatewaySecurityRule $remoteGatewaySecurityFix"
|
||||
}
|
||||
GatewayEndpointValidationError.INVALID_URL ->
|
||||
when (source) {
|
||||
GatewayEndpointInputSource.SETUP_CODE -> "Setup code has invalid gateway URL."
|
||||
GatewayEndpointInputSource.QR_SCAN -> "QR code did not contain a valid setup code."
|
||||
GatewayEndpointInputSource.MANUAL -> "Enter a valid manual host and port to connect."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun composeGatewayManualUrl(hostInput: String, portInput: String, tls: Boolean): String? {
|
||||
|
||||
@@ -49,6 +49,7 @@ internal fun buildGatewayDiagnosticsReport(
|
||||
Please:
|
||||
- pick one route only: same machine, same LAN, Tailscale, or public URL
|
||||
- classify this as pairing/auth, TLS trust, wrong advertised route, wrong address/port, or gateway down
|
||||
- remember: Tailscale/public mobile routes require wss:// or Tailscale Serve; private LAN ws:// is still allowed
|
||||
- quote the exact app status/error below
|
||||
- tell me whether `openclaw devices list` should show a pending pairing request
|
||||
- if more signal is needed, ask for `openclaw qr --json`, `openclaw devices list`, and `openclaw nodes status`
|
||||
|
||||
@@ -566,12 +566,16 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) {
|
||||
if (contents.isEmpty()) {
|
||||
return@addOnSuccessListener
|
||||
}
|
||||
val scannedSetupCode = resolveScannedSetupCode(contents)
|
||||
if (scannedSetupCode == null) {
|
||||
gatewayError = "QR code did not contain a valid setup code."
|
||||
val scannedSetupCode = resolveScannedSetupCodeResult(contents)
|
||||
if (scannedSetupCode.setupCode == null) {
|
||||
gatewayError =
|
||||
gatewayEndpointValidationMessage(
|
||||
scannedSetupCode.error ?: GatewayEndpointValidationError.INVALID_URL,
|
||||
GatewayEndpointInputSource.QR_SCAN,
|
||||
)
|
||||
return@addOnSuccessListener
|
||||
}
|
||||
setupCode = scannedSetupCode
|
||||
setupCode = scannedSetupCode.setupCode
|
||||
gatewayInputMode = GatewayInputMode.SetupCode
|
||||
gatewayError = null
|
||||
attemptedConnect = false
|
||||
@@ -799,9 +803,13 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) {
|
||||
gatewayError = "Scan QR code first, or use Advanced setup."
|
||||
return@Button
|
||||
}
|
||||
val parsedGateway = parseGatewayEndpoint(parsedSetup.url)
|
||||
if (parsedGateway == null) {
|
||||
gatewayError = "Setup code has invalid gateway URL."
|
||||
val parsedGateway = parseGatewayEndpointResult(parsedSetup.url)
|
||||
if (parsedGateway.config == null) {
|
||||
gatewayError =
|
||||
gatewayEndpointValidationMessage(
|
||||
parsedGateway.error ?: GatewayEndpointValidationError.INVALID_URL,
|
||||
GatewayEndpointInputSource.SETUP_CODE,
|
||||
)
|
||||
return@Button
|
||||
}
|
||||
gatewayUrl = parsedSetup.url
|
||||
@@ -819,12 +827,16 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) {
|
||||
}
|
||||
} else {
|
||||
val manualUrl = composeGatewayManualUrl(manualHost, manualPort, manualTls)
|
||||
val parsedGateway = manualUrl?.let(::parseGatewayEndpoint)
|
||||
if (parsedGateway == null) {
|
||||
gatewayError = "Manual endpoint is invalid."
|
||||
val parsedGateway = manualUrl?.let(::parseGatewayEndpointResult)
|
||||
if (parsedGateway?.config == null) {
|
||||
gatewayError =
|
||||
gatewayEndpointValidationMessage(
|
||||
parsedGateway?.error ?: GatewayEndpointValidationError.INVALID_URL,
|
||||
GatewayEndpointInputSource.MANUAL,
|
||||
)
|
||||
return@Button
|
||||
}
|
||||
gatewayUrl = parsedGateway.displayUrl
|
||||
gatewayUrl = parsedGateway.config.displayUrl
|
||||
viewModel.setGatewayBootstrapToken("")
|
||||
}
|
||||
step = OnboardingStep.Permissions
|
||||
@@ -863,19 +875,23 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) {
|
||||
} else {
|
||||
Button(
|
||||
onClick = {
|
||||
val parsed = parseGatewayEndpoint(gatewayUrl)
|
||||
if (parsed == null) {
|
||||
val parsed = parseGatewayEndpointResult(gatewayUrl)
|
||||
if (parsed.config == null) {
|
||||
step = OnboardingStep.Gateway
|
||||
gatewayError = "Invalid gateway URL."
|
||||
gatewayError =
|
||||
gatewayEndpointValidationMessage(
|
||||
parsed.error ?: GatewayEndpointValidationError.INVALID_URL,
|
||||
GatewayEndpointInputSource.MANUAL,
|
||||
)
|
||||
return@Button
|
||||
}
|
||||
val token = persistedGatewayToken.trim()
|
||||
val password = gatewayPassword.trim()
|
||||
attemptedConnect = true
|
||||
viewModel.setManualEnabled(true)
|
||||
viewModel.setManualHost(parsed.host)
|
||||
viewModel.setManualPort(parsed.port)
|
||||
viewModel.setManualTls(parsed.tls)
|
||||
viewModel.setManualHost(parsed.config.host)
|
||||
viewModel.setManualPort(parsed.config.port)
|
||||
viewModel.setManualTls(parsed.config.tls)
|
||||
if (gatewayInputMode == GatewayInputMode.Manual) {
|
||||
viewModel.setGatewayBootstrapToken("")
|
||||
}
|
||||
@@ -886,7 +902,7 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) {
|
||||
}
|
||||
viewModel.setGatewayPassword(password)
|
||||
viewModel.connect(
|
||||
GatewayEndpoint.manual(host = parsed.host, port = parsed.port),
|
||||
GatewayEndpoint.manual(host = parsed.config.host, port = parsed.config.port),
|
||||
token = token.ifEmpty { null },
|
||||
bootstrapToken =
|
||||
if (gatewayInputMode == GatewayInputMode.SetupCode) {
|
||||
@@ -1040,7 +1056,7 @@ private fun GatewayStep(
|
||||
|
||||
StepShell(title = "Gateway Connection") {
|
||||
Text(
|
||||
"Run `openclaw qr` on your gateway host, then scan the code with this device.",
|
||||
"Run `openclaw qr` on your gateway host, then scan the code with this device. For Tailscale or public hosts, use wss:// or Tailscale Serve.",
|
||||
style = onboardingCalloutStyle,
|
||||
color = onboardingTextSecondary,
|
||||
)
|
||||
@@ -1072,7 +1088,7 @@ private fun GatewayStep(
|
||||
) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
|
||||
Text("Advanced setup", style = onboardingHeadlineStyle, color = onboardingText)
|
||||
Text("Paste setup code or enter host/port manually.", style = onboardingCaption1Style, color = onboardingTextSecondary)
|
||||
Text("Paste setup code or enter host/port manually. Private LAN ws:// is supported; Tailscale/public hosts need wss://.", style = onboardingCaption1Style, color = onboardingTextSecondary)
|
||||
}
|
||||
Icon(
|
||||
imageVector = if (advancedOpen) Icons.Default.ExpandLess else Icons.Default.ExpandMore,
|
||||
@@ -1153,7 +1169,11 @@ private fun GatewayStep(
|
||||
) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
|
||||
Text("Use TLS", style = onboardingHeadlineStyle, color = onboardingText)
|
||||
Text("Switch to secure websocket (`wss`).", style = onboardingCalloutStyle.copy(lineHeight = 18.sp), color = onboardingTextSecondary)
|
||||
Text(
|
||||
"Turn this on for Tailscale or public hosts. Private LAN ws:// remains supported.",
|
||||
style = onboardingCalloutStyle.copy(lineHeight = 18.sp),
|
||||
color = onboardingTextSecondary,
|
||||
)
|
||||
}
|
||||
Switch(
|
||||
checked = manualTls,
|
||||
|
||||
@@ -2,6 +2,8 @@ package ai.openclaw.app
|
||||
|
||||
import ai.openclaw.app.gateway.GatewayEndpoint
|
||||
import ai.openclaw.app.gateway.GatewaySession
|
||||
import ai.openclaw.app.gateway.GatewayTlsProbeFailure
|
||||
import ai.openclaw.app.gateway.GatewayTlsProbeResult
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
@@ -77,7 +79,7 @@ class GatewayBootstrapAuthTest {
|
||||
NodeRuntime(
|
||||
app,
|
||||
prefs,
|
||||
tlsFingerprintProbe = { _, _ -> "fp-1" },
|
||||
tlsFingerprintProbe = { _, _ -> GatewayTlsProbeResult(fingerprintSha256 = "fp-1") },
|
||||
)
|
||||
val endpoint = GatewayEndpoint.manual(host = "gateway.example", port = 18789)
|
||||
val explicitAuth =
|
||||
@@ -98,6 +100,29 @@ class GatewayBootstrapAuthTest {
|
||||
assertEquals("setup-bootstrap-token", desiredBootstrapToken(runtime, "operatorSession"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun connect_showsSecureEndpointGuidanceWhenTlsProbeFails() {
|
||||
val app = RuntimeEnvironment.getApplication()
|
||||
val runtime =
|
||||
NodeRuntime(
|
||||
app,
|
||||
tlsFingerprintProbe = { _, _ ->
|
||||
GatewayTlsProbeResult(failure = GatewayTlsProbeFailure.TLS_UNAVAILABLE)
|
||||
},
|
||||
)
|
||||
|
||||
runtime.connect(
|
||||
GatewayEndpoint.manual(host = "gateway.example", port = 18789),
|
||||
NodeRuntime.GatewayConnectAuth(token = "shared-token", bootstrapToken = null, password = null),
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
"Failed: this host requires wss:// or Tailscale Serve. No TLS endpoint detected.",
|
||||
waitForStatusText(runtime),
|
||||
)
|
||||
assertNull(runtime.pendingGatewayTrust.value)
|
||||
}
|
||||
|
||||
private fun waitForGatewayTrustPrompt(runtime: NodeRuntime): NodeRuntime.GatewayTrustPrompt {
|
||||
repeat(50) {
|
||||
runtime.pendingGatewayTrust.value?.let { return it }
|
||||
@@ -106,6 +131,17 @@ class GatewayBootstrapAuthTest {
|
||||
error("Expected pending gateway trust prompt")
|
||||
}
|
||||
|
||||
private fun waitForStatusText(runtime: NodeRuntime): String {
|
||||
repeat(50) {
|
||||
val status = runtime.statusText.value
|
||||
if (status != "Verify gateway TLS fingerprint…") {
|
||||
return status
|
||||
}
|
||||
Thread.sleep(10)
|
||||
}
|
||||
error("Expected status text update")
|
||||
}
|
||||
|
||||
private fun desiredBootstrapToken(runtime: NodeRuntime, sessionFieldName: String): String? {
|
||||
val session = readField<GatewaySession>(runtime, sessionFieldName)
|
||||
val desired = readField<Any?>(session, "desired") ?: return null
|
||||
|
||||
@@ -11,6 +11,7 @@ import ai.openclaw.app.protocol.OpenClawMotionCommand
|
||||
import ai.openclaw.app.protocol.OpenClawSmsCommand
|
||||
import ai.openclaw.app.gateway.GatewayEndpoint
|
||||
import ai.openclaw.app.gateway.isLoopbackGatewayHost
|
||||
import ai.openclaw.app.gateway.isPrivateLanGatewayHost
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertNull
|
||||
@@ -107,12 +108,52 @@ class ConnectionManagerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun resolveTlsParamsForEndpoint_discoveryNonLoopbackWithoutHintsStillRequiresTls() {
|
||||
fun resolveTlsParamsForEndpoint_manualPrivateLanRequiresTlsWhenToggleIsOff() {
|
||||
val endpoint = GatewayEndpoint.manual(host = "192.168.1.20", port = 18789)
|
||||
|
||||
val params =
|
||||
ConnectionManager.resolveTlsParamsForEndpoint(
|
||||
endpoint,
|
||||
storedFingerprint = null,
|
||||
manualTlsEnabled = false,
|
||||
)
|
||||
|
||||
assertEquals(true, params?.required)
|
||||
assertNull(params?.expectedFingerprint)
|
||||
assertEquals(false, params?.allowTOFU)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun resolveTlsParamsForEndpoint_discoveryTailnetWithoutHintsStillRequiresTls() {
|
||||
val endpoint =
|
||||
GatewayEndpoint(
|
||||
stableId = "_openclaw-gw._tcp.|local.|Test",
|
||||
name = "Test",
|
||||
host = "10.0.0.2",
|
||||
host = "100.64.0.9",
|
||||
port = 18789,
|
||||
tlsEnabled = false,
|
||||
tlsFingerprintSha256 = null,
|
||||
)
|
||||
|
||||
val params =
|
||||
ConnectionManager.resolveTlsParamsForEndpoint(
|
||||
endpoint,
|
||||
storedFingerprint = null,
|
||||
manualTlsEnabled = false,
|
||||
)
|
||||
|
||||
assertEquals(true, params?.required)
|
||||
assertNull(params?.expectedFingerprint)
|
||||
assertEquals(false, params?.allowTOFU)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun resolveTlsParamsForEndpoint_discoveryPrivateLanWithoutHintsRequiresTls() {
|
||||
val endpoint =
|
||||
GatewayEndpoint(
|
||||
stableId = "_openclaw-gw._tcp.|local.|Test",
|
||||
name = "Test",
|
||||
host = "192.168.1.20",
|
||||
port = 18789,
|
||||
tlsEnabled = false,
|
||||
tlsFingerprintSha256 = null,
|
||||
@@ -202,6 +243,14 @@ class ConnectionManagerTest {
|
||||
assertFalse(isLoopbackGatewayHost("10.0.2.2", allowEmulatorBridgeAlias = false))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isPrivateLanGatewayHost_acceptsLanHostsButRejectsTailnetHosts() {
|
||||
assertTrue(isPrivateLanGatewayHost("192.168.1.20"))
|
||||
assertTrue(isPrivateLanGatewayHost("gateway.local"))
|
||||
assertFalse(isPrivateLanGatewayHost("100.64.0.9"))
|
||||
assertFalse(isPrivateLanGatewayHost("gateway.tailnet.ts.net"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun resolveTlsParamsForEndpoint_discoveryIpv6LoopbackWithoutHintsCanStayCleartext() {
|
||||
val endpoint =
|
||||
|
||||
@@ -31,6 +31,13 @@ class GatewayConfigResolverTest {
|
||||
assertNull(parsed)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseGatewayEndpointRejectsTailnetCleartextWsUrls() {
|
||||
val parsed = parseGatewayEndpoint("ws://100.64.0.9:18789")
|
||||
|
||||
assertNull(parsed)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseGatewayEndpointOmitsExplicitDefaultTlsPortFromDisplayUrl() {
|
||||
val parsed = parseGatewayEndpoint("https://gateway.example:443")
|
||||
@@ -91,6 +98,36 @@ class GatewayConfigResolverTest {
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseGatewayEndpointAllowsPrivateLanCleartextWsUrls() {
|
||||
val parsed = parseGatewayEndpoint("ws://192.168.1.20:18789")
|
||||
|
||||
assertEquals(
|
||||
GatewayEndpointConfig(
|
||||
host = "192.168.1.20",
|
||||
port = 18789,
|
||||
tls = false,
|
||||
displayUrl = "http://192.168.1.20:18789",
|
||||
),
|
||||
parsed,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseGatewayEndpointAllowsMdnsCleartextWsUrls() {
|
||||
val parsed = parseGatewayEndpoint("ws://gateway.local:18789")
|
||||
|
||||
assertEquals(
|
||||
GatewayEndpointConfig(
|
||||
host = "gateway.local",
|
||||
port = 18789,
|
||||
tls = false,
|
||||
displayUrl = "http://gateway.local:18789",
|
||||
),
|
||||
parsed,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseGatewayEndpointAllowsIpv6LoopbackCleartextWsUrls() {
|
||||
val parsed = parseGatewayEndpoint("ws://[::1]")
|
||||
@@ -126,10 +163,23 @@ class GatewayConfigResolverTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseGatewayEndpointRejectsLinkLocalIpv6ZoneCleartextWsUrls() {
|
||||
fun parseGatewayEndpointAllowsLinkLocalIpv6ZoneCleartextWsUrls() {
|
||||
val parsed = parseGatewayEndpoint("ws://[fe80::1%25eth0]")
|
||||
|
||||
assertNull(parsed)
|
||||
assertEquals("fe80::1%25eth0", parsed?.host)
|
||||
assertEquals(18789, parsed?.port)
|
||||
assertEquals(false, parsed?.tls)
|
||||
assertEquals("http://[fe80::1%25eth0]:18789", parsed?.displayUrl)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseGatewayEndpointAllowsSecureIpv6ZoneUrls() {
|
||||
val parsed = parseGatewayEndpoint("wss://[fe80::1%25wlan0]:443")
|
||||
|
||||
assertEquals("fe80::1%25wlan0", parsed?.host)
|
||||
assertEquals(443, parsed?.port)
|
||||
assertEquals(true, parsed?.tls)
|
||||
assertEquals("https://[fe80::1%25wlan0]", parsed?.displayUrl)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -220,6 +270,33 @@ class GatewayConfigResolverTest {
|
||||
assertNull(resolved)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun resolveScannedSetupCodeResultFlagsInsecureRemoteGateway() {
|
||||
val setupCode =
|
||||
encodeSetupCode("""{"url":"ws://attacker.example:18789","bootstrapToken":"bootstrap-1"}""")
|
||||
|
||||
val resolved = resolveScannedSetupCodeResult(setupCode)
|
||||
|
||||
assertNull(resolved.setupCode)
|
||||
assertEquals(GatewayEndpointValidationError.INSECURE_REMOTE_URL, resolved.error)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseGatewayEndpointResultFlagsInsecureRemoteGateway() {
|
||||
val parsed = parseGatewayEndpointResult("ws://gateway.example:18789")
|
||||
|
||||
assertNull(parsed.config)
|
||||
assertEquals(GatewayEndpointValidationError.INSECURE_REMOTE_URL, parsed.error)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseGatewayEndpointResultFlagsInsecureLanCleartextGateway() {
|
||||
val parsed = parseGatewayEndpointResult("ws://192.168.1.20:18789")
|
||||
|
||||
assertNull(parsed.config)
|
||||
assertEquals(GatewayEndpointValidationError.INSECURE_REMOTE_URL, parsed.error)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun decodeGatewaySetupCodeParsesBootstrapToken() {
|
||||
val setupCode =
|
||||
@@ -358,7 +435,7 @@ class GatewayConfigResolverTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun resolveGatewayConnectConfigRejectsNonLoopbackManualCleartextEndpoint() {
|
||||
fun resolveGatewayConnectConfigAllowsPrivateLanManualCleartextEndpoint() {
|
||||
val resolved =
|
||||
resolveGatewayConnectConfig(
|
||||
useSetupCode = false,
|
||||
@@ -374,7 +451,9 @@ class GatewayConfigResolverTest {
|
||||
fallbackPassword = "",
|
||||
)
|
||||
|
||||
assertNull(resolved)
|
||||
assertEquals("192.168.31.100", resolved?.host)
|
||||
assertEquals(18789, resolved?.port)
|
||||
assertEquals(false, resolved?.tls)
|
||||
}
|
||||
|
||||
private fun encodeSetupCode(payloadJson: String): String {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// Shared iOS version defaults.
|
||||
// Generated overrides live in build/Version.xcconfig (git-ignored).
|
||||
|
||||
OPENCLAW_GATEWAY_VERSION = 2026.4.2
|
||||
OPENCLAW_MARKETING_VERSION = 2026.4.2
|
||||
OPENCLAW_BUILD_VERSION = 2026040201
|
||||
OPENCLAW_GATEWAY_VERSION = 2026.4.3
|
||||
OPENCLAW_MARKETING_VERSION = 2026.4.3
|
||||
OPENCLAW_BUILD_VERSION = 2026040301
|
||||
|
||||
#include? "../build/Version.xcconfig"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"originHash" : "1c9c9d251b760ed3234ecff741a88eb4bf42315ad6f50ac7392b187cf226c16c",
|
||||
"originHash" : "fb90e7b1977f43661ac91681d16da11f9ddd85630407ef170eaada0a6ee39972",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "axorcist",
|
||||
@@ -24,7 +24,7 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/steipete/ElevenLabsKit",
|
||||
"state" : {
|
||||
"revision" : "c8679fbd37416a8780fe43be88a497ff16209e2d",
|
||||
"revision" : "7e3c948d8340abe3977014f3de020edf221e9269",
|
||||
"version" : "0.1.0"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -110,6 +110,7 @@ enum HostEnvSecurityPolicy {
|
||||
"PIP_TRUSTED_HOST",
|
||||
"UV_INDEX",
|
||||
"UV_INDEX_URL",
|
||||
"UV_PYTHON",
|
||||
"UV_EXTRA_INDEX_URL",
|
||||
"UV_DEFAULT_INDEX",
|
||||
"DOCKER_HOST",
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2026.4.2</string>
|
||||
<string>2026.4.3</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>2026040201</string>
|
||||
<string>2026040301</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>OpenClaw</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
|
||||
@@ -2893,6 +2893,78 @@ public struct SkillsBinsResult: Codable, Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
public struct SkillsSearchParams: Codable, Sendable {
|
||||
public let query: String?
|
||||
public let limit: Int?
|
||||
|
||||
public init(
|
||||
query: String?,
|
||||
limit: Int?)
|
||||
{
|
||||
self.query = query
|
||||
self.limit = limit
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case query
|
||||
case limit
|
||||
}
|
||||
}
|
||||
|
||||
public struct SkillsSearchResult: Codable, Sendable {
|
||||
public let results: [[String: AnyCodable]]
|
||||
|
||||
public init(
|
||||
results: [[String: AnyCodable]])
|
||||
{
|
||||
self.results = results
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case results
|
||||
}
|
||||
}
|
||||
|
||||
public struct SkillsDetailParams: Codable, Sendable {
|
||||
public let slug: String
|
||||
|
||||
public init(
|
||||
slug: String)
|
||||
{
|
||||
self.slug = slug
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case slug
|
||||
}
|
||||
}
|
||||
|
||||
public struct SkillsDetailResult: Codable, Sendable {
|
||||
public let skill: AnyCodable
|
||||
public let latestversion: AnyCodable?
|
||||
public let metadata: AnyCodable?
|
||||
public let owner: AnyCodable?
|
||||
|
||||
public init(
|
||||
skill: AnyCodable,
|
||||
latestversion: AnyCodable?,
|
||||
metadata: AnyCodable?,
|
||||
owner: AnyCodable?)
|
||||
{
|
||||
self.skill = skill
|
||||
self.latestversion = latestversion
|
||||
self.metadata = metadata
|
||||
self.owner = owner
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case skill
|
||||
case latestversion = "latestVersion"
|
||||
case metadata
|
||||
case owner
|
||||
}
|
||||
}
|
||||
|
||||
public struct CronJob: Codable, Sendable {
|
||||
public let id: String
|
||||
public let agentid: String?
|
||||
|
||||
@@ -2893,6 +2893,78 @@ public struct SkillsBinsResult: Codable, Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
public struct SkillsSearchParams: Codable, Sendable {
|
||||
public let query: String?
|
||||
public let limit: Int?
|
||||
|
||||
public init(
|
||||
query: String?,
|
||||
limit: Int?)
|
||||
{
|
||||
self.query = query
|
||||
self.limit = limit
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case query
|
||||
case limit
|
||||
}
|
||||
}
|
||||
|
||||
public struct SkillsSearchResult: Codable, Sendable {
|
||||
public let results: [[String: AnyCodable]]
|
||||
|
||||
public init(
|
||||
results: [[String: AnyCodable]])
|
||||
{
|
||||
self.results = results
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case results
|
||||
}
|
||||
}
|
||||
|
||||
public struct SkillsDetailParams: Codable, Sendable {
|
||||
public let slug: String
|
||||
|
||||
public init(
|
||||
slug: String)
|
||||
{
|
||||
self.slug = slug
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case slug
|
||||
}
|
||||
}
|
||||
|
||||
public struct SkillsDetailResult: Codable, Sendable {
|
||||
public let skill: AnyCodable
|
||||
public let latestversion: AnyCodable?
|
||||
public let metadata: AnyCodable?
|
||||
public let owner: AnyCodable?
|
||||
|
||||
public init(
|
||||
skill: AnyCodable,
|
||||
latestversion: AnyCodable?,
|
||||
metadata: AnyCodable?,
|
||||
owner: AnyCodable?)
|
||||
{
|
||||
self.skill = skill
|
||||
self.latestversion = latestversion
|
||||
self.metadata = metadata
|
||||
self.owner = owner
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case skill
|
||||
case latestversion = "latestVersion"
|
||||
case metadata
|
||||
case owner
|
||||
}
|
||||
}
|
||||
|
||||
public struct CronJob: Codable, Sendable {
|
||||
public let id: String
|
||||
public let agentid: String?
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
These baseline artifacts are generated from the repo-owned OpenClaw config schema and bundled channel/plugin metadata.
|
||||
|
||||
- Do not edit `config-baseline.json` by hand.
|
||||
- Do not edit `config-baseline.jsonl` by hand.
|
||||
- Do not edit `config-baseline.core.json` by hand.
|
||||
- Do not edit `config-baseline.channel.json` by hand.
|
||||
- Do not edit `config-baseline.plugin.json` by hand.
|
||||
- Do not edit `plugin-sdk-api-baseline.json` by hand.
|
||||
- Do not edit `plugin-sdk-api-baseline.jsonl` by hand.
|
||||
- Regenerate config baseline artifacts with `pnpm config:docs:gen`.
|
||||
|
||||
31578
docs/.generated/config-baseline.channel.json
Normal file
31578
docs/.generated/config-baseline.channel.json
Normal file
File diff suppressed because it is too large
Load Diff
28035
docs/.generated/config-baseline.core.json
Normal file
28035
docs/.generated/config-baseline.core.json
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
14013
docs/.generated/config-baseline.plugin.json
Normal file
14013
docs/.generated/config-baseline.plugin.json
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -215,6 +215,10 @@
|
||||
"source": "FAQ",
|
||||
"target": "常见问题"
|
||||
},
|
||||
{
|
||||
"source": "Ollama Web Search",
|
||||
"target": "Ollama Web 搜索"
|
||||
},
|
||||
{
|
||||
"source": "onboarding",
|
||||
"target": "新手引导"
|
||||
|
||||
@@ -37,9 +37,7 @@ openclaw cron runs --id <job-id>
|
||||
- All cron executions create [background task](/automation/tasks) records.
|
||||
- One-shot jobs (`--at`) auto-delete after success by default.
|
||||
|
||||
## Adding jobs
|
||||
|
||||
### Schedule types
|
||||
## Schedule types
|
||||
|
||||
| Kind | CLI flag | Description |
|
||||
| ------- | --------- | ------------------------------------------------------- |
|
||||
@@ -51,7 +49,35 @@ Timestamps without a timezone are treated as UTC. Add `--tz America/New_York` fo
|
||||
|
||||
Recurring top-of-hour expressions are automatically staggered by up to 5 minutes to reduce load spikes. Use `--exact` to force precise timing or `--stagger 30s` for an explicit window.
|
||||
|
||||
### CLI examples
|
||||
## Execution styles
|
||||
|
||||
| Style | `--session` value | Runs in | Best for |
|
||||
| --------------- | ------------------- | ------------------------ | ------------------------------- |
|
||||
| Main session | `main` | Next heartbeat turn | Reminders, system events |
|
||||
| Isolated | `isolated` | Dedicated `cron:<jobId>` | Reports, background chores |
|
||||
| Current session | `current` | Bound at creation time | Context-aware recurring work |
|
||||
| Custom session | `session:custom-id` | Persistent named session | Workflows that build on history |
|
||||
|
||||
**Main session** jobs enqueue a system event and optionally wake the heartbeat (`--wake now` or `--wake next-heartbeat`). **Isolated** jobs run a dedicated agent turn with a fresh session. **Custom sessions** (`session:xxx`) persist context across runs, enabling workflows like daily standups that build on previous summaries.
|
||||
|
||||
### Payload options for isolated jobs
|
||||
|
||||
- `--message`: prompt text (required for isolated)
|
||||
- `--model` / `--thinking`: model and thinking level overrides
|
||||
- `--light-context`: skip workspace bootstrap file injection
|
||||
- `--tools exec,read`: restrict which tools the job can use
|
||||
|
||||
## Delivery and output
|
||||
|
||||
| Mode | What happens |
|
||||
| ---------- | -------------------------------------------------------- |
|
||||
| `announce` | Deliver summary to target channel (default for isolated) |
|
||||
| `webhook` | POST finished event payload to a URL |
|
||||
| `none` | Internal only, no delivery |
|
||||
|
||||
Use `--announce --channel telegram --to "-1001234567890"` for channel delivery. For Telegram forum topics, use `-1001234567890:topic:123`. Slack/Discord/Mattermost targets should use explicit prefixes (`channel:<id>`, `user:<id>`).
|
||||
|
||||
## CLI examples
|
||||
|
||||
One-shot reminder (main session):
|
||||
|
||||
@@ -92,40 +118,6 @@ openclaw cron add \
|
||||
--announce
|
||||
```
|
||||
|
||||
## Execution styles
|
||||
|
||||
| Style | `--session` value | Runs in | Best for |
|
||||
| --------------- | ------------------- | ------------------------ | ------------------------------- |
|
||||
| Main session | `main` | Next heartbeat turn | Reminders, system events |
|
||||
| Isolated | `isolated` | Dedicated `cron:<jobId>` | Reports, background chores |
|
||||
| Current session | `current` | Bound at creation time | Context-aware recurring work |
|
||||
| Custom session | `session:custom-id` | Persistent named session | Workflows that build on history |
|
||||
|
||||
**Main session** jobs enqueue a system event and optionally wake the heartbeat (`--wake now` or `--wake next-heartbeat`). They use `payload.kind = "systemEvent"`.
|
||||
|
||||
**Isolated** jobs run a dedicated agent turn. Each run starts a fresh session (no carry-over) unless using a custom session. Default delivery is `announce` (summary to chat).
|
||||
|
||||
**Custom sessions** (`session:xxx`) persist context across runs, enabling workflows like daily standups that build on previous summaries.
|
||||
|
||||
### Payload options for isolated jobs
|
||||
|
||||
- `--message`: prompt text (required for isolated)
|
||||
- `--model` / `--thinking`: model and thinking level overrides
|
||||
- `--light-context`: skip workspace bootstrap file injection
|
||||
- `--tools exec,read`: restrict which tools the job can use
|
||||
|
||||
## Delivery and output
|
||||
|
||||
| Mode | What happens |
|
||||
| ---------- | -------------------------------------------------------- |
|
||||
| `announce` | Deliver summary to target channel (default for isolated) |
|
||||
| `webhook` | POST finished event payload to a URL |
|
||||
| `none` | Internal only, no delivery |
|
||||
|
||||
Use `--announce --channel telegram --to "-1001234567890"` for channel delivery.
|
||||
|
||||
For Telegram forum topics, use `-1001234567890:topic:123`. Slack/Discord/Mattermost targets should use explicit prefixes (`channel:<id>`, `user:<id>`).
|
||||
|
||||
## Webhooks
|
||||
|
||||
Gateway can expose HTTP webhook endpoints for external triggers. Enable in config:
|
||||
@@ -247,13 +239,6 @@ gog gmail watch start \
|
||||
}
|
||||
```
|
||||
|
||||
### Test
|
||||
|
||||
```bash
|
||||
gog gmail send --account openclaw@gmail.com --to openclaw@gmail.com --subject "watch test" --body "ping"
|
||||
gog gmail watch status --account openclaw@gmail.com
|
||||
```
|
||||
|
||||
## Managing jobs
|
||||
|
||||
```bash
|
||||
@@ -280,33 +265,6 @@ openclaw cron add --name "Ops sweep" --cron "0 6 * * *" --session isolated --mes
|
||||
openclaw cron edit <jobId> --clear-agent
|
||||
```
|
||||
|
||||
## JSON schema for tool calls
|
||||
|
||||
One-shot main session job:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Reminder",
|
||||
"schedule": { "kind": "at", "at": "2026-02-01T16:00:00Z" },
|
||||
"sessionTarget": "main",
|
||||
"wakeMode": "now",
|
||||
"payload": { "kind": "systemEvent", "text": "Reminder text" },
|
||||
"deleteAfterRun": true
|
||||
}
|
||||
```
|
||||
|
||||
Recurring isolated job with delivery:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Morning brief",
|
||||
"schedule": { "kind": "cron", "expr": "0 7 * * *", "tz": "America/Los_Angeles" },
|
||||
"sessionTarget": "isolated",
|
||||
"payload": { "kind": "agentTurn", "message": "Summarize overnight updates." },
|
||||
"delivery": { "mode": "announce", "channel": "slack", "to": "channel:C1234567890" }
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
```json5
|
||||
@@ -329,16 +287,11 @@ Recurring isolated job with delivery:
|
||||
|
||||
Disable cron: `cron.enabled: false` or `OPENCLAW_SKIP_CRON=1`.
|
||||
|
||||
### Retry policy
|
||||
**One-shot retry**: transient errors (rate limit, overload, network, server error) retry up to 3 times with exponential backoff. Permanent errors disable immediately.
|
||||
|
||||
**One-shot jobs**: retry transient errors (rate limit, overload, network, server error) up to 3 times with exponential backoff. Permanent errors disable immediately.
|
||||
**Recurring retry**: exponential backoff (30s to 60m) between retries. Backoff resets after the next successful run.
|
||||
|
||||
**Recurring jobs**: exponential backoff (30s to 60m) between retries. Backoff resets after the next successful run.
|
||||
|
||||
### Maintenance
|
||||
|
||||
- `cron.sessionRetention` (default `24h`): prune isolated run-session entries.
|
||||
- `cron.runLog.maxBytes` / `cron.runLog.keepLines`: auto-prune run-log files.
|
||||
**Maintenance**: `cron.sessionRetention` (default `24h`) prunes isolated run-session entries. `cron.runLog.maxBytes` / `cron.runLog.keepLines` auto-prune run-log files.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
@@ -364,16 +317,10 @@ openclaw doctor
|
||||
|
||||
### Cron fired but no delivery
|
||||
|
||||
- Run succeeded but delivery mode is `none` means no external message is expected.
|
||||
- Delivery mode is `none` means no external message is expected.
|
||||
- Delivery target missing/invalid (`channel`/`to`) means outbound was skipped.
|
||||
- Channel auth errors (`unauthorized`, `Forbidden`) mean delivery was blocked by credentials.
|
||||
|
||||
### Heartbeat suppressed or skipped
|
||||
|
||||
- `reason=quiet-hours`: outside `activeHours`.
|
||||
- `requests-in-flight`: main lane busy, heartbeat deferred.
|
||||
- `empty-heartbeat-file`: `HEARTBEAT.md` has no actionable content and no cron event is queued.
|
||||
|
||||
### Timezone gotchas
|
||||
|
||||
- Cron without `--tz` uses the gateway host timezone.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -15,66 +15,90 @@ OpenClaw runs work in the background through tasks, scheduled jobs, event hooks,
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A{Run on a schedule?} -->|Yes| B{Exact timing needed?}
|
||||
A -->|No| C{React to events?}
|
||||
B -->|Yes| D[Cron]
|
||||
B -->|No| E[Heartbeat]
|
||||
C -->|Yes| F[Hooks]
|
||||
C -->|No| G[Standing Orders]
|
||||
START([What do you need?]) --> Q1{Schedule work?}
|
||||
START --> Q2{Track detached work?}
|
||||
START --> Q3{Orchestrate multi-step flows?}
|
||||
START --> Q4{React to lifecycle events?}
|
||||
START --> Q5{Give the agent persistent instructions?}
|
||||
|
||||
Q1 -->|Yes| Q1a{Exact timing or flexible?}
|
||||
Q1a -->|Exact| CRON["Scheduled Tasks (Cron)"]
|
||||
Q1a -->|Flexible| HEARTBEAT[Heartbeat]
|
||||
|
||||
Q2 -->|Yes| TASKS[Background Tasks]
|
||||
Q3 -->|Yes| FLOW[Task Flow]
|
||||
Q4 -->|Yes| HOOKS[Hooks]
|
||||
Q5 -->|Yes| SO[Standing Orders]
|
||||
```
|
||||
|
||||
| Use case | Recommended | Why |
|
||||
| ------------------------------------ | ------------------- | ---------------------------------------- |
|
||||
| Check inbox every 30 min | Heartbeat | Batches with other checks, context-aware |
|
||||
| Send daily report at 9 AM sharp | Cron (isolated) | Exact timing needed |
|
||||
| Monitor calendar for upcoming events | Heartbeat | Natural fit for periodic awareness |
|
||||
| Run weekly deep analysis | Cron (isolated) | Standalone task, can use different model |
|
||||
| Remind me in 20 minutes | Cron (main, `--at`) | One-shot with precise timing |
|
||||
| React to commands or lifecycle | Hooks | Event-driven, runs custom scripts |
|
||||
| Persistent agent instructions | Standing orders | Injected into every session |
|
||||
| Use case | Recommended | Why |
|
||||
| --------------------------------------- | ---------------------- | ------------------------------------------------ |
|
||||
| Send daily report at 9 AM sharp | Scheduled Tasks (Cron) | Exact timing, isolated execution |
|
||||
| Remind me in 20 minutes | Scheduled Tasks (Cron) | One-shot with precise timing (`--at`) |
|
||||
| Run weekly deep analysis | Scheduled Tasks (Cron) | Standalone task, can use different model |
|
||||
| Check inbox every 30 min | Heartbeat | Batches with other checks, context-aware |
|
||||
| Monitor calendar for upcoming events | Heartbeat | Natural fit for periodic awareness |
|
||||
| Inspect status of a subagent or ACP run | Background Tasks | Tasks ledger tracks all detached work |
|
||||
| Audit what ran and when | Background Tasks | `openclaw tasks list` and `openclaw tasks audit` |
|
||||
| Multi-step research then summarize | Task Flow | Durable orchestration with revision tracking |
|
||||
| Run a script on session reset | Hooks | Event-driven, fires on lifecycle events |
|
||||
| Execute code on every tool call | Hooks | Hooks can filter by event type |
|
||||
| Always check compliance before replying | Standing Orders | Injected into every session automatically |
|
||||
|
||||
### Scheduled Tasks (Cron) vs Heartbeat
|
||||
|
||||
| Dimension | Scheduled Tasks (Cron) | Heartbeat |
|
||||
| --------------- | ----------------------------------- | ------------------------------------- |
|
||||
| Timing | Exact (cron expressions, one-shot) | Approximate (default every 30 min) |
|
||||
| Session context | Fresh (isolated) or shared | Full main-session context |
|
||||
| Task records | Always created | Never created |
|
||||
| Delivery | Channel, webhook, or silent | Inline in main session |
|
||||
| Best for | Reports, reminders, background jobs | Inbox checks, calendar, notifications |
|
||||
|
||||
Use Scheduled Tasks (Cron) when you need precise timing or isolated execution. Use Heartbeat when the work benefits from full session context and approximate timing is fine.
|
||||
|
||||
## Core concepts
|
||||
|
||||
### Scheduled tasks (cron)
|
||||
|
||||
Cron is the Gateway's built-in scheduler for precise timing. It persists jobs, wakes the agent at the right time, and can deliver output to a chat channel or webhook endpoint. Supports one-shot reminders, recurring expressions, and inbound webhook triggers.
|
||||
|
||||
See [Scheduled Tasks](/automation/cron-jobs).
|
||||
|
||||
### Tasks
|
||||
|
||||
The background task ledger tracks all detached work: ACP runs, subagent spawns, isolated cron executions, and CLI operations. Tasks are records, not schedulers. Use `openclaw tasks list` and `openclaw tasks audit` to inspect them.
|
||||
|
||||
See [Background Tasks](/automation/tasks).
|
||||
|
||||
### Scheduled tasks (cron)
|
||||
|
||||
Cron is the Gateway's built-in scheduler for precise timing. It persists jobs, wakes the agent at the right time, and can deliver output to a chat channel or webhook. Supports one-shot reminders, recurring expressions, and inbound webhook triggers.
|
||||
|
||||
See [Scheduled Tasks](/automation/cron-jobs).
|
||||
|
||||
### Task Flow
|
||||
|
||||
Task Flow is the flow orchestration substrate above background tasks. It manages durable multi-step flows with managed and mirrored sync modes, revision tracking, and `openclaw tasks flow list|show|cancel` for inspection.
|
||||
|
||||
See [Task Flow](/automation/taskflow).
|
||||
|
||||
### Heartbeat
|
||||
|
||||
Heartbeat is a periodic main-session turn (default every 30 minutes). It batches multiple checks (inbox, calendar, notifications) in one agent turn with full session context. Heartbeat turns do not create task records. Use `HEARTBEAT.md` to define what the agent checks.
|
||||
|
||||
See [Heartbeat](/gateway/heartbeat).
|
||||
|
||||
### Hooks
|
||||
|
||||
Hooks are event-driven scripts triggered by agent lifecycle events (`/new`, `/reset`, `/stop`), session compaction, gateway startup, message flow, and tool calls. Hooks are automatically discovered from directories and can be managed with `openclaw hooks`.
|
||||
|
||||
See [Hooks](/automation/hooks).
|
||||
|
||||
### Standing orders
|
||||
|
||||
Standing orders grant the agent permanent operating authority for defined programs. They live in workspace files (typically `AGENTS.md`) and are injected into every session. Combine with cron for time-based enforcement.
|
||||
|
||||
See [Standing Orders](/automation/standing-orders).
|
||||
|
||||
### Hooks
|
||||
|
||||
Hooks are event-driven scripts triggered by agent lifecycle events (`/new`, `/reset`, `/stop`), session compaction, gateway startup, message flow, and tool calls. Hooks are automatically discovered from directories and can be managed with `openclaw hooks`.
|
||||
|
||||
See [Hooks](/automation/hooks).
|
||||
|
||||
### Heartbeat
|
||||
|
||||
Heartbeat is a periodic main-session turn (default every 30 minutes). It batches multiple checks (inbox, calendar, notifications) in one agent turn with full session context. Heartbeat turns do not create task records. Use `HEARTBEAT.md` to define what the agent checks.
|
||||
|
||||
See [Heartbeat](/gateway/heartbeat).
|
||||
|
||||
## How they work together
|
||||
|
||||
- **Heartbeat** handles routine monitoring (inbox, calendar, notifications) in one batched turn every 30 minutes.
|
||||
- **Cron** handles precise schedules (daily reports, weekly reviews) and one-shot reminders. All cron executions create task records.
|
||||
- **Heartbeat** handles routine monitoring (inbox, calendar, notifications) in one batched turn every 30 minutes.
|
||||
- **Hooks** react to specific events (tool calls, session resets, compaction) with custom scripts.
|
||||
- **Standing orders** give the agent persistent context and authority boundaries.
|
||||
- **Task Flow** coordinates multi-step flows above individual tasks.
|
||||
@@ -82,6 +106,10 @@ See [Standing Orders](/automation/standing-orders).
|
||||
|
||||
## Related
|
||||
|
||||
- [Task Flow](/automation/taskflow) — flow orchestration above tasks
|
||||
- [Scheduled Tasks](/automation/cron-jobs) — precise scheduling and one-shot reminders
|
||||
- [Background Tasks](/automation/tasks) — task ledger for all detached work
|
||||
- [Task Flow](/automation/taskflow) — durable multi-step flow orchestration
|
||||
- [Hooks](/automation/hooks) — event-driven lifecycle scripts
|
||||
- [Standing Orders](/automation/standing-orders) — persistent agent instructions
|
||||
- [Heartbeat](/gateway/heartbeat) — periodic main-session turns
|
||||
- [Configuration Reference](/gateway/configuration-reference) — all config keys
|
||||
|
||||
@@ -11,16 +11,45 @@ title: "Task Flow"
|
||||
|
||||
Task Flow is the flow orchestration substrate that sits above [background tasks](/automation/tasks). It manages durable multi-step flows with their own state, revision tracking, and sync semantics while individual tasks remain the unit of detached work.
|
||||
|
||||
## When to use Task Flow
|
||||
|
||||
Use Task Flow when work spans multiple sequential or branching steps and you need durable progress tracking across gateway restarts. For single background operations, a plain [task](/automation/tasks) is sufficient.
|
||||
|
||||
| Scenario | Use |
|
||||
| ------------------------------------- | -------------------- |
|
||||
| Single background job | Plain task |
|
||||
| Multi-step pipeline (A then B then C) | Task Flow (managed) |
|
||||
| Observe externally created tasks | Task Flow (mirrored) |
|
||||
| One-shot reminder | Cron job |
|
||||
|
||||
## Sync modes
|
||||
|
||||
Task Flow supports two sync modes:
|
||||
### Managed mode
|
||||
|
||||
- **Managed** — Task Flow owns the lifecycle end-to-end, creating and driving tasks as flow steps progress.
|
||||
- **Mirrored** — Task Flow observes externally created tasks and keeps flow state in sync without taking ownership of task creation.
|
||||
Task Flow owns the lifecycle end-to-end. It creates tasks as flow steps, drives them to completion, and advances the flow state automatically.
|
||||
|
||||
Example: a weekly report flow that (1) gathers data, (2) generates the report, and (3) delivers it. Task Flow creates each step as a background task, waits for completion, then moves to the next step.
|
||||
|
||||
```
|
||||
Flow: weekly-report
|
||||
Step 1: gather-data → task created → succeeded
|
||||
Step 2: generate-report → task created → succeeded
|
||||
Step 3: deliver → task created → running
|
||||
```
|
||||
|
||||
### Mirrored mode
|
||||
|
||||
Task Flow observes externally created tasks and keeps flow state in sync without taking ownership of task creation. This is useful when tasks originate from cron jobs, CLI commands, or other sources and you want a unified view of their progress as a flow.
|
||||
|
||||
Example: three independent cron jobs that together form a "morning ops" routine. A mirrored flow tracks their collective progress without controlling when or how they run.
|
||||
|
||||
## Durable state and revision tracking
|
||||
|
||||
Each flow persists its own state and tracks revisions so progress survives gateway restarts. Revision tracking enables conflict detection when multiple sources attempt to advance the same flow.
|
||||
Each flow persists its own state and tracks revisions so progress survives gateway restarts. Revision tracking enables conflict detection when multiple sources attempt to advance the same flow concurrently.
|
||||
|
||||
## Cancel behavior
|
||||
|
||||
`openclaw tasks flow cancel` sets a sticky cancel intent on the flow. Active tasks within the flow are cancelled, and no new steps are started. The cancel intent persists across restarts, so a cancelled flow stays cancelled even if the gateway restarts before all child tasks have terminated.
|
||||
|
||||
## CLI commands
|
||||
|
||||
@@ -31,13 +60,15 @@ openclaw tasks flow list
|
||||
# Show details for a specific flow
|
||||
openclaw tasks flow show <lookup>
|
||||
|
||||
# Cancel a running flow
|
||||
# Cancel a running flow and its active tasks
|
||||
openclaw tasks flow cancel <lookup>
|
||||
```
|
||||
|
||||
- `openclaw tasks flow list` — shows tracked flows with status and sync mode
|
||||
- `openclaw tasks flow show <lookup>` — inspect one flow by flow id or lookup key
|
||||
- `openclaw tasks flow cancel <lookup>` — cancel a running flow and its active tasks
|
||||
| Command | Description |
|
||||
| --------------------------------- | --------------------------------------------- |
|
||||
| `openclaw tasks flow list` | Shows tracked flows with status and sync mode |
|
||||
| `openclaw tasks flow show <id>` | Inspect one flow by flow id or lookup key |
|
||||
| `openclaw tasks flow cancel <id>` | Cancel a running flow and its active tasks |
|
||||
|
||||
## How flows relate to tasks
|
||||
|
||||
|
||||
@@ -643,6 +643,8 @@ Default slash command settings:
|
||||
- thread config inherits parent channel config unless a thread-specific entry exists
|
||||
|
||||
Channel topics are injected as **untrusted** context (not as system prompt).
|
||||
Reply and quoted-message context currently stays as received.
|
||||
Discord allowlists primarily gate who can trigger the agent, not a full supplemental-context redaction boundary.
|
||||
|
||||
</Accordion>
|
||||
|
||||
|
||||
@@ -36,6 +36,28 @@ requireMention? yes -> mentioned? no -> store for context only
|
||||
otherwise -> reply
|
||||
```
|
||||
|
||||
## Context visibility and allowlists
|
||||
|
||||
Two different controls are involved in group safety:
|
||||
|
||||
- **Trigger authorization**: who can trigger the agent (`groupPolicy`, `groups`, `groupAllowFrom`, channel-specific allowlists).
|
||||
- **Context visibility**: what supplemental context is injected into the model (reply text, quotes, thread history, forwarded metadata).
|
||||
|
||||
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:
|
||||
|
||||
- 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.
|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
If you want...
|
||||
@@ -298,6 +320,9 @@ Notes:
|
||||
|
||||
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.
|
||||
|
||||
Common intents (copy/paste):
|
||||
|
||||
1. Disable all group replies
|
||||
|
||||
@@ -123,8 +123,11 @@ Example for account `ops`:
|
||||
|
||||
For normalized account ID `ops-bot`, use:
|
||||
|
||||
- `MATRIX_OPS_BOT_HOMESERVER`
|
||||
- `MATRIX_OPS_BOT_ACCESS_TOKEN`
|
||||
- `MATRIX_OPS_X2D_BOT_HOMESERVER`
|
||||
- `MATRIX_OPS_X2D_BOT_ACCESS_TOKEN`
|
||||
|
||||
Matrix escapes punctuation in account IDs to keep scoped env vars collision-free.
|
||||
For example, `-` becomes `_X2D_`, so `ops-prod` maps to `MATRIX_OPS_X2D_PROD_*`.
|
||||
|
||||
The interactive wizard only offers the env-var shortcut when those auth env vars are already present and the selected account does not already have Matrix auth saved in config.
|
||||
|
||||
@@ -190,6 +193,9 @@ done:
|
||||
- Media replies still send attachments normally. If a stale preview can no longer be reused safely, OpenClaw redacts it before sending the final media reply.
|
||||
- Preview edits cost extra Matrix API calls. Leave streaming off if you want the most conservative rate-limit behavior.
|
||||
|
||||
`blockStreaming` does not enable draft previews by itself.
|
||||
Use `streaming: "partial"` for preview edits; then add `blockStreaming: true` only if you also want completed assistant blocks to remain visible as separate progress messages.
|
||||
|
||||
## Encryption and verification
|
||||
|
||||
In encrypted (E2EE) rooms, outbound image events use `thumbnail_file` so image previews are encrypted alongside the full attachment. Unencrypted rooms still use plain `thumbnail_url`. No configuration is needed — the plugin detects E2EE state automatically.
|
||||
@@ -422,6 +428,7 @@ OpenClaw currently provides that in Node by:
|
||||
- using `fake-indexeddb` as the IndexedDB API shim expected by the SDK
|
||||
- restoring the Rust crypto IndexedDB contents from `crypto-idb-snapshot.json` before `initRustCrypto`
|
||||
- persisting the updated IndexedDB contents back to `crypto-idb-snapshot.json` after init and during runtime
|
||||
- serializing snapshot restore and persist against `crypto-idb-snapshot.json` with an advisory file lock so gateway runtime persistence and CLI maintenance do not race on the same snapshot file
|
||||
|
||||
This is compatibility/storage plumbing, not a custom crypto implementation.
|
||||
The snapshot file is sensitive runtime state and is stored with restrictive file permissions.
|
||||
@@ -589,7 +596,17 @@ Current behavior:
|
||||
- Matrix room history is pending-only: OpenClaw buffers room messages that did not trigger a reply yet, then snapshots that window when a mention or other trigger arrives.
|
||||
- The current trigger message is not included in `InboundHistory`; it stays in the main inbound body for that turn.
|
||||
- Retries of the same Matrix event reuse the original history snapshot instead of drifting forward to newer room messages.
|
||||
- Fetched room context (including reply and thread context lookups) is filtered by sender allowlists (`groupAllowFrom`), so non-allowlisted messages are excluded from agent context.
|
||||
|
||||
## Context visibility
|
||||
|
||||
Matrix supports the shared `contextVisibility` control for supplemental room context such as fetched reply text, thread roots, and pending history.
|
||||
|
||||
- `contextVisibility: "all"` is the default. Supplemental context is kept as received.
|
||||
- `contextVisibility: "allowlist"` filters supplemental context to senders allowed by the active room/user allowlist checks.
|
||||
- `contextVisibility: "allowlist_quote"` behaves like `allowlist`, but still keeps one explicit quoted reply.
|
||||
|
||||
This setting affects supplemental context visibility, not whether the inbound message itself can trigger a reply.
|
||||
Trigger authorization still comes from `groupPolicy`, `groups`, `groupAllowFrom`, and DM policy settings.
|
||||
|
||||
## DM and room policy example
|
||||
|
||||
@@ -627,6 +644,36 @@ If an unapproved Matrix user keeps messaging you before approval, OpenClaw reuse
|
||||
|
||||
See [Pairing](/channels/pairing) for the shared DM pairing flow and storage layout.
|
||||
|
||||
## Exec approvals
|
||||
|
||||
Matrix can act as an exec approval client for a Matrix account.
|
||||
|
||||
- `channels.matrix.execApprovals.enabled`
|
||||
- `channels.matrix.execApprovals.approvers` (optional; falls back to `channels.matrix.dm.allowFrom`)
|
||||
- `channels.matrix.execApprovals.target` (`dm` | `channel` | `both`, default: `dm`)
|
||||
- `channels.matrix.execApprovals.agentFilter`
|
||||
- `channels.matrix.execApprovals.sessionFilter`
|
||||
|
||||
Approvers must be Matrix user IDs such as `@owner:example.org`. Matrix auto-enables native exec approvals when `enabled` is unset or `"auto"` and at least one approver can be resolved, either from `execApprovals.approvers` or from `channels.matrix.dm.allowFrom`. Set `enabled: false` to disable Matrix as a native approval client explicitly. Approval requests otherwise fall back to other configured approval routes or the exec approval fallback policy.
|
||||
|
||||
Delivery rules:
|
||||
|
||||
- `target: "dm"` sends approval prompts to approver DMs
|
||||
- `target: "channel"` sends the prompt back to the originating Matrix room or DM
|
||||
- `target: "both"` sends to approver DMs and the originating Matrix room or DM
|
||||
|
||||
Matrix uses text approval prompts today. Approvers resolve them with `/approve <id> allow-once`, `/approve <id> allow-always`, or `/approve <id> deny`.
|
||||
|
||||
Only resolved approvers can approve or deny. Channel delivery includes the command text, so only enable `channel` or `both` in trusted rooms.
|
||||
|
||||
Matrix approval prompts reuse the shared core approval planner. The Matrix-specific surface is transport only: room/DM routing and message send/update/delete behavior.
|
||||
|
||||
Per-account override:
|
||||
|
||||
- `channels.matrix.accounts.<account>.execApprovals`
|
||||
|
||||
Related docs: [Exec approvals](/tools/exec-approvals)
|
||||
|
||||
## Multi-account example
|
||||
|
||||
```json5
|
||||
@@ -748,13 +795,16 @@ Live directory lookup uses the logged-in Matrix account:
|
||||
- `initialSyncLimit`: startup sync event limit.
|
||||
- `encryption`: enable E2EE.
|
||||
- `allowlistOnly`: force allowlist-only behavior for DMs and rooms.
|
||||
- `allowBots`: allow messages from other configured OpenClaw Matrix accounts (`true` or `"mentions"`).
|
||||
- `groupPolicy`: `open`, `allowlist`, or `disabled`.
|
||||
- `contextVisibility`: supplemental room-context visibility mode (`all`, `allowlist`, `allowlist_quote`).
|
||||
- `groupAllowFrom`: allowlist of user IDs for room traffic.
|
||||
- `groupAllowFrom` entries should be full Matrix user IDs. Unresolved names are ignored at runtime.
|
||||
- `historyLimit`: max room messages to include as group history context. Falls back to `messages.groupChat.historyLimit`. Set `0` to disable.
|
||||
- `replyToMode`: `off`, `first`, or `all`.
|
||||
- `streaming`: `off` (default) or `partial`. `partial` enables single-message draft previews with edit-in-place updates.
|
||||
- `blockStreaming`: `true` enables separate progress messages; when unset, Matrix keeps `streaming: "off"` as final-only delivery.
|
||||
- `markdown`: optional Markdown rendering configuration for outbound Matrix text.
|
||||
- `streaming`: `off` (default), `partial`, `true`, or `false`. `partial` and `true` enable single-message draft previews with edit-in-place updates.
|
||||
- `blockStreaming`: `true` enables separate progress messages for completed assistant blocks while draft preview streaming is active.
|
||||
- `threadReplies`: `off`, `inbound`, or `always`.
|
||||
- `threadBindings`: per-channel overrides for thread-bound session routing and lifecycle.
|
||||
- `startupVerification`: automatic self-verification request mode on startup (`if-unverified`, `off`).
|
||||
@@ -771,8 +821,18 @@ Live directory lookup uses the logged-in Matrix account:
|
||||
- `dm`: DM policy block (`enabled`, `policy`, `allowFrom`, `threadReplies`).
|
||||
- `dm.allowFrom` entries should be full Matrix user IDs unless you already resolved them through live directory lookup.
|
||||
- `dm.threadReplies`: DM-only thread policy override (`off`, `inbound`, `always`). It overrides the top-level `threadReplies` setting for both reply placement and session isolation in DMs.
|
||||
- `execApprovals`: Matrix-native exec approval delivery (`enabled`, `approvers`, `target`, `agentFilter`, `sessionFilter`).
|
||||
- `execApprovals.approvers`: Matrix user IDs allowed to approve exec requests. Optional when `dm.allowFrom` already identifies the approvers.
|
||||
- `execApprovals.target`: `dm | channel | both` (default: `dm`).
|
||||
- `accounts`: named per-account overrides. Top-level `channels.matrix` values act as defaults for these entries.
|
||||
- `groups`: per-room policy map. Prefer room IDs or aliases; unresolved room names are ignored at runtime. Session/group identity uses the stable room ID after resolution, while human-readable labels still come from room names.
|
||||
- `groups.<room>.account`: restrict one inherited room entry to a specific Matrix account in multi-account setups.
|
||||
- `groups.<room>.allowBots`: room-level override for configured-bot senders (`true` or `"mentions"`).
|
||||
- `groups.<room>.users`: per-room sender allowlist.
|
||||
- `groups.<room>.tools`: per-room tool allow/deny overrides.
|
||||
- `groups.<room>.autoReply`: room-level mention-gating override. `true` disables mention requirements for that room; `false` forces them back on.
|
||||
- `groups.<room>.skills`: optional room-level skill filter.
|
||||
- `groups.<room>.systemPrompt`: optional room-level system prompt snippet.
|
||||
- `rooms`: legacy alias for `groups`.
|
||||
- `actions`: per-action tool gating (`messages`, `reactions`, `pins`, `profile`, `memberInfo`, `channelInfo`, `verification`).
|
||||
|
||||
|
||||
@@ -302,6 +302,8 @@ The action is gated by `channels.msteams.actions.memberInfo` (default: enabled w
|
||||
- `channels.msteams.historyLimit` controls how many recent channel/group messages are wrapped into the prompt.
|
||||
- Falls back to `messages.groupChat.historyLimit`. Set `0` to disable (default 50).
|
||||
- Fetched thread history is filtered by sender allowlists (`allowFrom` / `groupAllowFrom`), so thread context seeding only includes messages from allowed senders.
|
||||
- Quoted attachment context (`ReplyTo*` derived from Teams reply HTML) is currently passed as received.
|
||||
- In other words, allowlists gate who can trigger the agent; only specific supplemental context paths are filtered today.
|
||||
- DM history can be limited with `channels.msteams.dmHistoryLimit` (user turns). Per-user overrides: `channels.msteams.dms["<user_id>"].historyLimit`.
|
||||
|
||||
## Current Teams RSC Permissions (Manifest)
|
||||
|
||||
@@ -54,6 +54,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).
|
||||
|
||||
## 2) Node device pairing (iOS/Android/macOS/headless nodes)
|
||||
|
||||
Nodes connect to the Gateway as **devices** with `role: node`. The Gateway
|
||||
|
||||
@@ -354,6 +354,7 @@ Current Slack message actions include `send`, `upload-file`, `download-file`, `r
|
||||
- Assistant thread status updates (for "is typing..." indicators in threads) use `assistant.threads.setStatus` and require bot scope `assistant:write`.
|
||||
- `channel_id_changed` can migrate channel config keys when `configWrites` is enabled.
|
||||
- Channel topic/purpose metadata is treated as untrusted context and can be injected into routing context.
|
||||
- Thread starter and initial thread-history context seeding are filtered by configured sender allowlists when applicable.
|
||||
- Block actions and modal interactions emit structured `Slack interaction: ...` system events with rich payload fields:
|
||||
- block actions: selected values, labels, picker values, and `workflow_*` metadata
|
||||
- modal `view_submission` and `view_closed` events with routed channel metadata and form inputs
|
||||
@@ -391,7 +392,7 @@ Notes:
|
||||
## Manifest and scope checklist
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Slack app manifest example">
|
||||
<Accordion title="Slack app manifest example" defaultOpen>
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -402,7 +403,7 @@ Notes:
|
||||
"features": {
|
||||
"bot_user": {
|
||||
"display_name": "OpenClaw",
|
||||
"always_online": false
|
||||
"always_online": true
|
||||
},
|
||||
"app_home": {
|
||||
"messages_tab_enabled": true,
|
||||
@@ -419,27 +420,28 @@ Notes:
|
||||
"oauth_config": {
|
||||
"scopes": {
|
||||
"bot": [
|
||||
"chat:write",
|
||||
"app_mentions:read",
|
||||
"assistant:write",
|
||||
"channels:history",
|
||||
"channels:read",
|
||||
"chat:write",
|
||||
"commands",
|
||||
"emoji:read",
|
||||
"files:read",
|
||||
"files:write",
|
||||
"groups:history",
|
||||
"groups:read",
|
||||
"im:history",
|
||||
"im:read",
|
||||
"im:write",
|
||||
"mpim:history",
|
||||
"mpim:read",
|
||||
"mpim:write",
|
||||
"users:read",
|
||||
"app_mentions:read",
|
||||
"assistant:write",
|
||||
"reactions:read",
|
||||
"reactions:write",
|
||||
"pins:read",
|
||||
"pins:write",
|
||||
"emoji:read",
|
||||
"commands",
|
||||
"files:read",
|
||||
"files:write"
|
||||
"reactions:read",
|
||||
"reactions:write",
|
||||
"users:read"
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -448,17 +450,17 @@ Notes:
|
||||
"event_subscriptions": {
|
||||
"bot_events": [
|
||||
"app_mention",
|
||||
"channel_rename",
|
||||
"member_joined_channel",
|
||||
"member_left_channel",
|
||||
"message.channels",
|
||||
"message.groups",
|
||||
"message.im",
|
||||
"message.mpim",
|
||||
"reaction_added",
|
||||
"reaction_removed",
|
||||
"member_joined_channel",
|
||||
"member_left_channel",
|
||||
"channel_rename",
|
||||
"pin_added",
|
||||
"pin_removed"
|
||||
"pin_removed",
|
||||
"reaction_added",
|
||||
"reaction_removed"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,6 +121,10 @@ Token resolution order is account-aware. In practice, config values win over env
|
||||
|
||||
For one-owner bots, prefer `dmPolicy: "allowlist"` with explicit numeric `allowFrom` IDs to keep access policy durable in config (instead of depending on previous pairing approvals).
|
||||
|
||||
Common confusion: DM pairing approval does not mean "this sender is authorized everywhere".
|
||||
Pairing grants DM access only. Group sender authorization still comes from explicit config allowlists.
|
||||
If you want "I am authorized once and both DMs and group commands work", put your numeric Telegram user ID in `channels.telegram.allowFrom`.
|
||||
|
||||
### Finding your Telegram user ID
|
||||
|
||||
Safer (no third-party bot):
|
||||
@@ -159,6 +163,8 @@ curl "https://api.telegram.org/bot<bot_token>/getUpdates"
|
||||
Non-numeric entries are ignored for sender authorization.
|
||||
Security boundary (`2026.2.25+`): group sender auth does **not** inherit DM pairing-store approvals.
|
||||
Pairing stays DM-only. For groups, set `groupAllowFrom` or per-group/per-topic `allowFrom`.
|
||||
If `groupAllowFrom` is unset, Telegram falls back to config `allowFrom`, not the pairing store.
|
||||
Practical pattern for one-owner bots: set your user ID in `channels.telegram.allowFrom`, leave `groupAllowFrom` unset, and allow the target groups under `channels.telegram.groups`.
|
||||
Runtime note: if `channels.telegram` is completely missing, runtime defaults to fail-closed `groupPolicy="allowlist"` unless `channels.defaults.groupPolicy` is explicitly set.
|
||||
|
||||
Example: allow any member in one specific group:
|
||||
@@ -759,6 +765,8 @@ curl "https://api.telegram.org/bot<bot_token>/getUpdates"
|
||||
- `channels.telegram.mediaMaxMb` (default 100) caps inbound and outbound Telegram media size.
|
||||
- `channels.telegram.timeoutSeconds` overrides Telegram API client timeout (if unset, grammY default applies).
|
||||
- group context history uses `channels.telegram.historyLimit` or `messages.groupChat.historyLimit` (default 50); `0` disables.
|
||||
- reply/quote/forward supplemental context is currently passed as received.
|
||||
- Telegram allowlists primarily gate who can trigger the agent, not a full supplemental-context redaction boundary.
|
||||
- DM history controls:
|
||||
- `channels.telegram.dmHistoryLimit`
|
||||
- `channels.telegram.dms["<user_id>"].historyLimit`
|
||||
@@ -912,6 +920,33 @@ channels:
|
||||
autoSelectFamily: false
|
||||
```
|
||||
|
||||
- RFC 2544 benchmark-range answers (`198.18.0.0/15`) are already allowed
|
||||
for Telegram media downloads by default. If a trusted fake-IP or
|
||||
transparent proxy rewrites `api.telegram.org` to some other
|
||||
private/internal/special-use address during media downloads, you can opt
|
||||
in to the Telegram-only bypass:
|
||||
|
||||
```yaml
|
||||
channels:
|
||||
telegram:
|
||||
network:
|
||||
dangerouslyAllowPrivateNetwork: true
|
||||
```
|
||||
|
||||
- The same opt-in is available per account at
|
||||
`channels.telegram.accounts.<accountId>.network.dangerouslyAllowPrivateNetwork`.
|
||||
- If your proxy resolves Telegram media hosts into `198.18.x.x`, leave the
|
||||
dangerous flag off first. Telegram media already allows the RFC 2544
|
||||
benchmark range by default.
|
||||
|
||||
<Warning>
|
||||
`channels.telegram.network.dangerouslyAllowPrivateNetwork` weakens Telegram
|
||||
media SSRF protections. Use it only for trusted operator-controlled proxy
|
||||
environments such as Clash, Mihomo, or Surge fake-IP routing when they
|
||||
synthesize private or special-use answers outside the RFC 2544 benchmark
|
||||
range. Leave it off for normal public internet Telegram access.
|
||||
</Warning>
|
||||
|
||||
- Environment overrides (temporary):
|
||||
- `OPENCLAW_TELEGRAM_DISABLE_AUTO_SELECT_FAMILY=1`
|
||||
- `OPENCLAW_TELEGRAM_ENABLE_AUTO_SELECT_FAMILY=1`
|
||||
@@ -978,6 +1013,7 @@ Primary reference:
|
||||
- `channels.telegram.retry`: retry policy for Telegram send helpers (CLI/tools/actions) on recoverable outbound API errors (attempts, minDelayMs, maxDelayMs, jitter).
|
||||
- `channels.telegram.network.autoSelectFamily`: override Node autoSelectFamily (true=enable, false=disable). Defaults to enabled on Node 22+, with WSL2 defaulting to disabled.
|
||||
- `channels.telegram.network.dnsResultOrder`: override DNS result order (`ipv4first` or `verbatim`). Defaults to `ipv4first` on Node 22+.
|
||||
- `channels.telegram.network.dangerouslyAllowPrivateNetwork`: dangerous opt-in for trusted fake-IP or transparent-proxy environments where Telegram media downloads resolve `api.telegram.org` to private/internal/special-use addresses outside the default RFC 2544 benchmark-range allowance.
|
||||
- `channels.telegram.proxy`: proxy URL for Bot API calls (SOCKS/HTTP).
|
||||
- `channels.telegram.webhookUrl`: enable webhook mode (requires `channels.telegram.webhookSecret`).
|
||||
- `channels.telegram.webhookSecret`: webhook secret (required when webhookUrl is set).
|
||||
@@ -1004,7 +1040,7 @@ Telegram-specific high-signal fields:
|
||||
- threading/replies: `replyToMode`
|
||||
- streaming: `streaming` (preview), `blockStreaming`
|
||||
- formatting/delivery: `textChunkLimit`, `chunkMode`, `linkPreview`, `responsePrefix`
|
||||
- media/network: `mediaMaxMb`, `timeoutSeconds`, `retry`, `network.autoSelectFamily`, `proxy`
|
||||
- media/network: `mediaMaxMb`, `timeoutSeconds`, `retry`, `network.autoSelectFamily`, `network.dangerouslyAllowPrivateNetwork`, `proxy`
|
||||
- webhook: `webhookUrl`, `webhookSecret`, `webhookPath`, `webhookHost`
|
||||
- actions/capabilities: `capabilities.inlineButtons`, `actions.sendMessage|editMessage|deleteMessage|reactions|sticker`
|
||||
- reactions: `reactionNotifications`, `reactionLevel`
|
||||
|
||||
@@ -122,10 +122,12 @@ Full troubleshooting: [/channels/qqbot#troubleshooting](/channels/qqbot#troubles
|
||||
|
||||
### Matrix failure signatures
|
||||
|
||||
| Symptom | Fastest check | Fix |
|
||||
| ----------------------------------- | -------------------------------------------- | ----------------------------------------------- |
|
||||
| Logged in but ignores room messages | `openclaw channels status --probe` | Check `groupPolicy` and room allowlist. |
|
||||
| DMs do not process | `openclaw pairing list matrix` | Approve sender or adjust DM policy. |
|
||||
| Encrypted rooms fail | Verify crypto module and encryption settings | Enable encryption support and rejoin/sync room. |
|
||||
| Symptom | Fastest check | Fix |
|
||||
| ----------------------------------- | -------------------------------------- | ------------------------------------------------------------------------- |
|
||||
| Logged in but ignores room messages | `openclaw channels status --probe` | Check `groupPolicy`, room allowlist, and mention gating. |
|
||||
| DMs do not process | `openclaw pairing list matrix` | Approve sender or adjust DM policy. |
|
||||
| Encrypted rooms fail | `openclaw matrix verify status` | Re-verify the device, then check `openclaw matrix verify backup status`. |
|
||||
| Backup restore is pending/broken | `openclaw matrix verify backup status` | Run `openclaw matrix verify backup restore` or rerun with a recovery key. |
|
||||
| Cross-signing/bootstrap looks wrong | `openclaw matrix verify bootstrap` | Repair secret storage, cross-signing, and backup state in one pass. |
|
||||
|
||||
Full setup and config: [Matrix](/channels/matrix)
|
||||
|
||||
@@ -13,6 +13,7 @@ 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)
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -31,6 +32,11 @@ 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/configuration-reference#agentsdefaultsskills).
|
||||
|
||||
List bindings:
|
||||
|
||||
```bash
|
||||
|
||||
@@ -36,6 +36,8 @@ 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.
|
||||
@@ -50,7 +52,7 @@ Notes:
|
||||
- `--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.
|
||||
- `--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.
|
||||
|
||||
@@ -308,7 +308,7 @@ Manage extensions and their config:
|
||||
|
||||
- `openclaw plugins list` — discover plugins (use `--json` for machine output).
|
||||
- `openclaw plugins inspect <id>` — show details for a plugin (`info` is an alias).
|
||||
- `openclaw plugins install <path|.tgz|npm-spec|plugin@marketplace>` — install a plugin (or add a plugin path to `plugins.load.paths`).
|
||||
- `openclaw plugins install <path|.tgz|npm-spec|plugin@marketplace>` — install a plugin (or add a plugin path to `plugins.load.paths`; use `--force` to overwrite an existing install target).
|
||||
- `openclaw plugins marketplace list <marketplace>` — list marketplace entries before install.
|
||||
- `openclaw plugins enable <id>` / `disable <id>` — toggle `plugins.entries.<id>.enabled`.
|
||||
- `openclaw plugins doctor` — report plugin load errors.
|
||||
|
||||
@@ -82,6 +82,8 @@ Gateway token options in non-interactive mode:
|
||||
- With `--install-daemon`, when token auth requires a token, SecretRef-managed gateway tokens are validated but not persisted as resolved plaintext in supervisor service environment metadata.
|
||||
- With `--install-daemon`, if token mode requires a token and the configured token SecretRef is unresolved, onboarding fails closed with remediation guidance.
|
||||
- With `--install-daemon`, if both `gateway.auth.token` and `gateway.auth.password` are configured and `gateway.auth.mode` is unset, onboarding blocks install until mode is set explicitly.
|
||||
- Local onboarding writes `gateway.mode="local"` into the config. If a later config file is missing `gateway.mode`, treat that as config damage or an incomplete manual edit, not as a valid local-mode shortcut.
|
||||
- `--allow-unconfigured` is a separate gateway runtime escape hatch. It does not mean onboarding may omit `gateway.mode`.
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ capabilities.
|
||||
```bash
|
||||
openclaw plugins install <package> # ClawHub first, then npm
|
||||
openclaw plugins install clawhub:<package> # ClawHub only
|
||||
openclaw plugins install <package> --force # overwrite existing install
|
||||
openclaw plugins install <package> --pin # pin version
|
||||
openclaw plugins install <package> --dangerously-force-unsafe-install
|
||||
openclaw plugins install <path> # local path
|
||||
@@ -58,13 +59,17 @@ openclaw plugins install <plugin> --marketplace <name> # marketplace (explicit)
|
||||
Bare package names are checked against ClawHub first, then npm. Security note:
|
||||
treat plugin installs like running code. Prefer pinned versions.
|
||||
|
||||
`--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.
|
||||
|
||||
`--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 `openclaw plugins install`. Gateway-backed skill
|
||||
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.
|
||||
@@ -157,6 +162,9 @@ 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.
|
||||
|
||||
Use `--pin` on npm installs to save the resolved exact spec (`name@version`) in
|
||||
`plugins.installs` while keeping the default behavior unpinned.
|
||||
|
||||
@@ -185,6 +193,7 @@ openclaw plugins update <id-or-npm-spec>
|
||||
openclaw plugins update --all
|
||||
openclaw plugins update <id-or-npm-spec> --dry-run
|
||||
openclaw plugins update @openclaw/voice-call@beta
|
||||
openclaw plugins update openclaw-codex-app-server --dangerously-force-unsafe-install
|
||||
```
|
||||
|
||||
Updates apply to tracked installs in `plugins.installs` and tracked hook-pack
|
||||
@@ -203,6 +212,12 @@ When a stored integrity hash exists and the fetched artifact hash changes,
|
||||
OpenClaw prints a warning and asks for confirmation before proceeding. Use
|
||||
global `--yes` to bypass prompts in CI/non-interactive runs.
|
||||
|
||||
`--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.
|
||||
|
||||
### Inspect
|
||||
|
||||
```bash
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
---
|
||||
summary: "CLI reference for `openclaw qr` (generate iOS pairing QR + setup code)"
|
||||
summary: "CLI reference for `openclaw qr` (generate mobile pairing QR + setup code)"
|
||||
read_when:
|
||||
- You want to pair the iOS app with a gateway quickly
|
||||
- You want to pair a mobile node app with a gateway quickly
|
||||
- You need setup-code output for remote/manual sharing
|
||||
title: "qr"
|
||||
---
|
||||
|
||||
# `openclaw qr`
|
||||
|
||||
Generate an iOS pairing QR and setup code from your current Gateway configuration.
|
||||
Generate a mobile pairing QR and setup code from your current Gateway configuration.
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -35,6 +35,7 @@ openclaw qr --url wss://gateway.example/ws
|
||||
|
||||
- `--token` and `--password` are mutually exclusive.
|
||||
- The setup code itself now carries an opaque short-lived `bootstrapToken`, not the shared gateway token/password.
|
||||
- Mobile pairing fails closed for Tailscale/public `ws://` gateway URLs. Private LAN `ws://` remains supported, but Tailscale/public mobile routes should use Tailscale Serve/Funnel or a `wss://` gateway URL.
|
||||
- With `--remote`, if effectively active remote credentials are configured as SecretRefs and you do not pass `--token` or `--password`, the command resolves them from the active gateway snapshot. If gateway is unavailable, the command fails fast.
|
||||
- Without `--remote`, local gateway auth SecretRefs are resolved when no CLI auth override is passed:
|
||||
- `gateway.auth.token` resolves when token auth can win (explicit `gateway.auth.mode="token"` or inferred mode where no password source wins).
|
||||
|
||||
@@ -111,7 +111,8 @@ See [Memory](/concepts/memory) for the workflow and automatic memory flush.
|
||||
|
||||
- `skills/` (optional)
|
||||
- Workspace-specific skills.
|
||||
- Overrides managed/bundled skills when names collide.
|
||||
- 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`).
|
||||
|
||||
@@ -55,11 +55,14 @@ guidance for how _you_ want them used.
|
||||
|
||||
## Skills
|
||||
|
||||
OpenClaw loads skills from three locations (workspace wins on name conflict):
|
||||
OpenClaw loads skills from these locations (highest precedence first):
|
||||
|
||||
- Bundled (shipped with the install)
|
||||
- Managed/local: `~/.openclaw/skills`
|
||||
- Workspace: `<workspace>/skills`
|
||||
- Project agent skills: `<workspace>/.agents/skills`
|
||||
- Personal agent skills: `~/.agents/skills`
|
||||
- Managed/local: `~/.openclaw/skills`
|
||||
- Bundled (shipped with the install)
|
||||
- Extra skill folders: `skills.load.extraDirs`
|
||||
|
||||
Skills can be gated by config/env (see `skills` in [Gateway configuration](/gateway/configuration)).
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ summary: "How OpenClaw rotates auth profiles and falls back across models"
|
||||
read_when:
|
||||
- Diagnosing auth profile rotation, cooldowns, or model fallback behavior
|
||||
- Updating failover rules for auth profiles or models
|
||||
- Understanding how session model overrides interact with fallback retries
|
||||
title: "Model Failover"
|
||||
---
|
||||
|
||||
@@ -15,6 +16,44 @@ OpenClaw handles failures in two stages:
|
||||
|
||||
This doc explains the runtime rules and the data that backs them.
|
||||
|
||||
## Runtime flow
|
||||
|
||||
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.
|
||||
|
||||
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:
|
||||
|
||||
- `providerOverride`
|
||||
- `modelOverride`
|
||||
- `authProfileOverride`
|
||||
- `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.
|
||||
|
||||
## Auth storage (keys + OAuth)
|
||||
|
||||
OpenClaw uses **auth profiles** for both API keys and OAuth tokens.
|
||||
@@ -148,6 +187,102 @@ with `auth.cooldowns.overloadedProfileRotations`,
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
### 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
|
||||
- a final unknown error when there are no candidates left
|
||||
|
||||
### 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:
|
||||
|
||||
- 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).
|
||||
- Transient cooldown probes are limited to one per provider per fallback run so
|
||||
a single provider does not stall cross-provider fallback.
|
||||
|
||||
## 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.
|
||||
|
||||
That means fallback retries have to coordinate with live model switching:
|
||||
|
||||
- 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.
|
||||
|
||||
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.
|
||||
|
||||
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:
|
||||
|
||||
- provider/model attempted
|
||||
- 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.
|
||||
|
||||
## Related config
|
||||
|
||||
See [Gateway configuration](/gateway/configuration) for:
|
||||
|
||||
@@ -16,6 +16,8 @@ For model selection rules, see [/concepts/models](/concepts/models).
|
||||
- Model refs use `provider/model` (example: `opencode/claude-opus-4-6`).
|
||||
- If you set `agents.defaults.models`, it becomes the allowlist.
|
||||
- CLI helpers: `openclaw onboard`, `openclaw models list`, `openclaw models set <provider/model>`.
|
||||
- Fallback runtime rules, cooldown probes, and session-override persistence are
|
||||
documented in [/concepts/model-failover](/concepts/model-failover).
|
||||
- Provider plugins can inject model catalogs via `registerProvider({ catalog })`;
|
||||
OpenClaw merges that output into `models.providers` before writing
|
||||
`models.json`.
|
||||
@@ -106,7 +108,7 @@ Current bundled examples:
|
||||
policy, binary-thinking/live-model policy, and usage auth + quota fetching
|
||||
- `mistral`, `opencode`, and `opencode-go`: plugin-owned capability metadata
|
||||
- `byteplus`, `cloudflare-ai-gateway`, `huggingface`, `kimi-coding`,
|
||||
`modelstudio`, `nvidia`, `qianfan`, `synthetic`, `together`, `venice`,
|
||||
`modelstudio`, `nvidia`, `qianfan`, `stepfun`, `synthetic`, `together`, `venice`,
|
||||
`vercel-ai-gateway`, and `volcengine`: plugin-owned catalogs only
|
||||
- `minimax` and `xiaomi`: plugin-owned catalogs plus usage auth/snapshot logic
|
||||
|
||||
@@ -265,6 +267,8 @@ See [/providers/kilocode](/providers/kilocode) for setup details.
|
||||
- Qianfan: `qianfan` (`QIANFAN_API_KEY`)
|
||||
- Model Studio: `modelstudio` (`MODELSTUDIO_API_KEY`)
|
||||
- NVIDIA: `nvidia` (`NVIDIA_API_KEY`)
|
||||
- StepFun: `stepfun` / `stepfun-plan` (`STEPFUN_API_KEY`)
|
||||
- Example models: `stepfun/step-3.5-flash`, `stepfun-plan/step-3.5-flash-2603`
|
||||
- Together: `together` (`TOGETHER_API_KEY`)
|
||||
- Venice: `venice` (`VENICE_API_KEY`)
|
||||
- Xiaomi: `xiaomi` (`XIAOMI_API_KEY`)
|
||||
|
||||
@@ -27,8 +27,12 @@ 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`.
|
||||
|
||||
Skills are per-agent via each workspace’s `skills/` folder, with shared skills
|
||||
available from `~/.openclaw/skills`. See [Skills: per-agent vs shared](/tools/skills#per-agent-vs-shared-skills).
|
||||
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.
|
||||
|
||||
|
||||
@@ -110,6 +110,10 @@ prompt instructs the model to use `read` to load the SKILL.md at the listed
|
||||
location (workspace, managed, or bundled). If no skills are eligible, the
|
||||
Skills section is omitted.
|
||||
|
||||
Eligibility includes skill metadata gates, runtime environment/config checks,
|
||||
and the effective agent skill allowlist when `agents.defaults.skills` or
|
||||
`agents.list[].skills` is configured.
|
||||
|
||||
```
|
||||
<available_skills>
|
||||
<skill>
|
||||
|
||||
@@ -1145,11 +1145,11 @@
|
||||
"group": "Automation & Tasks",
|
||||
"pages": [
|
||||
"automation/index",
|
||||
"automation/tasks",
|
||||
"automation/cron-jobs",
|
||||
"automation/tasks",
|
||||
"automation/taskflow",
|
||||
"automation/hooks",
|
||||
"automation/standing-orders"
|
||||
"automation/standing-orders",
|
||||
"automation/hooks"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1177,6 +1177,7 @@
|
||||
"tools/gemini-search",
|
||||
"tools/grok-search",
|
||||
"tools/kimi-search",
|
||||
"tools/ollama-search",
|
||||
"tools/perplexity-search",
|
||||
"tools/searxng-search",
|
||||
"tools/tavily"
|
||||
@@ -1249,6 +1250,7 @@
|
||||
"providers/qwen_modelstudio",
|
||||
"providers/qwen",
|
||||
"providers/sglang",
|
||||
"providers/stepfun",
|
||||
"providers/synthetic",
|
||||
"providers/together",
|
||||
"providers/venice",
|
||||
|
||||
@@ -250,6 +250,7 @@ Save to `~/.openclaw/openclaw.json` and you can DM the bot from that number.
|
||||
"anthropic/claude-sonnet-4-6": { alias: "sonnet" },
|
||||
"openai/gpt-5.2": { alias: "gpt" },
|
||||
},
|
||||
skills: ["github", "weather"], // inherited by agents that omit list[].skills
|
||||
thinkingDefault: "low",
|
||||
verboseDefault: "off",
|
||||
elevatedDefault: "on",
|
||||
@@ -308,12 +309,14 @@ Save to `~/.openclaw/openclaw.json` and you can DM the bot from that number.
|
||||
{
|
||||
id: "main",
|
||||
default: true,
|
||||
// inherits defaults.skills -> github, weather
|
||||
thinkingDefault: "high", // per-agent thinking override
|
||||
reasoningDefault: "on", // per-agent reasoning visibility
|
||||
fastModeDefault: false, // per-agent fast mode
|
||||
},
|
||||
{
|
||||
id: "quick",
|
||||
skills: [], // no skills for this agent
|
||||
fastModeDefault: true, // this agent always runs fast
|
||||
thinkingDefault: "off",
|
||||
},
|
||||
@@ -462,6 +465,27 @@ Save to `~/.openclaw/openclaw.json` and you can DM the bot from that number.
|
||||
|
||||
## Common patterns
|
||||
|
||||
### Shared skill baseline with one override
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
defaults: {
|
||||
workspace: "~/.openclaw/workspace",
|
||||
skills: ["github", "weather"],
|
||||
},
|
||||
list: [
|
||||
{ id: "main", default: true },
|
||||
{ id: "docs", workspace: "~/.openclaw/workspace-docs", skills: ["docs-search"] },
|
||||
],
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
- `agents.defaults.skills` is the shared baseline.
|
||||
- `agents.list[].skills` replaces that baseline for one agent.
|
||||
- Use `skills: []` when an agent should see no skills.
|
||||
|
||||
### Multi-platform setup
|
||||
|
||||
```json5
|
||||
|
||||
@@ -818,6 +818,30 @@ Optional repository root shown in the system prompt's Runtime line. If unset, Op
|
||||
}
|
||||
```
|
||||
|
||||
### `agents.defaults.skills`
|
||||
|
||||
Optional default skill allowlist for agents that do not set
|
||||
`agents.list[].skills`.
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
defaults: { skills: ["github", "weather"] },
|
||||
list: [
|
||||
{ id: "writer" }, // inherits github, weather
|
||||
{ id: "docs", skills: ["docs-search"] }, // replaces defaults
|
||||
{ id: "locked-down", skills: [] }, // no skills
|
||||
],
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
- Omit `agents.defaults.skills` for unrestricted skills by default.
|
||||
- Omit `agents.list[].skills` to inherit the defaults.
|
||||
- Set `agents.list[].skills: []` for no skills.
|
||||
- A non-empty `agents.list[].skills` list is the final set for that agent; it
|
||||
does not merge with defaults.
|
||||
|
||||
### `agents.defaults.skipBootstrap`
|
||||
|
||||
Disables automatic creation of workspace bootstrap files (`AGENTS.md`, `SOUL.md`, `TOOLS.md`, `IDENTITY.md`, `USER.md`, `HEARTBEAT.md`, `BOOTSTRAP.md`).
|
||||
@@ -1425,6 +1449,7 @@ scripts/sandbox-browser-setup.sh # optional browser image
|
||||
reasoningDefault: "on", // per-agent reasoning visibility override
|
||||
fastModeDefault: false, // per-agent fast mode override
|
||||
params: { cacheRetention: "none" }, // overrides matching defaults.models params by key
|
||||
skills: ["docs-search"], // replaces agents.defaults.skills when set
|
||||
identity: {
|
||||
name: "Samantha",
|
||||
theme: "helpful sloth",
|
||||
@@ -1459,6 +1484,7 @@ scripts/sandbox-browser-setup.sh # optional browser image
|
||||
- `default`: when multiple are set, first wins (warning logged). If none set, first list entry is default.
|
||||
- `model`: string form overrides `primary` only; object form `{ primary, fallbacks }` overrides both (`[]` disables global fallbacks). Cron jobs that only override `primary` still inherit default fallbacks unless you set `fallbacks: []`.
|
||||
- `params`: per-agent stream params merged over the selected model entry in `agents.defaults.models`. Use this for agent-specific overrides like `cacheRetention`, `temperature`, or `maxTokens` without duplicating the whole model catalog.
|
||||
- `skills`: optional per-agent skill allowlist. If omitted, the agent inherits `agents.defaults.skills` when set; an explicit list replaces defaults instead of merging, and `[]` means no skills.
|
||||
- `thinkingDefault`: optional per-agent default thinking level (`off | minimal | low | medium | high | xhigh | adaptive`). Overrides `agents.defaults.thinkingDefault` for this agent when no per-message or session override is set.
|
||||
- `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.
|
||||
@@ -1748,7 +1774,10 @@ Variables are case-insensitive. `{think}` is an alias for `{thinkingLevel}`.
|
||||
- Per-channel overrides: `channels.<channel>.ackReaction`, `channels.<channel>.accounts.<id>.ackReaction`.
|
||||
- Resolution order: account → channel → `messages.ackReaction` → identity fallback.
|
||||
- Scope: `group-mentions` (default), `group-all`, `direct`, `all`.
|
||||
- `removeAckAfterReply`: removes ack after reply (Slack/Discord/Telegram/Google Chat only).
|
||||
- `removeAckAfterReply`: removes ack after reply on Slack, Discord, and Telegram.
|
||||
- `messages.statusReactions.enabled`: enables lifecycle status reactions on Slack, Discord, and Telegram.
|
||||
On Slack and Discord, unset keeps status reactions enabled when ack reactions are active.
|
||||
On Telegram, set it explicitly to `true` to enable lifecycle status reactions.
|
||||
|
||||
### Inbound debounce
|
||||
|
||||
@@ -2118,6 +2147,7 @@ Notes:
|
||||
agents: {
|
||||
defaults: {
|
||||
subagents: {
|
||||
allowAgents: ["research"],
|
||||
model: "minimax/MiniMax-M2.7",
|
||||
maxConcurrent: 8,
|
||||
runTimeoutSeconds: 900,
|
||||
@@ -2129,6 +2159,7 @@ Notes:
|
||||
```
|
||||
|
||||
- `model`: default model for spawned sub-agents. If omitted, sub-agents inherit the caller's model.
|
||||
- `allowAgents`: default allowlist of target agent ids for `sessions_spawn` when the requester agent does not set its own `subagents.allowAgents` (`["*"]` = any; default: same agent only).
|
||||
- `runTimeoutSeconds`: default timeout (seconds) for `sessions_spawn` when the tool call omits `runTimeoutSeconds`. `0` means no timeout.
|
||||
- Per-subagent tool policy: `tools.subagents.tools.allow` / `tools.subagents.tools.deny`.
|
||||
|
||||
@@ -3032,6 +3063,8 @@ Notes:
|
||||
billingBackoffHours: 5,
|
||||
billingBackoffHoursByProvider: { anthropic: 3, openai: 8 },
|
||||
billingMaxHours: 24,
|
||||
authPermanentBackoffMinutes: 10,
|
||||
authPermanentMaxMinutes: 60,
|
||||
failureWindowHours: 24,
|
||||
overloadedProfileRotations: 1,
|
||||
overloadedBackoffMs: 0,
|
||||
@@ -3044,6 +3077,8 @@ Notes:
|
||||
- `billingBackoffHours`: base backoff in hours when a profile fails due to billing/insufficient credits (default: `5`).
|
||||
- `billingBackoffHoursByProvider`: optional per-provider overrides for billing backoff hours.
|
||||
- `billingMaxHours`: cap in hours for billing backoff exponential growth (default: `24`).
|
||||
- `authPermanentBackoffMinutes`: base backoff in minutes for high-confidence `auth_permanent` failures (default: `10`).
|
||||
- `authPermanentMaxMinutes`: cap in minutes for `auth_permanent` backoff growth (default: `60`).
|
||||
- `failureWindowHours`: rolling window in hours used for backoff counters (default: `24`).
|
||||
- `overloadedProfileRotations`: maximum same-provider auth-profile rotations for overloaded errors before switching to model fallback (default: `1`).
|
||||
- `overloadedBackoffMs`: fixed delay before retrying an overloaded provider/profile rotation (default: `0`).
|
||||
|
||||
@@ -175,6 +175,33 @@ When validation fails:
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Restrict skills per agent">
|
||||
Use `agents.defaults.skills` for a shared baseline, then override specific
|
||||
agents with `agents.list[].skills`:
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
defaults: {
|
||||
skills: ["github", "weather"],
|
||||
},
|
||||
list: [
|
||||
{ id: "writer" }, // inherits github, weather
|
||||
{ id: "docs", skills: ["docs-search"] }, // replaces defaults
|
||||
{ id: "locked-down", skills: [] }, // no skills
|
||||
],
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
- Omit `agents.defaults.skills` for unrestricted skills by default.
|
||||
- Omit `agents.list[].skills` to inherit the defaults.
|
||||
- Set `agents.list[].skills: []` for no skills.
|
||||
- See [Skills](/tools/skills), [Skills config](/tools/skills-config), and
|
||||
the [Configuration Reference](/gateway/configuration-reference#agentsdefaultsskills).
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Tune gateway channel health monitoring">
|
||||
Control how aggressively the gateway restarts channels that look stale:
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ Security notes:
|
||||
- Bonjour/mDNS TXT records are **unauthenticated**. Clients must treat TXT values as UX hints only.
|
||||
- Routing (host/port) should prefer the **resolved service endpoint** (SRV + A/AAAA) over TXT-provided `lanHost`, `tailnetDns`, or `gatewayPort`.
|
||||
- TLS pinning must never allow an advertised `gatewayTlsSha256` to override a previously stored pin.
|
||||
- iOS/Android nodes should treat discovery-based direct connects as **TLS-only** and require an explicit “trust this fingerprint” confirmation before storing a first-time pin (out-of-band verification).
|
||||
- iOS/Android nodes should require an explicit “trust this fingerprint” confirmation before storing a first-time pin (out-of-band verification) whenever the chosen route is secure/TLS-based.
|
||||
|
||||
Disable/override:
|
||||
|
||||
@@ -95,6 +95,13 @@ If the gateway can detect it is running under Tailscale, it publishes `tailnetDn
|
||||
|
||||
The macOS app now prefers MagicDNS names over raw Tailscale IPs for gateway discovery. This improves reliability when tailnet IPs change (for example after node restarts or CGNAT reassignment), because MagicDNS names resolve to the current IP automatically.
|
||||
|
||||
For mobile node pairing, discovery hints do not relax transport security on tailnet/public routes:
|
||||
|
||||
- iOS/Android still require a secure first-time tailnet/public connect path (`wss://` or Tailscale Serve/Funnel).
|
||||
- A discovered raw tailnet IP is a routing hint, not permission to use plaintext remote `ws://`.
|
||||
- Private LAN direct-connect `ws://` remains supported.
|
||||
- If you want the simplest Tailscale path for mobile nodes, use Tailscale Serve so discovery and the setup code both resolve to the same secure MagicDNS endpoint.
|
||||
|
||||
### 3) Manual / SSH target
|
||||
|
||||
When there is no direct route (or direct is disabled), clients can always connect via SSH by forwarding the loopback gateway port.
|
||||
@@ -108,6 +115,7 @@ Recommended client behavior:
|
||||
1. If a paired direct endpoint is configured and reachable, use it.
|
||||
2. Else, if Bonjour finds a gateway on LAN, offer a one-tap “Use this gateway” choice and save it as the direct endpoint.
|
||||
3. Else, if a tailnet DNS/IP is configured, try direct.
|
||||
For mobile nodes on tailnet/public routes, direct means a secure endpoint, not plaintext remote `ws://`.
|
||||
4. Else, fall back to SSH.
|
||||
|
||||
## Pairing + auth (direct transport)
|
||||
|
||||
@@ -259,12 +259,12 @@ Events are not replayed. On sequence gaps, refresh state (`health`, `system-pres
|
||||
|
||||
## Common failure signatures
|
||||
|
||||
| Signature | Likely issue |
|
||||
| -------------------------------------------------------------- | ---------------------------------------- |
|
||||
| `refusing to bind gateway ... without auth` | Non-loopback bind without token/password |
|
||||
| `another gateway instance is already listening` / `EADDRINUSE` | Port conflict |
|
||||
| `Gateway start blocked: set gateway.mode=local` | Config set to remote mode |
|
||||
| `unauthorized` during connect | Auth mismatch between client and gateway |
|
||||
| Signature | Likely issue |
|
||||
| -------------------------------------------------------------- | ------------------------------------------------------------------------------- |
|
||||
| `refusing to bind gateway ... without auth` | Non-loopback bind without token/password |
|
||||
| `another gateway instance is already listening` / `EADDRINUSE` | Port conflict |
|
||||
| `Gateway start blocked: set gateway.mode=local` | Config set to remote mode, or local-mode stamp is missing from a damaged config |
|
||||
| `unauthorized` during connect | Auth mismatch between client and gateway |
|
||||
|
||||
For full diagnosis ladders, use [Gateway Troubleshooting](/gateway/troubleshooting).
|
||||
|
||||
|
||||
@@ -169,6 +169,26 @@ If more than one person can DM your bot:
|
||||
- Never combine shared DMs with broad tool access.
|
||||
- This hardens cooperative/shared inboxes, but is not designed as hostile co-tenant isolation when users share host/config write access.
|
||||
|
||||
## Context visibility model
|
||||
|
||||
OpenClaw separates two concepts:
|
||||
|
||||
- **Trigger authorization**: who can trigger the agent (`dmPolicy`, `groupPolicy`, allowlists, mention gates).
|
||||
- **Context visibility**: what supplemental context is injected into model input (reply body, quoted text, thread history, forwarded metadata).
|
||||
|
||||
Allowlists gate triggers and command authorization. The `contextVisibility` setting controls how supplemental context (quoted replies, thread roots, fetched history) is filtered:
|
||||
|
||||
- `contextVisibility: "all"` (default) keeps supplemental context as received.
|
||||
- `contextVisibility: "allowlist"` filters supplemental context to senders allowed by the active allowlist checks.
|
||||
- `contextVisibility: "allowlist_quote"` behaves like `allowlist`, but still keeps one explicit quoted reply.
|
||||
|
||||
Set `contextVisibility` per channel or per room/conversation. See [Group Chats](/channels/groups#context-visibility) for setup details.
|
||||
|
||||
Advisory triage guidance:
|
||||
|
||||
- Claims that only show "model can see quoted or historical text from non-allowlisted senders" are hardening findings addressable with `contextVisibility`, not auth or sandbox boundary bypasses by themselves.
|
||||
- To be security-impacting, reports still need a demonstrated trust-boundary bypass (auth, policy, sandbox, approval, or another documented boundary).
|
||||
|
||||
## What the audit checks (high level)
|
||||
|
||||
- **Inbound access** (DM policies, group policies, allowlists): can strangers trigger the bot?
|
||||
@@ -457,12 +477,12 @@ Plugins run **in-process** with the Gateway. Treat them as trusted code:
|
||||
- Prefer explicit `plugins.allow` allowlists.
|
||||
- Review plugin config before enabling.
|
||||
- Restart the Gateway after plugin changes.
|
||||
- If you install plugins (`openclaw plugins install <package>`), treat it like running untrusted code:
|
||||
- If you install or update plugins (`openclaw plugins install <package>`, `openclaw plugins update <id>`), treat it like running untrusted code:
|
||||
- The install path is the per-plugin directory under the active plugin install root.
|
||||
- OpenClaw runs a built-in dangerous-code scan before install. `critical` findings block by default.
|
||||
- OpenClaw runs a built-in dangerous-code scan before install/update. `critical` findings block by default.
|
||||
- OpenClaw uses `npm pack` and then runs `npm install --omit=dev` in that directory (npm lifecycle scripts can execute code during install).
|
||||
- Prefer pinned, exact versions (`@scope/pkg@1.2.3`), and inspect the unpacked code on disk before enabling.
|
||||
- `--dangerously-force-unsafe-install` is break-glass only for built-in scan false positives. It does not bypass plugin `before_install` hook policy blocks and does not bypass scan failures.
|
||||
- `--dangerously-force-unsafe-install` is break-glass only for built-in scan false positives on plugin install/update flows. It does not bypass plugin `before_install` hook policy blocks and does not bypass scan failures.
|
||||
- Gateway-backed skill dependency installs follow the same dangerous/suspicious split: built-in `critical` findings block unless the caller explicitly sets `dangerouslyForceUnsafeInstall`, while suspicious findings still warn only. `openclaw skills install` remains the separate ClawHub skill download/install flow.
|
||||
|
||||
Details: [Plugins](/tools/plugin)
|
||||
@@ -995,7 +1015,7 @@ Important: `tools.elevated` is the global baseline escape hatch that runs exec o
|
||||
If you allow session tools, treat delegated sub-agent runs as another boundary decision:
|
||||
|
||||
- Deny `sessions_spawn` unless the agent truly needs delegation.
|
||||
- Keep `agents.list[].subagents.allowAgents` restricted to known-safe target agents.
|
||||
- Keep `agents.defaults.subagents.allowAgents` and any per-agent `agents.list[].subagents.allowAgents` overrides restricted to known-safe target agents.
|
||||
- For any workflow that must remain sandboxed, call `sessions_spawn` with `sandbox: "require"` (default is `inherit`).
|
||||
- `sandbox: "require"` fails fast when the target child runtime is not sandboxed.
|
||||
|
||||
|
||||
@@ -170,7 +170,7 @@ Look for:
|
||||
|
||||
Common signatures:
|
||||
|
||||
- `Gateway start blocked: set gateway.mode=local` → local gateway mode is not enabled. Fix: set `gateway.mode="local"` in your config (or run `openclaw configure`). If you are running OpenClaw via Podman, the default config path is `~/.openclaw/openclaw.json`.
|
||||
- `Gateway start blocked: set gateway.mode=local` or `existing config is missing gateway.mode` → local gateway mode is not enabled, or the config file was clobbered and lost `gateway.mode`. Fix: set `gateway.mode="local"` in your config, or re-run `openclaw onboard --mode local` / `openclaw setup` to restamp the expected local-mode config. If you are running OpenClaw via Podman, the default config path is `~/.openclaw/openclaw.json`.
|
||||
- `refusing to bind gateway ... without auth` → non-loopback bind without token/password.
|
||||
- `another gateway instance is already listening` / `EADDRINUSE` → port conflict.
|
||||
|
||||
|
||||
@@ -946,11 +946,11 @@ for usage/billing and raise limits as needed.
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="How do I customize skills without keeping the repo dirty?">
|
||||
Use managed overrides instead of editing the repo copy. Put your changes in `~/.openclaw/skills/<name>/SKILL.md` (or add a folder via `skills.load.extraDirs` in `~/.openclaw/openclaw.json`). Precedence is `<workspace>/skills` > `~/.openclaw/skills` > bundled, so managed overrides win without touching git. Only upstream-worthy edits should live in the repo and go out as PRs.
|
||||
Use managed overrides instead of editing the repo copy. Put your changes in `~/.openclaw/skills/<name>/SKILL.md` (or add a folder via `skills.load.extraDirs` in `~/.openclaw/openclaw.json`). Precedence is `<workspace>/skills` → `<workspace>/.agents/skills` → `~/.agents/skills` → `~/.openclaw/skills` → bundled → `skills.load.extraDirs`, so managed overrides still win over bundled skills without touching git. If you need the skill installed globally but only visible to some agents, keep the shared copy in `~/.openclaw/skills` and control visibility with `agents.defaults.skills` and `agents.list[].skills`. Only upstream-worthy edits should live in the repo and go out as PRs.
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Can I load skills from a custom folder?">
|
||||
Yes. Add extra directories via `skills.load.extraDirs` in `~/.openclaw/openclaw.json` (lowest precedence). Default precedence remains: `<workspace>/skills` → `~/.openclaw/skills` → bundled → `skills.load.extraDirs`. `clawhub` installs into `./skills` by default, which OpenClaw treats as `<workspace>/skills` on the next session.
|
||||
Yes. Add extra directories via `skills.load.extraDirs` in `~/.openclaw/openclaw.json` (lowest precedence). Default precedence is `<workspace>/skills` → `<workspace>/.agents/skills` → `~/.agents/skills` → `~/.openclaw/skills` → bundled → `skills.load.extraDirs`. `clawhub` installs into `./skills` by default, which OpenClaw treats as `<workspace>/skills` on the next session. If the skill should only be visible to certain agents, pair that with `agents.defaults.skills` or `agents.list[].skills`.
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="How can I use different models for different tasks?">
|
||||
@@ -1030,7 +1030,7 @@ for usage/billing and raise limits as needed.
|
||||
openclaw skills update --all
|
||||
```
|
||||
|
||||
Install the separate `clawhub` CLI only if you want to publish or sync your own skills.
|
||||
Install the separate `clawhub` CLI only if you want to publish or sync your own skills. For shared installs across agents, put the skill under `~/.openclaw/skills` and use `agents.defaults.skills` or `agents.list[].skills` if you want to narrow which agents can see it.
|
||||
|
||||
</Accordion>
|
||||
|
||||
@@ -1106,7 +1106,7 @@ for usage/billing and raise limits as needed.
|
||||
openclaw skills update --all
|
||||
```
|
||||
|
||||
Native installs land in the active workspace `skills/` directory. For shared skills across agents, place them in `~/.openclaw/skills/<name>/SKILL.md`. Some skills expect binaries installed via Homebrew; on Linux that means Linuxbrew (see the Homebrew Linux FAQ entry above). See [Skills](/tools/skills) and [ClawHub](/tools/clawhub).
|
||||
Native installs land in the active workspace `skills/` directory. For shared skills across agents, place them in `~/.openclaw/skills/<name>/SKILL.md`. If only some agents should see a shared install, configure `agents.defaults.skills` or `agents.list[].skills`. Some skills expect binaries installed via Homebrew; on Linux that means Linuxbrew (see the Homebrew Linux FAQ entry above). See [Skills](/tools/skills), [Skills config](/tools/skills-config), and [ClawHub](/tools/clawhub).
|
||||
|
||||
</Accordion>
|
||||
|
||||
@@ -1410,16 +1410,24 @@ for usage/billing and raise limits as needed.
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="How do I enable web search (and web fetch)?">
|
||||
`web_fetch` works without an API key. `web_search` requires a key for your
|
||||
selected provider (Brave, Gemini, Grok, Kimi, or Perplexity).
|
||||
`web_fetch` works without an API key. `web_search` depends on your selected
|
||||
provider:
|
||||
|
||||
- API-backed providers such as Brave, Exa, Firecrawl, Gemini, Grok, Kimi, Perplexity, and Tavily require their normal API key setup.
|
||||
- Ollama Web Search is key-free, but it uses your configured Ollama host and requires `ollama signin`.
|
||||
- DuckDuckGo is key-free, but it is an unofficial HTML-based integration.
|
||||
|
||||
**Recommended:** run `openclaw configure --section web` and choose a provider.
|
||||
Environment alternatives:
|
||||
|
||||
- Brave: `BRAVE_API_KEY`
|
||||
- Exa: `EXA_API_KEY`
|
||||
- Firecrawl: `FIRECRAWL_API_KEY`
|
||||
- Gemini: `GEMINI_API_KEY`
|
||||
- Grok: `XAI_API_KEY`
|
||||
- Kimi: `KIMI_API_KEY` or `MOONSHOT_API_KEY`
|
||||
- Perplexity: `PERPLEXITY_API_KEY` or `OPENROUTER_API_KEY`
|
||||
- Tavily: `TAVILY_API_KEY`
|
||||
|
||||
```json5
|
||||
{
|
||||
@@ -2080,13 +2088,13 @@ for usage/billing and raise limits as needed.
|
||||
|
||||
1. Install Ollama from `https://ollama.com/download`
|
||||
2. Pull a local model such as `ollama pull glm-4.7-flash`
|
||||
3. If you want Ollama Cloud too, run `ollama signin`
|
||||
3. If you want cloud models too, run `ollama signin`
|
||||
4. Run `openclaw onboard` and choose `Ollama`
|
||||
5. Pick `Local` or `Cloud + Local`
|
||||
|
||||
Notes:
|
||||
|
||||
- `Cloud + Local` gives you Ollama Cloud models plus your local Ollama models
|
||||
- `Cloud + Local` gives you cloud models plus your local Ollama models
|
||||
- cloud models such as `kimi-k2.5:cloud` do not need a local pull
|
||||
- for manual switching, use `openclaw models list` and `openclaw models set ollama/<model>`
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@ Most days:
|
||||
|
||||
- Full gate (expected before push): `pnpm build && pnpm check && pnpm test`
|
||||
- Faster local full-suite run on a roomy machine: `pnpm test:max`
|
||||
- Direct Vitest watch loop (modern projects config): `pnpm test:watch`
|
||||
- Direct file targeting now routes extension/channel paths too: `pnpm test -- extensions/discord/src/monitor/message-handler.preflight.test.ts`
|
||||
|
||||
When you touch tests or want extra confidence:
|
||||
|
||||
@@ -44,8 +46,8 @@ Think of the suites as “increasing realism” (and increasing flakiness/cost):
|
||||
### Unit / integration (default)
|
||||
|
||||
- Command: `pnpm test`
|
||||
- Config: `scripts/test-parallel.mjs` (runs `vitest.unit.config.ts`, `vitest.extensions.config.ts`, `vitest.gateway.config.ts`)
|
||||
- Files: `src/**/*.test.ts`, bundled plugin `**/*.test.ts`
|
||||
- Config: native Vitest `projects` via `vitest.config.ts` (`unit` + `boundary`)
|
||||
- Files: core/unit inventories under `src/**/*.test.ts`, `packages/**/*.test.ts`, `test/**/*.test.ts`, and the whitelisted `ui` node tests covered by `vitest.unit.config.ts`
|
||||
- Scope:
|
||||
- Pure unit tests
|
||||
- In-process integration tests (gateway auth, routing, tooling, parsing, config)
|
||||
@@ -54,22 +56,10 @@ Think of the suites as “increasing realism” (and increasing flakiness/cost):
|
||||
- Runs in CI
|
||||
- No real keys required
|
||||
- Should be fast and stable
|
||||
- Scheduler note:
|
||||
- `pnpm test` now keeps a small checked-in behavioral manifest for true pool/isolation overrides and a separate timing snapshot for the slowest unit files.
|
||||
- Extension-only local runs now also use a checked-in extensions timing snapshot plus a slightly coarser shared batch target on high-memory hosts, so the shared extensions lane avoids spawning an extra batch when two measured shared runs are enough.
|
||||
- High-memory local extension shared batches also run with a slightly higher worker cap than before, which shortened the two remaining shared extension batches without changing the isolated extension lanes.
|
||||
- High-memory local channel runs now reuse the checked-in channel timing snapshot to split the shared channels lane into a few measured batches instead of one long shared worker.
|
||||
- High-memory local channel shared batches also run with a slightly lower worker cap than shared unit batches, which helped targeted channel reruns avoid CPU oversubscription once isolated channel lanes are already in flight.
|
||||
- Targeted local channel reruns now start splitting shared channel work a bit earlier, which keeps medium-sized targeted reruns from leaving one oversized shared channel batch on the critical path.
|
||||
- Targeted local unit reruns also split medium-sized shared unit selections into measured batches, which helps large focused reruns overlap instead of waiting behind one long shared unit lane.
|
||||
- High-memory local multi-surface runs also use slightly coarser shared `unit-fast` batches so the mixed planner spends less time spinning up extra shared unit workers before the later surfaces can overlap.
|
||||
- Shared unit, extension, channel, and gateway runs all stay on Vitest `forks`.
|
||||
- The wrapper keeps measured fork-isolated exceptions and heavy singleton lanes explicit in `test/fixtures/test-parallel.behavior.json`.
|
||||
- The wrapper peels the heaviest measured files into dedicated lanes instead of relying on a growing hand-maintained exclusion list.
|
||||
- CLI startup benchmarking now has distinct saved outputs: `pnpm test:startup:bench:smoke` writes the targeted smoke artifact at `.artifacts/cli-startup-bench-smoke.json`, `pnpm test:startup:bench:save` writes the full-suite artifact at `.artifacts/cli-startup-bench-all.json` with `runs=5` and `warmup=1`, and `pnpm test:startup:bench:update` refreshes the checked-in fixture at `test/fixtures/cli-startup-bench.json` with `runs=5` and `warmup=1`.
|
||||
- For surface-only local runs, unit, extension, and channel shared lanes can overlap their isolated hotspots instead of waiting behind one serial prefix.
|
||||
- For multi-surface local runs, the wrapper keeps the shared surface phases ordered, but batches inside the same shared phase now fan out together, deferred isolated work can overlap the next shared phase, and spare `unit-fast` headroom now starts that deferred work earlier instead of leaving those slots idle.
|
||||
- Refresh the timing snapshots with `pnpm test:perf:update-timings` and `pnpm test:perf:update-timings:extensions` after major suite shape changes.
|
||||
- Projects note:
|
||||
- `pnpm test` and `pnpm test:watch` both use the same native Vitest `projects` config now.
|
||||
- The tiny script wrapper still keeps scheduling native, but it now reroutes direct `extensions/...` and channel-surface test paths onto the matching Vitest lane automatically.
|
||||
- If you target mixed suites in one command, the wrapper runs those lanes sequentially under the same local heavy-check lock.
|
||||
- Embedded runner note:
|
||||
- When you change message-tool discovery inputs or compaction runtime context,
|
||||
keep both levels of coverage.
|
||||
@@ -83,19 +73,17 @@ Think of the suites as “increasing realism” (and increasing flakiness/cost):
|
||||
sufficient substitute for those integration paths.
|
||||
- Pool note:
|
||||
- Base Vitest config still defaults to `forks`.
|
||||
- Unit, channel, extension, and gateway wrapper lanes all default to `forks`.
|
||||
- Unit and boundary projects stay on `forks`.
|
||||
- Channel, extension, and gateway configs also stay on `forks`.
|
||||
- Unit, channel, and extension configs default to `isolate: false` for faster file startup.
|
||||
- `pnpm test` also passes `--isolate=false` at the wrapper level.
|
||||
- Opt back into Vitest file isolation with `OPENCLAW_TEST_ISOLATE=1 pnpm test`.
|
||||
- `pnpm test` inherits the isolation defaults from the root `vitest.config.ts` projects config.
|
||||
- Opt back into unit-file isolation with `OPENCLAW_TEST_ISOLATE=1 pnpm test`.
|
||||
- `OPENCLAW_TEST_NO_ISOLATE=0` or `OPENCLAW_TEST_NO_ISOLATE=false` also force isolated runs.
|
||||
- Fast-local iteration note:
|
||||
- `pnpm test:changed` runs the wrapper with `--changed origin/main`.
|
||||
- `pnpm test:changed:max` keeps the same changed-file filter but uses the wrapper's aggressive local planner profile.
|
||||
- `pnpm test:max` exposes that same planner profile for a full local run.
|
||||
- On supported local Node versions, including Node 25, the normal profile can use top-level lane parallelism. `pnpm test:max` still pushes the planner harder when you want a more aggressive local run.
|
||||
- The base Vitest config marks the wrapper manifests/config files as `forceRerunTriggers` so changed-mode reruns stay correct when scheduler inputs change.
|
||||
- The wrapper keeps `OPENCLAW_VITEST_FS_MODULE_CACHE` enabled on supported hosts, but assigns a lane-local `OPENCLAW_VITEST_FS_MODULE_CACHE_PATH` so concurrent Vitest processes do not race on one shared experimental cache directory.
|
||||
- Set `OPENCLAW_VITEST_FS_MODULE_CACHE_PATH=/abs/path` if you want one explicit cache location for direct single-run profiling.
|
||||
- `pnpm test:changed` runs the native projects config with `--changed origin/main`.
|
||||
- `pnpm test:max` and `pnpm test:changed:max` keep the same native projects config, just with a higher worker cap.
|
||||
- The base Vitest config marks the projects/config files as `forceRerunTriggers` so changed-mode reruns stay correct when test wiring changes.
|
||||
- The config keeps `OPENCLAW_VITEST_FS_MODULE_CACHE` enabled on supported hosts; set `OPENCLAW_VITEST_FS_MODULE_CACHE_PATH=/abs/path` if you want one explicit cache location for direct profiling.
|
||||
- Perf-debug note:
|
||||
- `pnpm test:perf:imports` enables Vitest import-duration reporting plus import-breakdown output.
|
||||
- `pnpm test:perf:imports:changed` scopes the same profiling view to files changed since `origin/main`.
|
||||
|
||||
@@ -165,7 +165,7 @@ flowchart TD
|
||||
|
||||
Common log signatures:
|
||||
|
||||
- `Gateway start blocked: set gateway.mode=local` → gateway mode is unset/remote.
|
||||
- `Gateway start blocked: set gateway.mode=local` or `existing config is missing gateway.mode` → gateway mode is remote, or the config file is missing the local-mode stamp and should be repaired.
|
||||
- `refusing to bind gateway ... without auth` → non-loopback bind without token/password.
|
||||
- `another gateway instance is already listening` or `EADDRINUSE` → port already taken.
|
||||
|
||||
|
||||
@@ -297,7 +297,7 @@ The lock file is at `/data/gateway.*.lock` (not in a subdirectory).
|
||||
|
||||
### Config Not Being Read
|
||||
|
||||
If using `--allow-unconfigured`, the gateway creates a minimal config. Your custom config at `/data/openclaw.json` should be read on restart.
|
||||
`--allow-unconfigured` only bypasses the startup guard. It does not create or repair `/data/openclaw.json`, so make sure your real config exists and includes `gateway.mode="local"` when you want a normal local gateway start.
|
||||
|
||||
Verify the config exists:
|
||||
|
||||
|
||||
@@ -502,9 +502,7 @@ if (sandboxRoot) {
|
||||
|
||||
### Google/Gemini
|
||||
|
||||
- Turn ordering fixes (`applyGoogleTurnOrderingFix`)
|
||||
- Tool schema sanitization (`sanitizeToolsForGoogle`)
|
||||
- Session history sanitization (`sanitizeSessionHistory`)
|
||||
- Plugin-owned tool schema sanitization
|
||||
|
||||
### OpenAI
|
||||
|
||||
|
||||
@@ -27,7 +27,13 @@ System control (launchd/systemd) lives on the Gateway host. See [Gateway](/gatew
|
||||
|
||||
Android node app ⇄ (mDNS/NSD + WebSocket) ⇄ **Gateway**
|
||||
|
||||
Android connects directly to the Gateway WebSocket (default `ws://<host>:18789`) and uses device pairing (`role: node`).
|
||||
Android connects directly to the Gateway WebSocket and uses device pairing (`role: node`).
|
||||
|
||||
For Tailscale or public hosts, Android requires a secure endpoint:
|
||||
|
||||
- Preferred: Tailscale Serve / Funnel with `https://<magicdns>` / `wss://<magicdns>`
|
||||
- Also supported: any other `wss://` Gateway URL with a real TLS endpoint
|
||||
- Cleartext `ws://` remains supported on private LAN addresses / `.local` hosts, plus `localhost`, `127.0.0.1`, and the Android emulator bridge (`10.0.2.2`)
|
||||
|
||||
### Prerequisites
|
||||
|
||||
@@ -36,6 +42,7 @@ Android connects directly to the Gateway WebSocket (default `ws://<host>:18789`)
|
||||
- Same LAN with mDNS/NSD, **or**
|
||||
- Same Tailscale tailnet using Wide-Area Bonjour / unicast DNS-SD (see below), **or**
|
||||
- Manual gateway host/port (fallback)
|
||||
- Tailnet/public mobile pairing does **not** use raw tailnet IP `ws://` endpoints. Use Tailscale Serve or another `wss://` URL instead.
|
||||
- You can run the CLI (`openclaw`) on the gateway machine (or via SSH).
|
||||
|
||||
### 1) Start the Gateway
|
||||
@@ -48,10 +55,13 @@ Confirm in logs you see something like:
|
||||
|
||||
- `listening on ws://0.0.0.0:18789`
|
||||
|
||||
For tailnet-only setups (recommended for Vienna ⇄ London), bind the gateway to the tailnet IP:
|
||||
For remote Android access over Tailscale, prefer Serve/Funnel instead of a raw tailnet bind:
|
||||
|
||||
- Set `gateway.bind: "tailnet"` in `~/.openclaw/openclaw.json` on the gateway host.
|
||||
- Restart the Gateway / macOS menubar app.
|
||||
```bash
|
||||
openclaw gateway --tailscale serve
|
||||
```
|
||||
|
||||
This gives Android a secure `wss://` / `https://` endpoint. A plain `gateway.bind: "tailnet"` setup is not enough for first-time remote Android pairing unless you also terminate TLS separately.
|
||||
|
||||
### 2) Verify discovery (optional)
|
||||
|
||||
@@ -65,7 +75,9 @@ More debugging notes: [Bonjour](/gateway/bonjour).
|
||||
|
||||
#### Tailnet (Vienna ⇄ London) discovery via unicast DNS-SD
|
||||
|
||||
Android NSD/mDNS discovery won’t cross networks. If your Android node and the gateway are on different networks but connected via Tailscale, use Wide-Area Bonjour / unicast DNS-SD instead:
|
||||
Android NSD/mDNS discovery won’t cross networks. If your Android node and the gateway are on different networks but connected via Tailscale, use Wide-Area Bonjour / unicast DNS-SD instead.
|
||||
|
||||
Discovery alone is not sufficient for tailnet/public Android pairing. The discovered route still needs a secure endpoint (`wss://` or Tailscale Serve):
|
||||
|
||||
1. Set up a DNS-SD zone (example `openclaw.internal.`) on the gateway host and publish `_openclaw-gw._tcp` records.
|
||||
2. Configure Tailscale split DNS for your chosen domain pointing at that DNS server.
|
||||
@@ -79,7 +91,7 @@ In the Android app:
|
||||
- The app keeps its gateway connection alive via a **foreground service** (persistent notification).
|
||||
- Open the **Connect** tab.
|
||||
- Use **Setup Code** or **Manual** mode.
|
||||
- If discovery is blocked, use manual host/port (and TLS/token/password when required) in **Advanced controls**.
|
||||
- If discovery is blocked, use manual host/port in **Advanced controls**. For private LAN hosts, `ws://` still works. For Tailscale/public hosts, turn on TLS and use a `wss://` / Tailscale Serve endpoint.
|
||||
|
||||
After the first successful pairing, Android auto-reconnects on launch:
|
||||
|
||||
|
||||
@@ -196,6 +196,12 @@ We do not publish separate `plugin-sdk/*-action-runtime` subpaths, and bundled
|
||||
plugins should import their own local runtime code directly from their
|
||||
extension-owned modules.
|
||||
|
||||
The same boundary applies to provider-named SDK seams in general: core should
|
||||
not import channel-specific convenience barrels for Slack, Discord, Signal,
|
||||
WhatsApp, or similar extensions. If core needs a behavior, either consume the
|
||||
bundled plugin's own `api.ts` / `runtime-api.ts` barrel or promote the need
|
||||
into a narrow generic capability in the shared SDK.
|
||||
|
||||
For polls specifically, there are two execution paths:
|
||||
|
||||
- `outbound.sendPoll` is the shared baseline for channels that fit the common
|
||||
@@ -998,8 +1004,10 @@ authoring plugins:
|
||||
contract on the plugin. Core then reads approval auth, delivery, render, and
|
||||
native-routing behavior through that one capability instead of mixing
|
||||
approval behavior into unrelated plugin fields.
|
||||
- `openclaw/plugin-sdk/channel-runtime` remains only as a compatibility shim.
|
||||
New code should import the narrower primitives instead.
|
||||
- `openclaw/plugin-sdk/channel-runtime` is deprecated and remains only as a
|
||||
compatibility shim for older plugins. New code should import the narrower
|
||||
generic primitives instead, and repo code should not add new imports of the
|
||||
shim.
|
||||
- Bundled extension internals remain private. External plugins should use only
|
||||
`openclaw/plugin-sdk/*` subpaths. OpenClaw core/test code may use the repo
|
||||
public entry points under a plugin package root such as `index.js`, `api.js`,
|
||||
|
||||
@@ -46,6 +46,14 @@ The old approach caused problems:
|
||||
The modern plugin SDK fixes this: each import path (`openclaw/plugin-sdk/\<subpath\>`)
|
||||
is a small, self-contained module with a clear purpose and documented contract.
|
||||
|
||||
Legacy provider convenience seams for bundled channels are also gone. Imports
|
||||
such as `openclaw/plugin-sdk/slack`, `openclaw/plugin-sdk/discord`,
|
||||
`openclaw/plugin-sdk/signal`, `openclaw/plugin-sdk/whatsapp`, and
|
||||
`openclaw/plugin-sdk/telegram-core` were private mono-repo shortcuts, not
|
||||
stable plugin contracts. Use narrow generic SDK subpaths instead. Inside the
|
||||
bundled plugin workspace, keep provider-owned helpers in that plugin's own
|
||||
`api.ts` or `runtime-api.ts`.
|
||||
|
||||
## How to migrate
|
||||
|
||||
<Steps>
|
||||
@@ -147,7 +155,7 @@ is a small, self-contained module with a clear purpose and documented contract.
|
||||
| `plugin-sdk/channel-config-schema` | Config schema builders | Channel config schema types |
|
||||
| `plugin-sdk/channel-policy` | Group/DM policy resolution | `resolveChannelGroupRequireMention` |
|
||||
| `plugin-sdk/channel-lifecycle` | Account status tracking | `createAccountStatusSink` |
|
||||
| `plugin-sdk/channel-runtime` | Runtime wiring helpers | Channel runtime utilities |
|
||||
| `plugin-sdk/channel-runtime` | Deprecated compatibility shim | Legacy channel runtime utilities only |
|
||||
| `plugin-sdk/channel-send-result` | Send result types | Reply result types |
|
||||
| `plugin-sdk/runtime-store` | Persistent plugin storage | `createPluginRuntimeStore` |
|
||||
| `plugin-sdk/approval-runtime` | Approval prompt helpers | Exec/plugin approval payload, approval capability/profile helpers, native approval routing/runtime helpers |
|
||||
|
||||
@@ -32,6 +32,13 @@ import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
|
||||
Each subpath is a small, self-contained module. This keeps startup fast and
|
||||
prevents circular dependency issues.
|
||||
|
||||
Do not add or depend on provider-named convenience seams such as
|
||||
`openclaw/plugin-sdk/slack`, `openclaw/plugin-sdk/discord`,
|
||||
`openclaw/plugin-sdk/signal`, or `openclaw/plugin-sdk/whatsapp`. Bundled plugins should compose generic SDK
|
||||
subpaths inside their own `api.ts` or `runtime-api.ts` barrels, and core should
|
||||
either use those plugin-local barrels or add a narrow generic SDK contract when
|
||||
the need is truly cross-channel.
|
||||
|
||||
## Subpath reference
|
||||
|
||||
The most commonly used subpaths, grouped by purpose. The full list of 100+
|
||||
|
||||
@@ -252,7 +252,7 @@ pnpm test:coverage
|
||||
If local runs cause memory pressure:
|
||||
|
||||
```bash
|
||||
OPENCLAW_TEST_PROFILE=low OPENCLAW_TEST_SERIAL_GATEWAY=1 pnpm test
|
||||
OPENCLAW_VITEST_MAX_WORKERS=1 pnpm test
|
||||
```
|
||||
|
||||
## Related
|
||||
|
||||
@@ -68,5 +68,7 @@ openclaw models set github-copilot/gpt-4o
|
||||
- Requires an interactive TTY; run it directly in a terminal.
|
||||
- Copilot model availability depends on your plan; if a model is rejected, try
|
||||
another ID (for example `github-copilot/gpt-4.1`).
|
||||
- Claude model IDs use the Anthropic Messages transport automatically; GPT, o-series,
|
||||
and Gemini models keep the OpenAI Responses transport.
|
||||
- The login stores a GitHub token in the auth profile store and exchanges it for a
|
||||
Copilot API token when OpenClaw runs.
|
||||
|
||||
@@ -50,6 +50,7 @@ Looking for chat channel docs (WhatsApp/Telegram/Discord/Slack/Mattermost (plugi
|
||||
- [Qianfan](/providers/qianfan)
|
||||
- [Qwen / Model Studio (Alibaba Cloud)](/providers/qwen_modelstudio)
|
||||
- [SGLang (local models)](/providers/sglang)
|
||||
- [StepFun](/providers/stepfun)
|
||||
- [Synthetic](/providers/synthetic)
|
||||
- [Together AI](/providers/together)
|
||||
- [Venice (Venice AI, privacy-focused)](/providers/venice)
|
||||
|
||||
@@ -24,22 +24,23 @@ model as `provider/model`.
|
||||
|
||||
## Supported providers (starter set)
|
||||
|
||||
- [OpenAI (API + Codex)](/providers/openai)
|
||||
- [Anthropic (API + Claude Code CLI)](/providers/anthropic)
|
||||
- [OpenRouter](/providers/openrouter)
|
||||
- [Vercel AI Gateway](/providers/vercel-ai-gateway)
|
||||
- [Amazon Bedrock](/providers/bedrock)
|
||||
- [Cloudflare AI Gateway](/providers/cloudflare-ai-gateway)
|
||||
- [Moonshot AI (Kimi + Kimi Coding)](/providers/moonshot)
|
||||
- [Mistral](/providers/mistral)
|
||||
- [Synthetic](/providers/synthetic)
|
||||
- [OpenCode (Zen + Go)](/providers/opencode)
|
||||
- [Z.AI](/providers/zai)
|
||||
- [GLM models](/providers/glm)
|
||||
- [MiniMax](/providers/minimax)
|
||||
- [Venice (Venice AI)](/providers/venice)
|
||||
- [Amazon Bedrock](/providers/bedrock)
|
||||
- [Mistral](/providers/mistral)
|
||||
- [Moonshot AI (Kimi + Kimi Coding)](/providers/moonshot)
|
||||
- [OpenAI (API + Codex)](/providers/openai)
|
||||
- [OpenCode (Zen + Go)](/providers/opencode)
|
||||
- [OpenRouter](/providers/openrouter)
|
||||
- [Qianfan](/providers/qianfan)
|
||||
- [StepFun](/providers/stepfun)
|
||||
- [Synthetic](/providers/synthetic)
|
||||
- [Vercel AI Gateway](/providers/vercel-ai-gateway)
|
||||
- [Venice (Venice AI)](/providers/venice)
|
||||
- [xAI](/providers/xai)
|
||||
- [Z.AI](/providers/zai)
|
||||
|
||||
For the full provider catalog (xAI, Groq, Mistral, etc.) and advanced configuration,
|
||||
see [Model providers](/concepts/model-providers).
|
||||
|
||||
@@ -235,6 +235,33 @@ To use cloud models, select **Cloud + Local** mode during setup. The wizard chec
|
||||
|
||||
You can also sign in directly at [ollama.com/signin](https://ollama.com/signin).
|
||||
|
||||
## Ollama Web Search
|
||||
|
||||
OpenClaw also supports **Ollama Web Search** as a bundled `web_search`
|
||||
provider.
|
||||
|
||||
- It uses your configured Ollama host (`models.providers.ollama.baseUrl` when
|
||||
set, otherwise `http://127.0.0.1:11434`).
|
||||
- It is key-free.
|
||||
- It requires Ollama to be running and signed in with `ollama signin`.
|
||||
|
||||
Choose **Ollama Web Search** during `openclaw onboard` or
|
||||
`openclaw configure --section web`, or set:
|
||||
|
||||
```json5
|
||||
{
|
||||
tools: {
|
||||
web: {
|
||||
search: {
|
||||
provider: "ollama",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
For the full setup and behavior details, see [Ollama Web Search](/tools/ollama-search).
|
||||
|
||||
## Advanced
|
||||
|
||||
### Reasoning models
|
||||
|
||||
@@ -17,17 +17,27 @@ background.
|
||||
|
||||
</Warning>
|
||||
|
||||
## Recommended: Model Studio (Alibaba Cloud Coding Plan)
|
||||
## Recommended: Model Studio (Alibaba Cloud)
|
||||
|
||||
Use [Model Studio](/providers/qwen_modelstudio) for officially supported access to
|
||||
Qwen models (Qwen 3.5 Plus, GLM-4.7, Kimi K2.5, and more).
|
||||
Qwen models (Qwen 3.6 Plus, Qwen 3.5 Plus, GLM-5, Kimi K2.5, and more).
|
||||
|
||||
If you want `qwen3.6-plus` directly from Alibaba Cloud, prefer the **Standard
|
||||
(pay-as-you-go)** Model Studio endpoint. Coding Plan support can lag behind the
|
||||
public Model Studio catalog.
|
||||
|
||||
```bash
|
||||
# Global endpoint
|
||||
# Global Coding Plan endpoint
|
||||
openclaw onboard --auth-choice modelstudio-api-key
|
||||
|
||||
# China endpoint
|
||||
# China Coding Plan endpoint
|
||||
openclaw onboard --auth-choice modelstudio-api-key-cn
|
||||
|
||||
# Global Standard (pay-as-you-go) endpoint
|
||||
openclaw onboard --auth-choice modelstudio-standard-api-key
|
||||
|
||||
# China Standard (pay-as-you-go) endpoint
|
||||
openclaw onboard --auth-choice modelstudio-standard-api-key-cn
|
||||
```
|
||||
|
||||
See [Model Studio](/providers/qwen_modelstudio) for full setup details.
|
||||
|
||||
@@ -13,6 +13,15 @@ The Model Studio provider gives access to Alibaba Cloud models including Qwen
|
||||
and third-party models hosted on the platform. Two billing plans are supported:
|
||||
**Standard** (pay-as-you-go) and **Coding Plan** (subscription).
|
||||
|
||||
<Info>
|
||||
|
||||
If you need **`qwen3.6-plus`**, prefer **Standard (pay-as-you-go)**. Coding
|
||||
Plan availability can lag behind the public Model Studio catalog, and the
|
||||
Coding Plan API can reject a model until it appears in your plan's supported
|
||||
model list.
|
||||
|
||||
</Info>
|
||||
|
||||
- Provider: `modelstudio`
|
||||
- Auth: `MODELSTUDIO_API_KEY`
|
||||
- API: OpenAI-compatible
|
||||
@@ -71,12 +80,25 @@ override with a custom `baseUrl` in config.
|
||||
## Available models
|
||||
|
||||
- **qwen3.5-plus** (default) — Qwen 3.5 Plus
|
||||
- **qwen3.6-plus** — Qwen 3.6 Plus
|
||||
- **qwen3-coder-plus**, **qwen3-coder-next** — Qwen coding models
|
||||
- **GLM-5** — GLM models via Alibaba
|
||||
- **Kimi K2.5** — Moonshot AI via Alibaba
|
||||
- **MiniMax-M2.7** — MiniMax via Alibaba
|
||||
- **MiniMax-M2.5** — MiniMax via Alibaba
|
||||
|
||||
Some models (qwen3.5-plus, kimi-k2.5) support image input. Context windows range from 200K to 1M tokens.
|
||||
Some models (qwen3.5-plus, qwen3.6-plus, kimi-k2.5) support image input. Context windows range from 200K to 1M tokens. Availability can vary by endpoint and billing plan.
|
||||
|
||||
## Qwen 3.6 Plus availability
|
||||
|
||||
`qwen3.6-plus` is available on the Standard (pay-as-you-go) Model Studio
|
||||
endpoints:
|
||||
|
||||
- China: `dashscope.aliyuncs.com/compatible-mode/v1`
|
||||
- Global: `dashscope-intl.aliyuncs.com/compatible-mode/v1`
|
||||
|
||||
If the Coding Plan endpoints return an "unsupported model" error for
|
||||
`qwen3.6-plus`, switch to Standard (pay-as-you-go) instead of the Coding Plan
|
||||
endpoint/key pair.
|
||||
|
||||
## Environment note
|
||||
|
||||
|
||||
137
docs/providers/stepfun.md
Normal file
137
docs/providers/stepfun.md
Normal file
@@ -0,0 +1,137 @@
|
||||
---
|
||||
summary: "Use StepFun models with OpenClaw"
|
||||
read_when:
|
||||
- You want StepFun models in OpenClaw
|
||||
- You need StepFun setup guidance
|
||||
title: "StepFun"
|
||||
---
|
||||
|
||||
# StepFun
|
||||
|
||||
OpenClaw includes a bundled StepFun provider plugin with two provider ids:
|
||||
|
||||
- `stepfun` for the standard endpoint
|
||||
- `stepfun-plan` for the Step Plan endpoint
|
||||
|
||||
The built-in catalogs currently differ by surface:
|
||||
|
||||
- Standard: `step-3.5-flash`
|
||||
- Step Plan: `step-3.5-flash`, `step-3.5-flash-2603`
|
||||
|
||||
## Region and endpoint overview
|
||||
|
||||
- China standard endpoint: `https://api.stepfun.com/v1`
|
||||
- Global standard endpoint: `https://api.stepfun.ai/v1`
|
||||
- China Step Plan endpoint: `https://api.stepfun.com/step_plan/v1`
|
||||
- Global Step Plan endpoint: `https://api.stepfun.ai/step_plan/v1`
|
||||
- Auth env var: `STEPFUN_API_KEY`
|
||||
|
||||
Use a China key with the `.com` endpoints and a global key with the `.ai`
|
||||
endpoints.
|
||||
|
||||
## CLI setup
|
||||
|
||||
Interactive setup:
|
||||
|
||||
```bash
|
||||
openclaw onboard
|
||||
```
|
||||
|
||||
Choose one of these auth choices:
|
||||
|
||||
- `stepfun-standard-api-key-cn`
|
||||
- `stepfun-standard-api-key-intl`
|
||||
- `stepfun-plan-api-key-cn`
|
||||
- `stepfun-plan-api-key-intl`
|
||||
|
||||
Non-interactive examples:
|
||||
|
||||
```bash
|
||||
openclaw onboard --auth-choice stepfun-standard-api-key-intl --stepfun-api-key "$STEPFUN_API_KEY"
|
||||
openclaw onboard --auth-choice stepfun-plan-api-key-intl --stepfun-api-key "$STEPFUN_API_KEY"
|
||||
```
|
||||
|
||||
## Model refs
|
||||
|
||||
- Standard default model: `stepfun/step-3.5-flash`
|
||||
- Step Plan default model: `stepfun-plan/step-3.5-flash`
|
||||
- Step Plan alternate model: `stepfun-plan/step-3.5-flash-2603`
|
||||
|
||||
## Config snippets
|
||||
|
||||
Standard provider:
|
||||
|
||||
```json5
|
||||
{
|
||||
env: { STEPFUN_API_KEY: "your-key" },
|
||||
agents: { defaults: { model: { primary: "stepfun/step-3.5-flash" } } },
|
||||
models: {
|
||||
mode: "merge",
|
||||
providers: {
|
||||
stepfun: {
|
||||
baseUrl: "https://api.stepfun.ai/v1",
|
||||
api: "openai-completions",
|
||||
apiKey: "${STEPFUN_API_KEY}",
|
||||
models: [
|
||||
{
|
||||
id: "step-3.5-flash",
|
||||
name: "Step 3.5 Flash",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 262144,
|
||||
maxTokens: 65536,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Step Plan provider:
|
||||
|
||||
```json5
|
||||
{
|
||||
env: { STEPFUN_API_KEY: "your-key" },
|
||||
agents: { defaults: { model: { primary: "stepfun-plan/step-3.5-flash" } } },
|
||||
models: {
|
||||
mode: "merge",
|
||||
providers: {
|
||||
"stepfun-plan": {
|
||||
baseUrl: "https://api.stepfun.ai/step_plan/v1",
|
||||
api: "openai-completions",
|
||||
apiKey: "${STEPFUN_API_KEY}",
|
||||
models: [
|
||||
{
|
||||
id: "step-3.5-flash",
|
||||
name: "Step 3.5 Flash",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 262144,
|
||||
maxTokens: 65536,
|
||||
},
|
||||
{
|
||||
id: "step-3.5-flash-2603",
|
||||
name: "Step 3.5 Flash 2603",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 262144,
|
||||
maxTokens: 65536,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- The provider is bundled with OpenClaw, so there is no separate plugin install step.
|
||||
- `step-3.5-flash-2603` is currently exposed only on `stepfun-plan`.
|
||||
- A single auth flow writes region-matched profiles for both `stepfun` and `stepfun-plan`, so both surfaces can be discovered together.
|
||||
- Use `openclaw models list` and `openclaw models set <provider/model>` to inspect or switch models.
|
||||
- For the broader provider overview, see [Model providers](/concepts/model-providers).
|
||||
@@ -51,7 +51,8 @@ OpenClaw has three public release lanes:
|
||||
- real npm publish must pass a successful npm `preflight_run_id`
|
||||
- stable npm releases default to `beta`
|
||||
- stable npm publish can target `latest` explicitly via workflow input
|
||||
- stable npm promotion from `beta` to `latest` is still available as a separate manual workflow step
|
||||
- stable npm promotion from `beta` to `latest` is still available as an explicit manual mode on the trusted `OpenClaw NPM Release` workflow
|
||||
- that promotion mode still needs a valid `NPM_TOKEN` in the `npm-release` environment because npm `dist-tag` management is separate from trusted publishing
|
||||
- public `macOS Release` is validation-only
|
||||
- real private mac publish must pass successful private mac
|
||||
`preflight_run_id` and `validate_run_id`
|
||||
@@ -66,7 +67,7 @@ OpenClaw has three public release lanes:
|
||||
so we do not ship an empty browser dashboard again
|
||||
- If the release work touched CI planning, extension timing manifests, or fast
|
||||
test matrices, regenerate and review the planner-owned `checks-fast-extensions`
|
||||
shard plan via `node scripts/ci-write-manifest-outputs.mjs --workflow ci`
|
||||
workflow matrix outputs from `.github/workflows/ci.yml`
|
||||
before approval so release notes do not describe a stale CI layout
|
||||
- Stable macOS release readiness also includes the updater surfaces:
|
||||
- the GitHub release must end up with the packaged `.zip`, `.dmg`, and `.dSYM.zip`
|
||||
@@ -86,6 +87,8 @@ OpenClaw has three public release lanes:
|
||||
- `preflight_run_id`: required on the real publish path so the workflow reuses
|
||||
the prepared tarball from the successful preflight run
|
||||
- `npm_dist_tag`: npm target tag for the publish path; defaults to `beta`
|
||||
- `promote_beta_to_latest`: `true` to skip publish and move an already-published
|
||||
stable `beta` build onto `latest`
|
||||
|
||||
Rules:
|
||||
|
||||
@@ -93,6 +96,10 @@ Rules:
|
||||
- Beta prerelease tags may publish only to `beta`
|
||||
- The real publish path must use the same `npm_dist_tag` used during preflight;
|
||||
the workflow verifies that metadata before publish continues
|
||||
- Promotion mode must use a stable or correction tag, `preflight_only=false`,
|
||||
an empty `preflight_run_id`, and `npm_dist_tag=beta`
|
||||
- Promotion mode also requires a valid `NPM_TOKEN` in the `npm-release`
|
||||
environment because `npm dist-tag add` still needs regular npm auth
|
||||
|
||||
## Stable npm release sequence
|
||||
|
||||
@@ -104,9 +111,13 @@ When cutting a stable npm release:
|
||||
3. Save the successful `preflight_run_id`
|
||||
4. Run `OpenClaw NPM Release` again with `preflight_only=false`, the same
|
||||
`tag`, the same `npm_dist_tag`, and the saved `preflight_run_id`
|
||||
5. If the release landed on `beta`, run `OpenClaw NPM Promote Beta` later with
|
||||
the exact stable version when you want to move that published build to
|
||||
`latest`
|
||||
5. If the release landed on `beta`, run `OpenClaw NPM Release` later with the
|
||||
same stable `tag`, `promote_beta_to_latest=true`, `preflight_only=false`,
|
||||
`preflight_run_id` empty, and `npm_dist_tag=beta` when you want to move that
|
||||
published build to `latest`
|
||||
|
||||
The promotion mode still requires the `npm-release` environment approval and a
|
||||
valid `NPM_TOKEN` in that environment.
|
||||
|
||||
That keeps the direct publish path and the beta-first promotion path both
|
||||
documented and operator-visible.
|
||||
@@ -114,7 +125,6 @@ documented and operator-visible.
|
||||
## Public references
|
||||
|
||||
- [`.github/workflows/openclaw-npm-release.yml`](https://github.com/openclaw/openclaw/blob/main/.github/workflows/openclaw-npm-release.yml)
|
||||
- [`.github/workflows/openclaw-npm-promote-beta.yml`](https://github.com/openclaw/openclaw/blob/main/.github/workflows/openclaw-npm-promote-beta.yml)
|
||||
- [`scripts/openclaw-npm-release-check.ts`](https://github.com/openclaw/openclaw/blob/main/scripts/openclaw-npm-release-check.ts)
|
||||
- [`scripts/package-mac-dist.sh`](https://github.com/openclaw/openclaw/blob/main/scripts/package-mac-dist.sh)
|
||||
- [`scripts/make_appcast.sh`](https://github.com/openclaw/openclaw/blob/main/scripts/make_appcast.sh)
|
||||
|
||||
@@ -77,13 +77,18 @@ See [Memory](/concepts/memory).
|
||||
|
||||
### 4) Web search tool
|
||||
|
||||
`web_search` uses API keys and may incur usage charges depending on your provider:
|
||||
`web_search` may incur usage charges depending on your provider:
|
||||
|
||||
- **Brave Search API**: `BRAVE_API_KEY` or `plugins.entries.brave.config.webSearch.apiKey`
|
||||
- **Exa**: `EXA_API_KEY` or `plugins.entries.exa.config.webSearch.apiKey`
|
||||
- **Firecrawl**: `FIRECRAWL_API_KEY` or `plugins.entries.firecrawl.config.webSearch.apiKey`
|
||||
- **Gemini (Google Search)**: `GEMINI_API_KEY` or `plugins.entries.google.config.webSearch.apiKey`
|
||||
- **Grok (xAI)**: `XAI_API_KEY` or `plugins.entries.xai.config.webSearch.apiKey`
|
||||
- **Kimi (Moonshot)**: `KIMI_API_KEY`, `MOONSHOT_API_KEY`, or `plugins.entries.moonshot.config.webSearch.apiKey`
|
||||
- **Ollama Web Search**: key-free, but requires a reachable Ollama host plus `ollama signin`
|
||||
- **Perplexity Search API**: `PERPLEXITY_API_KEY`, `OPENROUTER_API_KEY`, or `plugins.entries.perplexity.config.webSearch.apiKey`
|
||||
- **Tavily**: `TAVILY_API_KEY` or `plugins.entries.tavily.config.webSearch.apiKey`
|
||||
- **DuckDuckGo**: key-free fallback (no API billing, but unofficial and HTML-based)
|
||||
|
||||
Legacy `tools.web.search.*` provider paths still load through the temporary compatibility shim, but they are no longer the recommended config surface.
|
||||
|
||||
|
||||
@@ -24,6 +24,17 @@ Scope intent:
|
||||
|
||||
- `models.providers.*.apiKey`
|
||||
- `models.providers.*.headers.*`
|
||||
- `models.providers.*.request.auth.token`
|
||||
- `models.providers.*.request.auth.value`
|
||||
- `models.providers.*.request.headers.*`
|
||||
- `models.providers.*.request.proxy.tls.ca`
|
||||
- `models.providers.*.request.proxy.tls.cert`
|
||||
- `models.providers.*.request.proxy.tls.key`
|
||||
- `models.providers.*.request.proxy.tls.passphrase`
|
||||
- `models.providers.*.request.tls.ca`
|
||||
- `models.providers.*.request.tls.cert`
|
||||
- `models.providers.*.request.tls.key`
|
||||
- `models.providers.*.request.tls.passphrase`
|
||||
- `skills.entries.*.apiKey`
|
||||
- `agents.defaults.memorySearch.remote.apiKey`
|
||||
- `agents.list[].memorySearch.remote.apiKey`
|
||||
|
||||
@@ -440,6 +440,83 @@
|
||||
"secretShape": "secret_input",
|
||||
"optIn": true
|
||||
},
|
||||
{
|
||||
"id": "models.providers.*.request.auth.token",
|
||||
"configFile": "openclaw.json",
|
||||
"path": "models.providers.*.request.auth.token",
|
||||
"secretShape": "secret_input",
|
||||
"optIn": true
|
||||
},
|
||||
{
|
||||
"id": "models.providers.*.request.auth.value",
|
||||
"configFile": "openclaw.json",
|
||||
"path": "models.providers.*.request.auth.value",
|
||||
"secretShape": "secret_input",
|
||||
"optIn": true
|
||||
},
|
||||
{
|
||||
"id": "models.providers.*.request.headers.*",
|
||||
"configFile": "openclaw.json",
|
||||
"path": "models.providers.*.request.headers.*",
|
||||
"secretShape": "secret_input",
|
||||
"optIn": true
|
||||
},
|
||||
{
|
||||
"id": "models.providers.*.request.proxy.tls.ca",
|
||||
"configFile": "openclaw.json",
|
||||
"path": "models.providers.*.request.proxy.tls.ca",
|
||||
"secretShape": "secret_input",
|
||||
"optIn": true
|
||||
},
|
||||
{
|
||||
"id": "models.providers.*.request.proxy.tls.cert",
|
||||
"configFile": "openclaw.json",
|
||||
"path": "models.providers.*.request.proxy.tls.cert",
|
||||
"secretShape": "secret_input",
|
||||
"optIn": true
|
||||
},
|
||||
{
|
||||
"id": "models.providers.*.request.proxy.tls.key",
|
||||
"configFile": "openclaw.json",
|
||||
"path": "models.providers.*.request.proxy.tls.key",
|
||||
"secretShape": "secret_input",
|
||||
"optIn": true
|
||||
},
|
||||
{
|
||||
"id": "models.providers.*.request.proxy.tls.passphrase",
|
||||
"configFile": "openclaw.json",
|
||||
"path": "models.providers.*.request.proxy.tls.passphrase",
|
||||
"secretShape": "secret_input",
|
||||
"optIn": true
|
||||
},
|
||||
{
|
||||
"id": "models.providers.*.request.tls.ca",
|
||||
"configFile": "openclaw.json",
|
||||
"path": "models.providers.*.request.tls.ca",
|
||||
"secretShape": "secret_input",
|
||||
"optIn": true
|
||||
},
|
||||
{
|
||||
"id": "models.providers.*.request.tls.cert",
|
||||
"configFile": "openclaw.json",
|
||||
"path": "models.providers.*.request.tls.cert",
|
||||
"secretShape": "secret_input",
|
||||
"optIn": true
|
||||
},
|
||||
{
|
||||
"id": "models.providers.*.request.tls.key",
|
||||
"configFile": "openclaw.json",
|
||||
"path": "models.providers.*.request.tls.key",
|
||||
"secretShape": "secret_input",
|
||||
"optIn": true
|
||||
},
|
||||
{
|
||||
"id": "models.providers.*.request.tls.passphrase",
|
||||
"configFile": "openclaw.json",
|
||||
"path": "models.providers.*.request.tls.passphrase",
|
||||
"secretShape": "secret_input",
|
||||
"optIn": true
|
||||
},
|
||||
{
|
||||
"id": "plugins.entries.brave.config.webSearch.apiKey",
|
||||
"configFile": "openclaw.json",
|
||||
|
||||
@@ -12,17 +12,16 @@ title: "Tests"
|
||||
- `pnpm test:force`: Kills any lingering gateway process holding the default control port, then runs the full Vitest suite with an isolated gateway port so server tests don’t collide with a running instance. Use this when a prior gateway run left port 18789 occupied.
|
||||
- `pnpm test:coverage`: Runs the unit suite with V8 coverage (via `vitest.unit.config.ts`). Global thresholds are 70% lines/branches/functions/statements. Coverage excludes integration-heavy entrypoints (CLI wiring, gateway/telegram bridges, webchat static server) to keep the target focused on unit-testable logic.
|
||||
- `pnpm test:coverage:changed`: Runs unit coverage only for files changed since `origin/main`.
|
||||
- `pnpm test:changed`: runs the wrapper with `--changed origin/main`. The base Vitest config treats the wrapper manifests/config files as `forceRerunTriggers` so scheduler changes still rerun broadly when needed.
|
||||
- `pnpm test`: runs the full wrapper. It keeps only a small behavioral override manifest in git, then uses a checked-in timing snapshot to peel the heaviest measured unit files into dedicated lanes.
|
||||
- Unit files default to `threads` in the wrapper; keep fork-only exceptions documented in `test/fixtures/test-parallel.behavior.json`.
|
||||
- `pnpm test:channels` now defaults to `threads` via `vitest.channels.config.ts`; the March 22, 2026 direct full-suite control run passed clean without channel-specific fork exceptions.
|
||||
- `pnpm test:extensions` runs through the wrapper and keeps documented extension fork-only exceptions in `test/fixtures/test-parallel.behavior.json`; the shared extension lane still defaults to `threads`.
|
||||
- `pnpm test:changed`: runs the native Vitest projects config with `--changed origin/main`. The base config treats the projects/config files as `forceRerunTriggers` so wiring changes still rerun broadly when needed.
|
||||
- `pnpm test`: runs the native Vitest projects config (`unit` + `boundary`) via a tiny passthrough wrapper so `pnpm test -- <filter>` keeps working.
|
||||
- Unit, channel, and extension configs default to `pool: "forks"`.
|
||||
- `pnpm test:channels` runs `vitest.channels.config.ts`.
|
||||
- `pnpm test:extensions` runs `vitest.extensions.config.ts`.
|
||||
- `pnpm test:extensions`: runs extension/plugin suites.
|
||||
- `pnpm test:perf:imports`: enables Vitest import-duration + import-breakdown reporting for the wrapper.
|
||||
- `pnpm test:perf:imports:changed`: same import profiling, but only for files changed since `origin/main`.
|
||||
- `pnpm test:perf:profile:main`: writes a CPU profile for the Vitest main thread (`.artifacts/vitest-main-profile`).
|
||||
- `pnpm test:perf:profile:runner`: writes CPU + heap profiles for the unit runner (`.artifacts/vitest-runner-profile`).
|
||||
- `pnpm test:perf:update-timings`: refreshes the checked-in slow-file timing snapshot used by `scripts/test-parallel.mjs`.
|
||||
- Gateway integration: opt-in via `OPENCLAW_TEST_INCLUDE_GATEWAY=1 pnpm test` or `pnpm test:gateway`.
|
||||
- `pnpm test:e2e`: Runs gateway end-to-end smoke tests (multi-instance WS/HTTP/node pairing). Defaults to `forks` + adaptive workers in `vitest.e2e.config.ts`; tune with `OPENCLAW_E2E_WORKERS=<n>` and set `OPENCLAW_E2E_VERBOSE=1` for verbose logs.
|
||||
- `pnpm test:live`: Runs provider live tests (minimax/zai). Requires API keys and `LIVE=1` (or provider-specific `*_LIVE_TEST=1`) to unskip.
|
||||
@@ -38,9 +37,9 @@ For local PR land/gate checks, run:
|
||||
- `pnpm test`
|
||||
- `pnpm check:docs`
|
||||
|
||||
If `pnpm test` flakes on a loaded host, rerun once before treating it as a regression, then isolate with `pnpm vitest run <path/to/test>`. For memory-constrained hosts, use:
|
||||
If `pnpm test` flakes on a loaded host, rerun once before treating it as a regression, then isolate with `pnpm test -- <path/to/test>`. For memory-constrained hosts, use:
|
||||
|
||||
- `OPENCLAW_TEST_PROFILE=low OPENCLAW_TEST_SERIAL_GATEWAY=1 pnpm test`
|
||||
- `OPENCLAW_VITEST_MAX_WORKERS=1 pnpm test`
|
||||
- `OPENCLAW_VITEST_FS_MODULE_CACHE_PATH=/tmp/openclaw-vitest-cache pnpm test:changed`
|
||||
|
||||
## Model latency bench (local keys)
|
||||
|
||||
@@ -48,6 +48,9 @@ For a high-level overview, see [Onboarding (CLI)](/start/wizard).
|
||||
- More detail: [Cloudflare AI Gateway](/providers/cloudflare-ai-gateway)
|
||||
- **MiniMax**: config is auto-written; hosted default is `MiniMax-M2.7`.
|
||||
- More detail: [MiniMax](/providers/minimax)
|
||||
- **StepFun**: config is auto-written for StepFun standard or Step Plan on China or global endpoints.
|
||||
- Standard currently includes `step-3.5-flash`, and Step Plan also includes `step-3.5-flash-2603`.
|
||||
- More detail: [StepFun](/providers/stepfun)
|
||||
- **Synthetic (Anthropic-compatible)**: prompts for `SYNTHETIC_API_KEY`.
|
||||
- More detail: [Synthetic](/providers/synthetic)
|
||||
- **Moonshot (Kimi K2)**: config is auto-written.
|
||||
@@ -97,8 +100,8 @@ For a high-level overview, see [Onboarding (CLI)](/start/wizard).
|
||||
- DM security: default is pairing. First DM sends a code; approve via `openclaw pairing approve <channel> <code>` or use allowlists.
|
||||
</Step>
|
||||
<Step title="Web search">
|
||||
- Pick a provider: Perplexity, Brave, Gemini, Grok, or Kimi (or skip).
|
||||
- Paste your API key (QuickStart auto-detects keys from env vars or existing config).
|
||||
- Pick a supported provider such as Brave, Firecrawl, Gemini, Grok, Kimi, Ollama Web Search, Perplexity, or Tavily (or skip).
|
||||
- API-backed providers can use env vars or existing config for quick setup; key-free providers use their provider-specific prerequisites instead.
|
||||
- Skip with `--skip-search`.
|
||||
- Configure later: `openclaw configure --section web`.
|
||||
</Step>
|
||||
|
||||
@@ -59,6 +59,7 @@ openclaw gateway --port 18789
|
||||
|
||||
```json5
|
||||
{
|
||||
gateway: { mode: "local" },
|
||||
channels: { whatsapp: { allowFrom: ["+15555550123"] } },
|
||||
}
|
||||
```
|
||||
|
||||
@@ -16,7 +16,7 @@ For the short guide, see [Onboarding (CLI)](/start/wizard).
|
||||
|
||||
Local mode (default) walks you through:
|
||||
|
||||
- Model and auth setup (OpenAI Code subscription OAuth, Anthropic API key or setup token, plus MiniMax, GLM, Ollama, Moonshot, and AI Gateway options)
|
||||
- Model and auth setup (OpenAI Code subscription OAuth, Anthropic API key or setup token, plus MiniMax, GLM, Ollama, Moonshot, StepFun, and AI Gateway options)
|
||||
- Workspace location and bootstrap files
|
||||
- Gateway settings (port, bind, auth, tailscale)
|
||||
- Channels and providers (Telegram, WhatsApp, Discord, Google Chat, Mattermost plugin, Signal)
|
||||
@@ -177,6 +177,11 @@ What you set:
|
||||
Config is auto-written. Hosted default is `MiniMax-M2.7`.
|
||||
More detail: [MiniMax](/providers/minimax).
|
||||
</Accordion>
|
||||
<Accordion title="StepFun">
|
||||
Config is auto-written for StepFun standard or Step Plan on China or global endpoints.
|
||||
Standard currently includes `step-3.5-flash`, and Step Plan also includes `step-3.5-flash-2603`.
|
||||
More detail: [StepFun](/providers/stepfun).
|
||||
</Accordion>
|
||||
<Accordion title="Synthetic (Anthropic-compatible)">
|
||||
Prompts for `SYNTHETIC_API_KEY`.
|
||||
More detail: [Synthetic](/providers/synthetic).
|
||||
|
||||
@@ -36,8 +36,9 @@ openclaw agents add <name>
|
||||
|
||||
<Tip>
|
||||
CLI onboarding includes a web search step where you can pick a provider
|
||||
(Perplexity, Brave, Gemini, Grok, or Kimi) and paste your API key so the agent
|
||||
can use `web_search`. You can also configure this later with
|
||||
such as Brave, Firecrawl, Gemini, Grok, Kimi, Ollama Web Search, Perplexity,
|
||||
or Tavily. Some providers require an API key, while others are key-free. You
|
||||
can also configure this later with
|
||||
`openclaw configure --section web`. Docs: [Web tools](/tools/web).
|
||||
</Tip>
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user