mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-12 17:23:20 +08:00
Compare commits
1108 Commits
core-runti
...
codex/docs
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dca9b01c75 | ||
|
|
6b12c89d57 | ||
|
|
0a12c35bce | ||
|
|
68fd946e6d | ||
|
|
ce0e191ae0 | ||
|
|
a65eb1b864 | ||
|
|
4407df6c03 | ||
|
|
06ff594a3e | ||
|
|
31160dc069 | ||
|
|
89b6d02481 | ||
|
|
081da17090 | ||
|
|
850b6d2d46 | ||
|
|
4bacdc8824 | ||
|
|
f1df354222 | ||
|
|
fcc86f043b | ||
|
|
f350bb4dfc | ||
|
|
14506aeca4 | ||
|
|
f1805ab54d | ||
|
|
1cc2fc82ca | ||
|
|
047acaa176 | ||
|
|
6a4a60fe25 | ||
|
|
f14e91b39f | ||
|
|
1d98853813 | ||
|
|
2ad7bd0f55 | ||
|
|
7b414d8c0b | ||
|
|
7b1871b99b | ||
|
|
9f054ee05b | ||
|
|
fccb2b8ace | ||
|
|
c197b3fef4 | ||
|
|
85d86ebc4b | ||
|
|
62fa507189 | ||
|
|
97534372f8 | ||
|
|
dc6ecd571a | ||
|
|
aacae4ce62 | ||
|
|
fb2c405dbc | ||
|
|
8bf57e8bde | ||
|
|
0d305839e5 | ||
|
|
0532feb0d3 | ||
|
|
494cd78889 | ||
|
|
05ba1335d9 | ||
|
|
c92490881b | ||
|
|
b9d2e0f86d | ||
|
|
19e451dc75 | ||
|
|
5579fef673 | ||
|
|
ab3938df1e | ||
|
|
0b25a73288 | ||
|
|
4f0a978fc2 | ||
|
|
9e160d5c0f | ||
|
|
4f2d24f463 | ||
|
|
c18b6fc9da | ||
|
|
4c8299ca3d | ||
|
|
d083702a7b | ||
|
|
657dcb416b | ||
|
|
8d6ed34e4a | ||
|
|
4c1f187da0 | ||
|
|
4a846dd129 | ||
|
|
6ce17db11a | ||
|
|
f89740a62c | ||
|
|
0bed456999 | ||
|
|
e4adb0b0e3 | ||
|
|
0da5e0e34e | ||
|
|
f5be489266 | ||
|
|
663501206f | ||
|
|
048766fea5 | ||
|
|
9fd0f7cd34 | ||
|
|
7752e3b30f | ||
|
|
49db424c80 | ||
|
|
3e43306346 | ||
|
|
ca16413f3f | ||
|
|
5275d008ed | ||
|
|
6c15561120 | ||
|
|
8bb4dd7d08 | ||
|
|
6d409a6182 | ||
|
|
b485ee7e36 | ||
|
|
1a3bde17a6 | ||
|
|
905da8bd6b | ||
|
|
91dde183dc | ||
|
|
62aff9aa56 | ||
|
|
1303b03241 | ||
|
|
5986431b02 | ||
|
|
2641b052dc | ||
|
|
e1d7e2e8a2 | ||
|
|
6f5b7120b8 | ||
|
|
817f861167 | ||
|
|
201bf85ce9 | ||
|
|
92e864a521 | ||
|
|
15258921ee | ||
|
|
22bff819ab | ||
|
|
6e2cbe3faf | ||
|
|
e032d44179 | ||
|
|
d7d1270ced | ||
|
|
32434b5f81 | ||
|
|
8e20e6584d | ||
|
|
1ccc1bac6d | ||
|
|
075e835858 | ||
|
|
b06ff2abf2 | ||
|
|
6a21962552 | ||
|
|
b5f25de352 | ||
|
|
d1f7f69cd4 | ||
|
|
11e6575c69 | ||
|
|
0c26623a96 | ||
|
|
04d41aeae1 | ||
|
|
aae4b1b29d | ||
|
|
bed2472121 | ||
|
|
9efd2d10e7 | ||
|
|
9b42cd8728 | ||
|
|
47bb5ddece | ||
|
|
5bc9d9cc5c | ||
|
|
0eb6f5d8bc | ||
|
|
215d5fb320 | ||
|
|
dc0e966ed2 | ||
|
|
f2abe28d40 | ||
|
|
76d72d48f3 | ||
|
|
8edf705238 | ||
|
|
2549dfe59b | ||
|
|
5c85624eeb | ||
|
|
0b9a1e94b7 | ||
|
|
cbdd6a4cbb | ||
|
|
3f90d92667 | ||
|
|
8a2d7f2541 | ||
|
|
8150c363b5 | ||
|
|
a9bef83a0c | ||
|
|
bd0c9024a2 | ||
|
|
18269f0b88 | ||
|
|
fb1a5a2c26 | ||
|
|
8ef356d5c3 | ||
|
|
43734b1dbd | ||
|
|
b938e6398b | ||
|
|
8d747d20b8 | ||
|
|
525e66e513 | ||
|
|
c910ddac38 | ||
|
|
82b8a4aab6 | ||
|
|
ab03d4e037 | ||
|
|
b6a8759b29 | ||
|
|
f04185cc70 | ||
|
|
5ab26a8774 | ||
|
|
c8e5150fd4 | ||
|
|
a112903802 | ||
|
|
32e8bca02c | ||
|
|
60a1f01a3e | ||
|
|
442da01db4 | ||
|
|
969ca8511d | ||
|
|
66665eea6d | ||
|
|
6bb6cfc68e | ||
|
|
97e528ed54 | ||
|
|
9f2f89320e | ||
|
|
14ceec27fa | ||
|
|
f50202ee95 | ||
|
|
f3b56165f5 | ||
|
|
e8898bb6c1 | ||
|
|
3f274006cd | ||
|
|
f85c0b7dc5 | ||
|
|
7b1f7b179f | ||
|
|
4ea8063203 | ||
|
|
6c67339798 | ||
|
|
758e83015b | ||
|
|
d04f7e7ce7 | ||
|
|
8c05043eca | ||
|
|
660e4257a7 | ||
|
|
0647481c7c | ||
|
|
7e28caa637 | ||
|
|
44ca47b2eb | ||
|
|
bcd232467f | ||
|
|
3caf9faef5 | ||
|
|
71154bf3bf | ||
|
|
05835dd2d4 | ||
|
|
25428c4631 | ||
|
|
f2f27775fb | ||
|
|
54a2a20447 | ||
|
|
874306a2ac | ||
|
|
aecd709dfe | ||
|
|
a8b81fa8e5 | ||
|
|
039d1010fe | ||
|
|
46fdc7d610 | ||
|
|
dcb525de50 | ||
|
|
5fb302ebf1 | ||
|
|
982b1c9464 | ||
|
|
92191d37e6 | ||
|
|
503af7afa6 | ||
|
|
1a834a0ff6 | ||
|
|
883f66eef3 | ||
|
|
3b1ef4354f | ||
|
|
7b5527a74e | ||
|
|
67719b3c28 | ||
|
|
da3f47ddd0 | ||
|
|
a95b61560a | ||
|
|
897a7b794f | ||
|
|
f700ad32a8 | ||
|
|
74178b37be | ||
|
|
f2a46ec46f | ||
|
|
594337698f | ||
|
|
8e681123d8 | ||
|
|
28d6aa5514 | ||
|
|
77a6187a70 | ||
|
|
45ffb6cc25 | ||
|
|
a732b916f4 | ||
|
|
c8086b731a | ||
|
|
871aa9d0b9 | ||
|
|
73f36b0c80 | ||
|
|
59d18a13b7 | ||
|
|
caf4766493 | ||
|
|
b8c02c64fb | ||
|
|
7ca649413a | ||
|
|
3fd64772d6 | ||
|
|
2055e75f9f | ||
|
|
a06f4d0808 | ||
|
|
d0b69a2064 | ||
|
|
04cdc33731 | ||
|
|
a216b4ebc3 | ||
|
|
0094f76314 | ||
|
|
6464cf4756 | ||
|
|
4fb2e2309e | ||
|
|
8b7418b127 | ||
|
|
e2abd4bc62 | ||
|
|
1151d69bb8 | ||
|
|
68954f9c6c | ||
|
|
31d545260e | ||
|
|
f6c9912e37 | ||
|
|
7e8b58cb25 | ||
|
|
b07c40a5a8 | ||
|
|
11eae6b2d8 | ||
|
|
c1be9ac0a7 | ||
|
|
c561e4c11b | ||
|
|
f1a544ef6d | ||
|
|
2d010306e4 | ||
|
|
3eb48ec3e7 | ||
|
|
431e33b567 | ||
|
|
da5a6b68bd | ||
|
|
85450b3da9 | ||
|
|
3f8ac729f2 | ||
|
|
72571f0d38 | ||
|
|
e0c01bf956 | ||
|
|
9f9235b692 | ||
|
|
35cb59e3b5 | ||
|
|
d7c7905a52 | ||
|
|
eb94d3af94 | ||
|
|
614d0348a5 | ||
|
|
8f4920e2eb | ||
|
|
60fea81cf1 | ||
|
|
250c756fb4 | ||
|
|
ca2c9fef8c | ||
|
|
c55e1f7566 | ||
|
|
40eae3cbb7 | ||
|
|
412d6cf21b | ||
|
|
b7e5d9a96e | ||
|
|
27c52f8062 | ||
|
|
2003ab736a | ||
|
|
171077037a | ||
|
|
b33ce7a371 | ||
|
|
e0621bd7b9 | ||
|
|
9dcbf911a0 | ||
|
|
1f951f36fd | ||
|
|
3e6758f55a | ||
|
|
c63fb08f81 | ||
|
|
b248899878 | ||
|
|
88d97c55c7 | ||
|
|
9cba6672d6 | ||
|
|
19525e1dd0 | ||
|
|
cf4354ad83 | ||
|
|
99b933f160 | ||
|
|
6d3ce088da | ||
|
|
382201acf0 | ||
|
|
3349cc5ea0 | ||
|
|
df3374d11d | ||
|
|
f197ca503a | ||
|
|
4b4631cd48 | ||
|
|
de404de321 | ||
|
|
8d1e734213 | ||
|
|
8f4ec8e6ce | ||
|
|
45f1d9cb0f | ||
|
|
98ba5fd952 | ||
|
|
d16634be57 | ||
|
|
0324114293 | ||
|
|
e96a4e8fc3 | ||
|
|
1680c86b6c | ||
|
|
692733ead4 | ||
|
|
7abf2e0574 | ||
|
|
8f6cf2afdd | ||
|
|
75c8c4c08c | ||
|
|
8134fe737c | ||
|
|
29a5ab9632 | ||
|
|
66fb12d18a | ||
|
|
972d01965c | ||
|
|
cb869c823e | ||
|
|
22f2de0c4f | ||
|
|
3a7a1f156d | ||
|
|
30eb467ec8 | ||
|
|
eb5f33a5c6 | ||
|
|
5272a94a19 | ||
|
|
51da1f70fa | ||
|
|
49b2ec1e2e | ||
|
|
5927eb73ec | ||
|
|
2f4cf2d67d | ||
|
|
88cd163a8d | ||
|
|
2c532eafa7 | ||
|
|
d8745d928d | ||
|
|
ba331014be | ||
|
|
9b8e549263 | ||
|
|
43d5255998 | ||
|
|
1f816b1561 | ||
|
|
134a56f3e4 | ||
|
|
56529d7850 | ||
|
|
1bd1cac23f | ||
|
|
40db9734c4 | ||
|
|
2b6acf9c92 | ||
|
|
e4a21b35f5 | ||
|
|
fa7da15be1 | ||
|
|
3a8aed4c77 | ||
|
|
d7ff1ceb29 | ||
|
|
47163f6bb7 | ||
|
|
dd9792662f | ||
|
|
f3e6eeb643 | ||
|
|
5a289f5cad | ||
|
|
027f4b4eda | ||
|
|
f5a0222af2 | ||
|
|
03b10e97d3 | ||
|
|
1a4917c3d3 | ||
|
|
5fa11582ae | ||
|
|
2247c8ea91 | ||
|
|
d4f602bdff | ||
|
|
226f0427bc | ||
|
|
17b46d5d56 | ||
|
|
c12500cf50 | ||
|
|
512dc4f2b1 | ||
|
|
d8b3de39b0 | ||
|
|
58c92e81b1 | ||
|
|
eb6a0f3529 | ||
|
|
01074e376c | ||
|
|
c28a3d9768 | ||
|
|
dc4e90bbd2 | ||
|
|
90bc577a12 | ||
|
|
fffb7d3d7a | ||
|
|
3df9a60b0b | ||
|
|
fbba29319f | ||
|
|
0882b85d5a | ||
|
|
26fdff9e03 | ||
|
|
6fbfb8b7a3 | ||
|
|
958ca2ebec | ||
|
|
9c9ca5f431 | ||
|
|
0f1ce47033 | ||
|
|
f587887122 | ||
|
|
f5305afcfb | ||
|
|
d8cf947f6b | ||
|
|
7896a44365 | ||
|
|
aa0957c4dd | ||
|
|
553cc80027 | ||
|
|
3d19f018ab | ||
|
|
2d55e0a00b | ||
|
|
8aaea14209 | ||
|
|
5945d4145a | ||
|
|
1bd92975c2 | ||
|
|
8595e6c872 | ||
|
|
a6aa028626 | ||
|
|
e3edd408aa | ||
|
|
84d8cb0826 | ||
|
|
44082acef5 | ||
|
|
d033662145 | ||
|
|
8a09b40cb2 | ||
|
|
28f7745a5e | ||
|
|
978e379079 | ||
|
|
0b948b51ae | ||
|
|
0355bc2b0d | ||
|
|
5f94c2592d | ||
|
|
4bbd1dc0d5 | ||
|
|
6e58da9750 | ||
|
|
18021818ce | ||
|
|
975b989de6 | ||
|
|
911cfe2adc | ||
|
|
9fa204003f | ||
|
|
497a126645 | ||
|
|
25e3a6078d | ||
|
|
577dc17d0c | ||
|
|
135578b4e9 | ||
|
|
ebb53d8dab | ||
|
|
c9d3c3022f | ||
|
|
c14594cd93 | ||
|
|
fb74a7f0a4 | ||
|
|
50b9526951 | ||
|
|
fe30b31a97 | ||
|
|
9641f9ebbb | ||
|
|
83bb7e8aab | ||
|
|
33254ca696 | ||
|
|
704feda9da | ||
|
|
24d50acc70 | ||
|
|
29a1c4f46c | ||
|
|
aa52d1be42 | ||
|
|
bccd429f70 | ||
|
|
41cce9ea79 | ||
|
|
59657913fd | ||
|
|
88de927a0c | ||
|
|
a2f158e5ed | ||
|
|
8e519aa826 | ||
|
|
43fa394b83 | ||
|
|
f48d040bf5 | ||
|
|
1603577dfd | ||
|
|
a74ba90196 | ||
|
|
5a12f30441 | ||
|
|
82e6501f89 | ||
|
|
cf7b906216 | ||
|
|
ee54a8d298 | ||
|
|
018494fa3e | ||
|
|
e1818116bc | ||
|
|
87eda35bcb | ||
|
|
c99a13f72c | ||
|
|
c4f628085d | ||
|
|
905d2d8062 | ||
|
|
b3a0da7c5e | ||
|
|
0a761a9eac | ||
|
|
456489974d | ||
|
|
020a49de41 | ||
|
|
dd409eec80 | ||
|
|
f48f0957f5 | ||
|
|
dab1be48fc | ||
|
|
bdc5a96db6 | ||
|
|
1f4bb2df82 | ||
|
|
a292cbf46f | ||
|
|
434e3d81f3 | ||
|
|
263a190fc9 | ||
|
|
40c9d0affc | ||
|
|
a3827a93a9 | ||
|
|
4be6ff9d5f | ||
|
|
33e63d914b | ||
|
|
16985aba4e | ||
|
|
bcf17447f0 | ||
|
|
901f2f38fc | ||
|
|
f8bb35ead0 | ||
|
|
5934a8eacc | ||
|
|
7aebac697e | ||
|
|
9a71595d97 | ||
|
|
ed526a2121 | ||
|
|
85c1ff6ea4 | ||
|
|
02a6e78531 | ||
|
|
305d04b758 | ||
|
|
8ea7866356 | ||
|
|
ed1716cd9d | ||
|
|
34f60de970 | ||
|
|
785ecf7715 | ||
|
|
d9311a7935 | ||
|
|
43a34e23b3 | ||
|
|
26c213031d | ||
|
|
f43e006529 | ||
|
|
81722f0b26 | ||
|
|
fafdd23568 | ||
|
|
0c75b9ce00 | ||
|
|
537f4689f9 | ||
|
|
aa36c077fc | ||
|
|
9430113fe5 | ||
|
|
4a7e3d9058 | ||
|
|
456bc8df65 | ||
|
|
0fb9a3beac | ||
|
|
29a48ab129 | ||
|
|
cde7ae8809 | ||
|
|
800572e9c6 | ||
|
|
af134f1dd9 | ||
|
|
90bbd6b453 | ||
|
|
dbfc3d7104 | ||
|
|
d206bf6362 | ||
|
|
1a006aa49e | ||
|
|
8b05743df2 | ||
|
|
2f06696579 | ||
|
|
99a896797f | ||
|
|
1471f25b45 | ||
|
|
1fc7dd2fc1 | ||
|
|
0d708eaacf | ||
|
|
803b6488d5 | ||
|
|
231ed8570c | ||
|
|
6bc9b34824 | ||
|
|
370dfc9279 | ||
|
|
27b37f18ba | ||
|
|
ddc355b04a | ||
|
|
e8a8c264d2 | ||
|
|
4ea6e426cd | ||
|
|
e5f2b25f25 | ||
|
|
71b08988fb | ||
|
|
4edc64037c | ||
|
|
84065b9a68 | ||
|
|
b97f993b0c | ||
|
|
f76b426e2b | ||
|
|
254417a344 | ||
|
|
17bac9e22d | ||
|
|
5c7667c15c | ||
|
|
e753fc9cc7 | ||
|
|
642a3567b1 | ||
|
|
a7978a271d | ||
|
|
e89b41fce7 | ||
|
|
ba40142f71 | ||
|
|
b10c434788 | ||
|
|
9d168dd2f3 | ||
|
|
96f7e322ba | ||
|
|
ecfb3abbed | ||
|
|
ca2d89bc4d | ||
|
|
1f139c198a | ||
|
|
164f0feddf | ||
|
|
78cf0e95ad | ||
|
|
873ef6cf45 | ||
|
|
e7befec3ff | ||
|
|
47d42606ac | ||
|
|
4e059035a9 | ||
|
|
23221a3b12 | ||
|
|
87083edf0a | ||
|
|
f8fdd854ed | ||
|
|
97e79bb5f6 | ||
|
|
14eb1923b4 | ||
|
|
f304af6b74 | ||
|
|
a73bbe4bdd | ||
|
|
db2678528d | ||
|
|
b591b3e79a | ||
|
|
e93860f5f2 | ||
|
|
bf18161ea8 | ||
|
|
b225d31179 | ||
|
|
b3963e847e | ||
|
|
cb2fc70741 | ||
|
|
3ed4b69b38 | ||
|
|
acb4c5c2f3 | ||
|
|
1f9bc4d057 | ||
|
|
9b5324ff7e | ||
|
|
c0490aa418 | ||
|
|
6a4d633e42 | ||
|
|
9e125184ed | ||
|
|
6ed67fc873 | ||
|
|
3ae3a5e77d | ||
|
|
68fbe9fab1 | ||
|
|
78ae7bbd90 | ||
|
|
bc98fd96f1 | ||
|
|
f42fc9e6c2 | ||
|
|
f0ef3070fa | ||
|
|
f3bc22d577 | ||
|
|
d2e2d971b6 | ||
|
|
eb4a9f2a2a | ||
|
|
1f24ecbf24 | ||
|
|
f11a8ea1ee | ||
|
|
96a6e1bf55 | ||
|
|
0603ceba23 | ||
|
|
68b7666d7c | ||
|
|
9fe066b37a | ||
|
|
d3c9b9d30f | ||
|
|
78f9f3093e | ||
|
|
c597db3fb8 | ||
|
|
d76cc4eb93 | ||
|
|
76052a4e01 | ||
|
|
21fbe416d4 | ||
|
|
0010b246c0 | ||
|
|
74967abd51 | ||
|
|
c292d58d91 | ||
|
|
2b65a5f0ac | ||
|
|
c4358fb567 | ||
|
|
705cc97331 | ||
|
|
e75ae8b3db | ||
|
|
938a78f9bf | ||
|
|
d02b10c3fb | ||
|
|
9b21540a4e | ||
|
|
98a5f737d7 | ||
|
|
df05668f8b | ||
|
|
c705720d87 | ||
|
|
c067c16360 | ||
|
|
3381e4c375 | ||
|
|
decdb92f34 | ||
|
|
1148f245c8 | ||
|
|
6abbe837b5 | ||
|
|
38cfdad16b | ||
|
|
360953cb49 | ||
|
|
24644e3c27 | ||
|
|
f930b23dad | ||
|
|
09171eee8d | ||
|
|
0b239d163a | ||
|
|
7d6b15eb67 | ||
|
|
4dcadecab0 | ||
|
|
e50b8e3e99 | ||
|
|
53df18943f | ||
|
|
372ca5e81e | ||
|
|
b4e3bbc57b | ||
|
|
c8a39b657e | ||
|
|
788b47536c | ||
|
|
5bac634abf | ||
|
|
d2a271d5c8 | ||
|
|
a72f102259 | ||
|
|
629b5b034a | ||
|
|
b5a16e263d | ||
|
|
fc56cd135f | ||
|
|
61fa215acd | ||
|
|
8d4e3f5c3c | ||
|
|
f6f7d2f85e | ||
|
|
eddfffebe8 | ||
|
|
f163432674 | ||
|
|
3a99b8b9e1 | ||
|
|
f73d8e8d9e | ||
|
|
0a9edac632 | ||
|
|
44030ac4fd | ||
|
|
795a8042a1 | ||
|
|
3ecb713b00 | ||
|
|
4e907f78ca | ||
|
|
beff874340 | ||
|
|
f6360da116 | ||
|
|
8642137252 | ||
|
|
8a660099f2 | ||
|
|
3664119029 | ||
|
|
099d4b50b6 | ||
|
|
53176153a2 | ||
|
|
2b64f4bf4b | ||
|
|
f27c164e7f | ||
|
|
85c1c59c5f | ||
|
|
17c77f1307 | ||
|
|
4da0a99a9e | ||
|
|
9d17871ff0 | ||
|
|
4f37a5d590 | ||
|
|
8a4332864b | ||
|
|
f006678f3c | ||
|
|
655e0be3d7 | ||
|
|
e8ad3573c0 | ||
|
|
e3dd80f9d4 | ||
|
|
eaea16f166 | ||
|
|
b722273acb | ||
|
|
80ab02d8be | ||
|
|
8645e8655e | ||
|
|
a9dcd52a7e | ||
|
|
60ec7ca0f1 | ||
|
|
8dc756747b | ||
|
|
1ea02d231d | ||
|
|
73f4bfadc1 | ||
|
|
a290e91b12 | ||
|
|
0c444ff5ba | ||
|
|
510fe8b95d | ||
|
|
b7703616f0 | ||
|
|
b79df1796c | ||
|
|
100e587243 | ||
|
|
283b72f2de | ||
|
|
76c4714ce7 | ||
|
|
60818959b0 | ||
|
|
ebcd475d24 | ||
|
|
848348f423 | ||
|
|
99123dc5fd | ||
|
|
46ae3d314a | ||
|
|
a1bd02fdfd | ||
|
|
c6a0452d13 | ||
|
|
ef9b1a0001 | ||
|
|
26a0172568 | ||
|
|
ff414f5870 | ||
|
|
91f1f881bb | ||
|
|
b4a3c00efb | ||
|
|
9607776ed7 | ||
|
|
0f1a938a3e | ||
|
|
abe2296daf | ||
|
|
b38988ca96 | ||
|
|
91d31197be | ||
|
|
039d22cda8 | ||
|
|
d2b67fbb68 | ||
|
|
1e4f3f2123 | ||
|
|
869950564f | ||
|
|
ffb1628727 | ||
|
|
dafc31502a | ||
|
|
897c50e1a4 | ||
|
|
8116e638f3 | ||
|
|
976306641d | ||
|
|
9429b0976a | ||
|
|
20c88ef5db | ||
|
|
6c711a64cb | ||
|
|
6686533d19 | ||
|
|
d0c756e8ab | ||
|
|
69c78fbef0 | ||
|
|
2c814d33e6 | ||
|
|
b4f12bb4c3 | ||
|
|
c700bfc35d | ||
|
|
50458789ad | ||
|
|
ea37a833dc | ||
|
|
94e2bf258d | ||
|
|
042c117342 | ||
|
|
92a4d72709 | ||
|
|
648f60c188 | ||
|
|
a48b655006 | ||
|
|
b9d108453f | ||
|
|
1df6d0467c | ||
|
|
60827fa096 | ||
|
|
cdce715ba4 | ||
|
|
7b5f09ab9d | ||
|
|
f88ffa7f79 | ||
|
|
3fb2c9a916 | ||
|
|
1f25db1514 | ||
|
|
aff96ea963 | ||
|
|
24c4e458f9 | ||
|
|
444ece721c | ||
|
|
221e550eb9 | ||
|
|
c9be0ece71 | ||
|
|
84f535c315 | ||
|
|
66c1190bcc | ||
|
|
f070a92e19 | ||
|
|
d63671fce0 | ||
|
|
41a01cdae5 | ||
|
|
67d2026e22 | ||
|
|
9de39accdb | ||
|
|
4e01916a7e | ||
|
|
a89c1baddc | ||
|
|
66e1c3982d | ||
|
|
98a0b22e8e | ||
|
|
4bc5eab390 | ||
|
|
2ad17098fe | ||
|
|
54d7728e74 | ||
|
|
4d27f3b04c | ||
|
|
4800d4e1d7 | ||
|
|
2c53354901 | ||
|
|
212f6ddf8f | ||
|
|
a80874a4c1 | ||
|
|
485c258aaf | ||
|
|
a4130ae8ed | ||
|
|
b36d688f78 | ||
|
|
a1b4ef9b2f | ||
|
|
f19e3ab298 | ||
|
|
f96eca4ab2 | ||
|
|
c68a582e6e | ||
|
|
451b37ece1 | ||
|
|
b414c8b863 | ||
|
|
be47599cad | ||
|
|
66ae458cce | ||
|
|
0ce5e358d4 | ||
|
|
3c354c0907 | ||
|
|
df3aa90a20 | ||
|
|
1d5b58ac18 | ||
|
|
9fc0d2a6bf | ||
|
|
77b424b15e | ||
|
|
97492cf602 | ||
|
|
fd90c30c23 | ||
|
|
4e6dfc015e | ||
|
|
4ebeb18fde | ||
|
|
3730d6d17a | ||
|
|
52cca21ea8 | ||
|
|
f657a25422 | ||
|
|
b64e1d8b91 | ||
|
|
03de50e70b | ||
|
|
7d9a9d83ff | ||
|
|
d5b326523f | ||
|
|
6159b17cdf | ||
|
|
f06493f0ea | ||
|
|
d41c9860d7 | ||
|
|
4277078bc5 | ||
|
|
f9a1875127 | ||
|
|
f309656325 | ||
|
|
f039d80306 | ||
|
|
4d4f3eb404 | ||
|
|
733c0c2fda | ||
|
|
efc19f0ddb | ||
|
|
8fbf0972e7 | ||
|
|
f38a498985 | ||
|
|
55f094ea33 | ||
|
|
d64948f5c2 | ||
|
|
517801282a | ||
|
|
c206702add | ||
|
|
9b38606d5c | ||
|
|
c0563aa532 | ||
|
|
aa76cf43f0 | ||
|
|
1d4e4314dd | ||
|
|
64089fd15e | ||
|
|
99fb9ab444 | ||
|
|
34334f0e68 | ||
|
|
21cfc21e4f | ||
|
|
f44a7423c2 | ||
|
|
055c17b088 | ||
|
|
84cd786911 | ||
|
|
bd3ad3436e | ||
|
|
c67a9c5259 | ||
|
|
28ee477930 | ||
|
|
4ca5f51430 | ||
|
|
8cb73844c8 | ||
|
|
d83215084f | ||
|
|
4cfc8cd5be | ||
|
|
25e51bba52 | ||
|
|
53495f5136 | ||
|
|
0787266637 | ||
|
|
2ecea9395b | ||
|
|
ec193a2b82 | ||
|
|
8c4ecf42df | ||
|
|
6682b12563 | ||
|
|
beb2fded6d | ||
|
|
a1f277e30e | ||
|
|
6d427f8c2a | ||
|
|
39cb6ecbb9 | ||
|
|
dc3df91e95 | ||
|
|
bb6ba38a10 | ||
|
|
9a93ea9d7a | ||
|
|
3e081c5d21 | ||
|
|
c72f539ced | ||
|
|
83801c49f7 | ||
|
|
812f96cf24 | ||
|
|
2f84c47b8b | ||
|
|
d385b96451 | ||
|
|
d4e1a790ab | ||
|
|
34abb441f6 | ||
|
|
984ecd98ca | ||
|
|
528f296cfc | ||
|
|
45381135df | ||
|
|
2a14f76964 | ||
|
|
ca3e5ffd89 | ||
|
|
9686e518bc | ||
|
|
5ca33f7cb4 | ||
|
|
dfe2e81829 | ||
|
|
3eed321081 | ||
|
|
2a35ea4f07 | ||
|
|
efda761724 | ||
|
|
c63d6bf508 | ||
|
|
bcbb3de760 | ||
|
|
590474a9a4 | ||
|
|
10e14bd5be | ||
|
|
bfea6bebc9 | ||
|
|
ab4eb5aa94 | ||
|
|
f5c49758fc | ||
|
|
394c7a2357 | ||
|
|
91ad6c2739 | ||
|
|
04697eca88 | ||
|
|
1908967cfa | ||
|
|
f54cf74ef6 | ||
|
|
44166f7cfe | ||
|
|
6a87d6e814 | ||
|
|
0f871664c5 | ||
|
|
0a5515297e | ||
|
|
97a3089cec | ||
|
|
555f74cf67 | ||
|
|
9e93aa0c32 | ||
|
|
bf5b6cba70 | ||
|
|
24b915ed41 | ||
|
|
8233ca6401 | ||
|
|
bf2fbf071b | ||
|
|
199f4d78d9 | ||
|
|
4da808da50 | ||
|
|
67bd9edd8b | ||
|
|
6de5f92835 | ||
|
|
83a0f1fd52 | ||
|
|
314654bd0f | ||
|
|
22d99ee9df | ||
|
|
8f92c0607c | ||
|
|
74f0dc87de | ||
|
|
43f6ffd0ae | ||
|
|
c560793482 | ||
|
|
1212412ff1 | ||
|
|
a56aa6ccbe | ||
|
|
59032f63b1 | ||
|
|
72f4b4186b | ||
|
|
aa8331c836 | ||
|
|
4862d34925 | ||
|
|
e39af9545f | ||
|
|
e28984c74a | ||
|
|
3d3d585165 | ||
|
|
5200ffb90c | ||
|
|
0969336ef6 | ||
|
|
2d6f44b6ce | ||
|
|
ff5904f5f4 | ||
|
|
faae8e08b3 | ||
|
|
f8f98c116e | ||
|
|
b7d362ddbb | ||
|
|
a4ac25972b | ||
|
|
b73103ab85 | ||
|
|
14435c8bdf | ||
|
|
9380128193 | ||
|
|
5bbfa40255 | ||
|
|
496ccc3f73 | ||
|
|
383fa94c92 | ||
|
|
d1485ada9c | ||
|
|
1917c09d1c | ||
|
|
6798cbbd52 | ||
|
|
10d7c4d50e | ||
|
|
37bed56c1d | ||
|
|
caf8d75dfb | ||
|
|
ac8f0c9c0d | ||
|
|
9a1761d80c | ||
|
|
89a5eadd4e | ||
|
|
77876bd05c | ||
|
|
805481c176 | ||
|
|
9d8e923ddb | ||
|
|
af711f9e9f | ||
|
|
346aa0ed47 | ||
|
|
6f076dcde7 | ||
|
|
1f71137d1e | ||
|
|
046d983d26 | ||
|
|
550b946696 | ||
|
|
13e707fb7f | ||
|
|
1bef457cb6 | ||
|
|
f40bd56793 | ||
|
|
473225c471 | ||
|
|
f62766b996 | ||
|
|
22a9dade9c | ||
|
|
3fb87b127c | ||
|
|
e1fe71872c | ||
|
|
c96a0b1112 | ||
|
|
0dda02515f | ||
|
|
a86c43e1fd | ||
|
|
60d83b1d32 | ||
|
|
7ac3c2ca88 | ||
|
|
1ce9c355ab | ||
|
|
7b7d69a31e | ||
|
|
ac0515ce7e | ||
|
|
6e18f0e59e | ||
|
|
ba58bc3787 | ||
|
|
58a3527e17 | ||
|
|
2172bf1cdd | ||
|
|
a5ea6d3cf4 | ||
|
|
f1d04006e0 | ||
|
|
861e23b02c | ||
|
|
6ffcf4523d | ||
|
|
cf3c1994dc | ||
|
|
e88a9e5ee4 | ||
|
|
158ebbb2ed | ||
|
|
aaad2468c8 | ||
|
|
1652707c6e | ||
|
|
f8f9f13e0d | ||
|
|
91bb931b0f | ||
|
|
e7343dbfa8 | ||
|
|
b7446a0c65 | ||
|
|
2070142c49 | ||
|
|
570fb5594c | ||
|
|
17fcbcefbc | ||
|
|
57326feb8d | ||
|
|
58da2f5897 | ||
|
|
0e9d63a417 | ||
|
|
cbe124689d | ||
|
|
a0919685be | ||
|
|
ecfd6cfa73 | ||
|
|
fc0c707b98 | ||
|
|
15a7869bbc | ||
|
|
b97d50f2fb | ||
|
|
5aaec6a389 | ||
|
|
cef82adf19 | ||
|
|
cea60d603e | ||
|
|
3455c857a0 | ||
|
|
0001551143 | ||
|
|
3ea27c63e2 | ||
|
|
d9b05e601e | ||
|
|
4a5a43fb98 | ||
|
|
212c4af50d | ||
|
|
de9f726add | ||
|
|
daabd058fc | ||
|
|
7bc3019691 | ||
|
|
73728127b6 | ||
|
|
6fb74d4985 | ||
|
|
9a94194329 | ||
|
|
4e2541e5fb | ||
|
|
f76883d46c | ||
|
|
1fd049e307 | ||
|
|
e90c89cf8b | ||
|
|
a4a34edd21 | ||
|
|
f48c91ac2f | ||
|
|
8bfa06e992 | ||
|
|
e89e214516 | ||
|
|
310d2db312 | ||
|
|
3b2db583cd | ||
|
|
7481478303 | ||
|
|
f0f4fa6978 | ||
|
|
da22866030 | ||
|
|
808be2cae7 | ||
|
|
7b2a723891 | ||
|
|
40d2e5aa45 | ||
|
|
d2c1b743c0 | ||
|
|
966a3ea27c | ||
|
|
b4543caf55 | ||
|
|
e069169765 | ||
|
|
127bafa0b9 | ||
|
|
23ff2a9cf7 | ||
|
|
db0d212835 | ||
|
|
f00ef03d91 | ||
|
|
607c855621 | ||
|
|
2bca977ced | ||
|
|
688adf732d | ||
|
|
1af8bd90c3 | ||
|
|
26f1f28ffe | ||
|
|
f60c3bf6e0 | ||
|
|
5530cec127 | ||
|
|
46d6f500f3 | ||
|
|
c6784493fc | ||
|
|
4db3c5145f | ||
|
|
cc8f4e98a6 | ||
|
|
d5f8f62ab2 | ||
|
|
eed0a93c59 | ||
|
|
57b55883c5 | ||
|
|
85826c83e4 | ||
|
|
3a20606c04 | ||
|
|
2dabf1932f | ||
|
|
2e1ddedc58 | ||
|
|
dc30298b29 | ||
|
|
8879ed153d | ||
|
|
5d6ee4f73e | ||
|
|
e8b401d0c8 | ||
|
|
2fc429dfbf | ||
|
|
f1cc8f0cfc | ||
|
|
b2ca265f11 | ||
|
|
4a4f52b097 | ||
|
|
a018257487 | ||
|
|
f6921fd733 | ||
|
|
20debfab90 | ||
|
|
78288e37ed | ||
|
|
859eb06662 | ||
|
|
f98e98ab66 | ||
|
|
d97d5c04f0 | ||
|
|
6f450c2d1f | ||
|
|
5f2e77a6e1 | ||
|
|
554507b413 | ||
|
|
de2a9459e5 | ||
|
|
ea1e933b29 | ||
|
|
848f154f3e | ||
|
|
f298f86a7f | ||
|
|
8f648078bd | ||
|
|
ed463f6de0 | ||
|
|
3a3ab31d2b | ||
|
|
1d7d268a63 | ||
|
|
1687c672a7 | ||
|
|
045010bb78 | ||
|
|
6794ff411a | ||
|
|
35e31ed351 | ||
|
|
2d59395883 | ||
|
|
67ebc433f9 | ||
|
|
93a6c93865 | ||
|
|
b3a97df754 | ||
|
|
8ba5865383 | ||
|
|
60baaf6e04 | ||
|
|
b928f360a1 | ||
|
|
a2b093cf6a | ||
|
|
0195da6b0e | ||
|
|
6d40de45c7 | ||
|
|
98316cfbbd | ||
|
|
3cb142ff2e | ||
|
|
501a68a69b | ||
|
|
b5038fd9a1 | ||
|
|
cfd796a515 | ||
|
|
7d728afa12 | ||
|
|
712644f0d9 | ||
|
|
511a6c0ad0 | ||
|
|
155162a8cd | ||
|
|
4fa961d4f1 | ||
|
|
0c245c35c5 | ||
|
|
cd783b9946 | ||
|
|
afebeb5e9a | ||
|
|
866d1eef0a | ||
|
|
ab1e091e39 | ||
|
|
d1fb2d25ea | ||
|
|
2b7b5774b6 | ||
|
|
73e497f9be | ||
|
|
85912849cc | ||
|
|
a5d6330f87 | ||
|
|
58759bb565 | ||
|
|
f168a62068 | ||
|
|
796f272f7d | ||
|
|
ebfab7bf84 | ||
|
|
90b8f3fba2 | ||
|
|
d8b18f1d96 | ||
|
|
a07b9fc840 | ||
|
|
9e27d04dc3 | ||
|
|
fe0055a1d1 | ||
|
|
6ccac3d208 | ||
|
|
5e7b5cf285 | ||
|
|
ec86d0f64a | ||
|
|
5dbfaa15fa | ||
|
|
d3eeadba94 | ||
|
|
858a3f72fa | ||
|
|
f6d336935d | ||
|
|
1cc9bc58a2 | ||
|
|
1f1ff0567a | ||
|
|
cc919db83b | ||
|
|
84aed919a9 | ||
|
|
162bf51adb | ||
|
|
28fe0296c4 | ||
|
|
00e613f12d | ||
|
|
7474b52584 | ||
|
|
438799e929 | ||
|
|
28be124cc1 | ||
|
|
a7e029fde9 | ||
|
|
c39314c14a | ||
|
|
3f3bc97cd3 | ||
|
|
235cdb3f81 | ||
|
|
6b525023d4 | ||
|
|
5cc4426f88 | ||
|
|
089e038dfe | ||
|
|
4a870300dd | ||
|
|
90c1ab2cef | ||
|
|
16bd427cb6 | ||
|
|
e45a50c828 | ||
|
|
4180e7cd59 | ||
|
|
6d776593ea | ||
|
|
df525b90f2 | ||
|
|
630f2bcabe | ||
|
|
106b770c40 | ||
|
|
960bc52e3c | ||
|
|
1a7d89e85b | ||
|
|
3d994aa03b | ||
|
|
72979129fb | ||
|
|
e11039087c | ||
|
|
cd2ef0f3a3 | ||
|
|
07785c6dbc | ||
|
|
753183e081 | ||
|
|
c95d6049c2 | ||
|
|
76891c9cf8 | ||
|
|
8aadca4c3e | ||
|
|
aad9a833c0 | ||
|
|
6368559c02 | ||
|
|
31e5cd6376 | ||
|
|
e7d33b4870 | ||
|
|
f38727acd9 | ||
|
|
8c5a4eb866 | ||
|
|
ca1aa08709 | ||
|
|
54f121f843 | ||
|
|
fa2f53993a | ||
|
|
53239102f8 | ||
|
|
6f9cebf1ca | ||
|
|
791dbf4f9d | ||
|
|
cdaa70facb | ||
|
|
d13869aab9 | ||
|
|
464cbbc9f9 | ||
|
|
aa73df571d | ||
|
|
4852935e8e | ||
|
|
c035c5c0d2 | ||
|
|
68502c90d1 | ||
|
|
66385670e4 | ||
|
|
3f2e73b723 | ||
|
|
cf88e4876d |
@@ -1 +0,0 @@
|
||||
Maintainer skills now live in [`openclaw/maintainers`](https://github.com/openclaw/maintainers/).
|
||||
@@ -14,6 +14,36 @@ Use this skill for release and publish-time workflow. Keep ordinary development
|
||||
- This skill should be sufficient to drive the normal release flow end-to-end.
|
||||
- Use the private maintainer release docs for credentials, recovery steps, and mac signing/notary specifics, and use `docs/reference/RELEASING.md` for public policy.
|
||||
- Core `openclaw` publish is manual `workflow_dispatch`; creating or pushing a tag does not publish by itself.
|
||||
- Normal release work happens on a branch cut from `main`, not directly on
|
||||
`main`. Use `release/YYYY.M.D` for the branch name.
|
||||
- If the operator asks for a release without saying stable/full, default to
|
||||
beta only. Continue from beta to stable only when the operator explicitly asks
|
||||
for the full release or an automated beta-and-stable train.
|
||||
- Before release branching, pull latest `main` and confirm current `main` CI is
|
||||
green. Then branch from that commit so regular development can continue on
|
||||
`main` while release validation runs.
|
||||
- Before release branching, commit any dirty files in coherent groups, push,
|
||||
pull/rebase, then run `/changelog` on `main` and commit/push/pull that
|
||||
changelog rewrite immediately before creating the release branch.
|
||||
- Do not delete or rewrite beta tags after they leave the machine. If a
|
||||
published or pushed beta needs a fix, commit the fix on the release branch and
|
||||
increment to the next `-beta.N`.
|
||||
- For a beta release train, run the full pre-npm test roster before publishing
|
||||
each beta. After a beta is published, run the smaller published-install roster
|
||||
focused on install/update/Docker/Parallels. If anything fails, fix it on the
|
||||
release branch, commit/push/pull, increment beta number, and repeat. Operators
|
||||
may authorize up to 4 autonomous beta attempts; after 4 failed beta attempts,
|
||||
stop and report.
|
||||
- Use `/changelog` before version/tag preparation so the top changelog section
|
||||
is deduped and ordered by user impact.
|
||||
- Do not create beta-specific `CHANGELOG.md` headings. Beta releases use the
|
||||
stable base version section, for example `v2026.4.20-beta.1` uses
|
||||
`## 2026.4.20` release notes.
|
||||
- When any beta or stable release is live, make a best-effort Discord
|
||||
announcement using Peter's bot token from `.profile`; do not block or roll
|
||||
back the release if the announcement fails.
|
||||
- When asked to announce on X, use `~/Projects/bird/bird` and follow the
|
||||
release tweet style below.
|
||||
|
||||
## Keep release channel naming aligned
|
||||
|
||||
@@ -37,7 +67,9 @@ Use this skill for release and publish-time workflow. Keep ordinary development
|
||||
- For fallback correction tags like `vYYYY.M.D-N`, the repo version locations still stay at `YYYY.M.D`.
|
||||
- “Bump version everywhere” means all version locations above except `appcast.xml`.
|
||||
- Release signing and notary credentials live outside the repo in the private maintainer docs.
|
||||
- Every OpenClaw release ships the npm package and macOS app together.
|
||||
- Every stable OpenClaw release ships the npm package and macOS app together.
|
||||
Beta releases normally ship npm/package artifacts first and skip mac app
|
||||
build/sign/notarize unless the operator requests mac beta validation.
|
||||
- The production Sparkle feed lives at `https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml`, and the canonical published file is `appcast.xml` on `main` in the `openclaw` repo.
|
||||
- That shared production Sparkle feed is stable-only. Beta mac releases may
|
||||
upload assets to the GitHub prerelease, but they must not replace the shared
|
||||
@@ -53,17 +85,77 @@ Use this skill for release and publish-time workflow. Keep ordinary development
|
||||
- When cutting a mac release with a beta GitHub prerelease:
|
||||
- tag `vYYYY.M.D-beta.N` from the release commit
|
||||
- create a prerelease titled `openclaw YYYY.M.D-beta.N`
|
||||
- use release notes from the matching `CHANGELOG.md` version section
|
||||
- use release notes from the stable base `CHANGELOG.md` version section
|
||||
(`## YYYY.M.D`), not a beta-specific heading
|
||||
- attach at least the zip and dSYM zip, plus dmg if available
|
||||
- Keep the top version entries in `CHANGELOG.md` sorted by impact:
|
||||
- `### Changes` first
|
||||
- `### Fixes` deduped with user-facing fixes first
|
||||
|
||||
## Write release tweets
|
||||
|
||||
Use the OpenClaw account's existing release-post style:
|
||||
|
||||
- Format: `OpenClaw YYYY.M.D 🦞` or `🦞 OpenClaw YYYY.M.D is live`, blank line,
|
||||
then 3-4 emoji-led bullets, blank line, one short punchline, then the release
|
||||
link.
|
||||
- For beta: say `OpenClaw YYYY.M.D-beta.N 🦞` or `OpenClaw YYYY.M.D beta N is
|
||||
live`; keep it clearly beta and avoid implying stable promotion.
|
||||
- Lead with user-visible capabilities, then important integrations, then
|
||||
reliability/security/install fixes. Compress "lots of fixes" into one
|
||||
readable bullet.
|
||||
- Tone: high-signal, slightly cheeky, confident, not corporate. One joke is
|
||||
enough. Avoid punching down, insulting users, or promising what was not
|
||||
verified.
|
||||
- Length: release tweets are always standard tweets under 280 characters. Trim
|
||||
to 3-4 bullets and count the final text before posting.
|
||||
- Links/media: include the GitHub release or changelog link at the end. Add a
|
||||
short docs follow-up reply only when there is a standout feature that needs
|
||||
setup instructions.
|
||||
- Hotfix/correction: be direct and accountable. State what slipped, what is
|
||||
fixed, and the new version. Keep jokes out of incident-style posts.
|
||||
|
||||
Examples to adapt:
|
||||
|
||||
```text
|
||||
OpenClaw 2026.4.20-beta.1 🦞
|
||||
|
||||
🐳 Docker install/update smoke
|
||||
🖥️ Parallels upgrade checks
|
||||
🔧 Package verification tightened
|
||||
|
||||
Beta first. Stable after the gauntlet.
|
||||
<release link>
|
||||
```
|
||||
|
||||
```text
|
||||
OpenClaw 2026.4.20 🦞
|
||||
|
||||
🚀 Faster install + update
|
||||
🐳 Docker + Parallels verified
|
||||
🍎 macOS signed + notarized
|
||||
🔧 Channel/plugin fixes
|
||||
|
||||
Good boring release. Best kind.
|
||||
<release link>
|
||||
```
|
||||
|
||||
```text
|
||||
Packaging issue in 2026.4.20-beta.1.
|
||||
|
||||
2026.4.20-beta.2 fixes install/update verification. No tag rewrites; beta moves
|
||||
forward.
|
||||
|
||||
Upgrade with the beta channel.
|
||||
<release link>
|
||||
```
|
||||
|
||||
## Run publish-time validation
|
||||
|
||||
Before tagging or publishing, run:
|
||||
|
||||
```bash
|
||||
pnpm check:architecture
|
||||
pnpm build
|
||||
pnpm ui:build
|
||||
pnpm release:check
|
||||
@@ -106,16 +198,46 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
|
||||
## Check all relevant release builds
|
||||
|
||||
- Always validate the OpenClaw npm release path before creating the tag.
|
||||
- Source Peter's profile before live release validation so OpenAI and Anthropic
|
||||
credentials are available without printing secrets:
|
||||
`set -a; source "$HOME/.profile"; set +a`.
|
||||
- Release QA and Parallels validation for this train must use both
|
||||
`OPENAI_API_KEY` and `ANTHROPIC_API_KEY`. If either is missing after sourcing
|
||||
`.profile`, stop before starting the long lanes and report the missing key.
|
||||
- Default release checks:
|
||||
- `pnpm check`
|
||||
- `pnpm check:test-types`
|
||||
- `pnpm check:architecture`
|
||||
- `pnpm build`
|
||||
- `pnpm ui:build`
|
||||
- `pnpm release:check`
|
||||
- `OPENCLAW_INSTALL_SMOKE_SKIP_NONROOT=1 pnpm test:install:smoke`
|
||||
- Full pre-npm beta test roster:
|
||||
- default release checks above
|
||||
- all Docker tests: `pnpm test:docker:all`, plus standalone Docker live lanes
|
||||
not covered by the aggregate when operator says "all docker tests":
|
||||
`pnpm test:docker:live-acp-bind`, `pnpm test:docker:live-cli-backend`, and
|
||||
`pnpm test:docker:live-codex-harness`
|
||||
- all Parallels install/update tests:
|
||||
`pnpm test:parallels:npm-update -- --json` plus any needed individual
|
||||
rerun lanes from `openclaw-parallels-smoke`
|
||||
- all QA release validation:
|
||||
OpenAI live suite with `openai/gpt-5.4` in fast mode, Anthropic live suite
|
||||
with `anthropic/claude-opus-4-6`, and the repo-backed character evals
|
||||
- Post-published beta verification roster:
|
||||
- `node --import tsx scripts/openclaw-npm-postpublish-verify.ts <beta-version>`
|
||||
- install/update smoke against the published beta channel
|
||||
- Docker install/update coverage that exercises the published beta package
|
||||
- Parallels published beta install/update coverage with both OpenAI and
|
||||
Anthropic provider keys available
|
||||
- targeted QA reruns only for areas touched by fixes after the full pre-npm
|
||||
roster, unless the operator requests the full QA roster again
|
||||
- Check all release-related build surfaces touched by the release, not only the npm package.
|
||||
- For beta-style full e2e batteries, hard-cap top-level long lanes instead of letting them run indefinitely. Use host `timeout --foreground`/`gtimeout --foreground` caps such as:
|
||||
- `45m` for `OPENCLAW_INSTALL_SMOKE_SKIP_NONROOT=1 pnpm test:install:smoke`
|
||||
- `90m` for `pnpm test:docker:all`
|
||||
- `60m` each for standalone Docker live lanes
|
||||
- `180m` for the full QA live OpenAI + Anthropic roster
|
||||
- Parallels caps from the `openclaw-parallels-smoke` skill
|
||||
If a lane hits its cap, stop and inspect/fix the affected lane before continuing; do not continue to wait on the same process.
|
||||
- Actual npm install/update phases are capped at 5 minutes. If `npm install -g`, installer package install, or `openclaw update` takes longer than 300s in release e2e, stop treating the run as healthy progress and debug the installer/updater or harness.
|
||||
@@ -129,6 +251,8 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
|
||||
- Any fix after preflight means a new commit. Delete and recreate the tag and
|
||||
matching GitHub release from the fixed commit, then rerun preflight from
|
||||
scratch before publishing.
|
||||
Exception: never delete or recreate a beta tag that has already been pushed or
|
||||
published; increment to the next beta number instead.
|
||||
- For stable mac releases, generate the signed `appcast.xml` before uploading
|
||||
public release assets so the updater feed cannot lag the published binaries.
|
||||
- Serialize stable appcast-producing runs across tags so two releases do not
|
||||
@@ -139,14 +263,13 @@ 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.
|
||||
- Direct stable publishes can also run the same workflow with
|
||||
`sync_stable_dist_tags=true` to point both `latest` and `beta` at the
|
||||
already-published stable version. This also needs the `npm-release`
|
||||
environment approval and `NPM_TOKEN`.
|
||||
- Stable npm promotion from `beta` to `latest` uses the private
|
||||
`openclaw/releases-private/.github/workflows/openclaw-npm-dist-tags.yml`
|
||||
workflow because `npm dist-tag` management needs `NPM_TOKEN`, while the
|
||||
public npm release workflow stays OIDC-only.
|
||||
- Direct stable publishes can also use that private dist-tag workflow to point
|
||||
`beta` at the already-published `latest` version when the operator wants both
|
||||
tags aligned immediately.
|
||||
- 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
|
||||
@@ -162,8 +285,9 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
|
||||
- `preflight_only=true` on the npm workflow is also the right way to validate an
|
||||
existing tag after publish; it should keep running the build checks even when
|
||||
the npm version is already published.
|
||||
- Validation-only runs may be dispatched from a branch when you are testing a
|
||||
workflow change before merge.
|
||||
- npm validation-only preflight may still be dispatched from ordinary branches
|
||||
when testing workflow changes before merge. Release checks and real publish
|
||||
use only `main` or `release/YYYY.M.D`.
|
||||
- `.github/workflows/macos-release.yml` in `openclaw/openclaw` is now a
|
||||
public validation-only handoff. It validates the tag/release state and points
|
||||
operators to the private repo. It still rebuilds the JS outputs needed for
|
||||
@@ -171,7 +295,7 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
|
||||
artifacts.
|
||||
- `openclaw/releases-private/.github/workflows/openclaw-macos-validate.yml`
|
||||
is the required private mac validation lane for `swift test`; keep it green
|
||||
before any real mac publish run starts.
|
||||
before any real stable mac publish run starts.
|
||||
- Real mac preflight and real mac publish both use
|
||||
`openclaw/releases-private/.github/workflows/openclaw-macos-publish.yml`.
|
||||
- The private mac validation lane runs on GitHub's standard macOS runner.
|
||||
@@ -181,10 +305,15 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
|
||||
instead of uploading public GitHub release assets.
|
||||
- Private smoke-test runs upload ad-hoc, non-notarized build artifacts as
|
||||
workflow artifacts and intentionally skip stable `appcast.xml` generation.
|
||||
- npm preflight, public mac validation, private mac validation, and private mac
|
||||
preflight must all pass before any real publish run starts.
|
||||
- Real publish runs must be dispatched from `main`; branch-dispatched publish
|
||||
attempts should fail before the protected environment is reached.
|
||||
- For stable releases, npm preflight, public mac validation, private mac
|
||||
validation, and private mac preflight must all pass before any real publish
|
||||
run starts. For beta releases, npm preflight plus the selected Docker,
|
||||
install/update, Parallels, and release-check lanes are sufficient unless mac
|
||||
beta validation was explicitly requested.
|
||||
- Real publish runs may be dispatched from `main` or from a
|
||||
`release/YYYY.M.D` branch. For release-branch runs, the tag must be contained
|
||||
in that release branch, and the real publish must reuse a successful preflight
|
||||
from the same branch.
|
||||
- The release workflows stay tag-based; rely on the documented release sequence
|
||||
rather than workflow-level SHA pinning.
|
||||
- The `npm-release` environment must be approved by `@openclaw/openclaw-release-managers` before publish continues.
|
||||
@@ -245,58 +374,82 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
|
||||
|
||||
1. Confirm the operator explicitly wants to cut a release.
|
||||
2. Choose the exact target version and git tag.
|
||||
3. Make every repo version location match that tag before creating it.
|
||||
4. Update `CHANGELOG.md` and assemble the matching GitHub release notes.
|
||||
5. Run the full preflight for all relevant release builds, including mac readiness.
|
||||
6. Confirm the target npm version is not already published.
|
||||
7. Create and push the git tag.
|
||||
8. Create or refresh the matching GitHub release.
|
||||
9. Start `.github/workflows/openclaw-npm-release.yml` with `preflight_only=true`
|
||||
and choose the intended `npm_dist_tag` (`beta` default; `latest` only for
|
||||
an intentional direct stable publish). Wait for it to pass. Save that run id
|
||||
because the real publish requires it to reuse the prepared npm tarball.
|
||||
10. Start `.github/workflows/macos-release.yml` in `openclaw/openclaw` and wait
|
||||
for the public validation-only run to pass.
|
||||
11. Start
|
||||
3. Commit any dirty files in coherent groups, push, pull/rebase, and verify the
|
||||
worktree is clean.
|
||||
4. Pull latest `main` and confirm current `main` CI is green.
|
||||
5. Run `/changelog` for the stable base target version on `main`, commit the
|
||||
changelog rewrite immediately, push, and pull/rebase. For beta releases,
|
||||
keep the changelog heading as `## YYYY.M.D`, not `## YYYY.M.D-beta.N`.
|
||||
6. Create `release/YYYY.M.D` from that post-changelog `main` commit.
|
||||
7. Make every repo version location match the beta tag before creating it.
|
||||
8. Commit release preparation changes on the release branch and push the branch.
|
||||
9. Run the full pre-npm beta test roster from the release branch before any npm
|
||||
preflight or publish.
|
||||
10. For beta releases, skip mac app build/sign/notarize unless beta scope or a
|
||||
release blocker specifically requires it. For stable releases, include the
|
||||
mac app, signing, notarization, and appcast path.
|
||||
11. Confirm the target npm version is not already published.
|
||||
12. Create and push the git tag from the release branch.
|
||||
13. Create or refresh the matching GitHub release.
|
||||
14. Start `.github/workflows/openclaw-npm-release.yml` from the release branch
|
||||
with `preflight_only=true`
|
||||
and choose the intended `npm_dist_tag` (`beta` default; `latest` only for
|
||||
an intentional direct stable publish). Wait for it to pass. Save that run id
|
||||
because the real publish requires it to reuse the prepared npm tarball.
|
||||
15. For stable releases, start `.github/workflows/macos-release.yml` in
|
||||
`openclaw/openclaw` and wait for the public validation-only run to pass.
|
||||
16. For stable releases, start
|
||||
`openclaw/releases-private/.github/workflows/openclaw-macos-validate.yml`
|
||||
with the same tag and wait for the private mac validation lane to pass.
|
||||
12. Start
|
||||
17. For stable releases, start
|
||||
`openclaw/releases-private/.github/workflows/openclaw-macos-publish.yml`
|
||||
with `preflight_only=true` and wait for it to pass. Save that run id because
|
||||
the real publish requires it to reuse the notarized mac artifacts.
|
||||
13. If any preflight or validation run fails, fix the issue on a new commit,
|
||||
18. If any preflight or validation run fails, fix the issue on a new commit,
|
||||
delete the tag and matching GitHub release, recreate them from the fixed
|
||||
commit, and rerun all relevant preflights from scratch before continuing.
|
||||
Never reuse old preflight results after the commit changes.
|
||||
14. Start `.github/workflows/openclaw-npm-release.yml` with the same tag for
|
||||
the real publish, choose `npm_dist_tag` (`beta` default, `latest` only when
|
||||
you intentionally want direct stable publish), keep it the same as the
|
||||
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-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. If the stable release was published directly to `latest` and `beta` should
|
||||
follow it, start `.github/workflows/openclaw-npm-release.yml` again with
|
||||
the same stable tag, `sync_stable_dist_tags=true`,
|
||||
`promote_beta_to_latest=false`, `preflight_only=false`, empty
|
||||
`preflight_run_id`, and `npm_dist_tag=latest`, then verify both `latest`
|
||||
and `beta` point at that version.
|
||||
18. Start
|
||||
Never reuse old preflight results after the commit changes. For pushed or
|
||||
published beta tags, do not delete/recreate; increment to the next beta tag.
|
||||
19. Start `.github/workflows/openclaw-npm-release.yml` from the same branch with
|
||||
the same tag for the real publish, choose `npm_dist_tag` (`beta` default,
|
||||
`latest` only when you intentionally want direct stable publish), keep it
|
||||
the same as the preflight run, and pass the successful npm
|
||||
`preflight_run_id`.
|
||||
20. Wait for `npm-release` approval from `@openclaw/openclaw-release-managers`.
|
||||
21. Run postpublish verification:
|
||||
`node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>`.
|
||||
22. Run the post-published beta verification roster. If any lane fails after
|
||||
the beta tag/package is pushed or published, fix, commit/push/pull,
|
||||
increment to the next beta tag, and restart at the full pre-npm beta test
|
||||
roster for the new beta. If a pre-npm lane fails before any tag/package
|
||||
leaves the machine, fix and rerun the same intended beta attempt. Repeat up
|
||||
to the operator's authorized beta-attempt limit, normally 4.
|
||||
23. Announce the beta/stable release on Discord best-effort using Peter's bot
|
||||
token from `.profile`.
|
||||
24. If the operator requested beta only, stop after beta verification and the
|
||||
announcement.
|
||||
25. If the stable release was published to `beta`, start the private
|
||||
`openclaw/releases-private/.github/workflows/openclaw-npm-dist-tags.yml`
|
||||
workflow after beta validation passes to promote that stable version from
|
||||
`beta` to `latest`, then verify `latest` now points at that version.
|
||||
26. If the stable release was published directly to `latest` and `beta` should
|
||||
follow it, start that same private dist-tag workflow to point `beta` at the
|
||||
stable version, then verify both `latest` and `beta` point at that version.
|
||||
27. For stable releases, start
|
||||
`openclaw/releases-private/.github/workflows/openclaw-macos-publish.yml`
|
||||
for the real publish with the successful private mac `preflight_run_id` and
|
||||
wait for success.
|
||||
19. Verify the successful real private mac run uploaded the `.zip`, `.dmg`,
|
||||
28. Verify the successful real private mac run uploaded the `.zip`, `.dmg`,
|
||||
and `.dSYM.zip` artifacts to the existing GitHub release in
|
||||
`openclaw/openclaw`.
|
||||
20. For stable releases, download `macos-appcast-<tag>` from the successful
|
||||
private mac run, update `appcast.xml` on `main`, and verify the feed.
|
||||
21. For beta releases, publish the mac assets but expect no shared production
|
||||
29. For stable releases, download `macos-appcast-<tag>` from the successful
|
||||
private mac run, update `appcast.xml` on `main`, and verify the feed. Merge
|
||||
or cherry-pick release branch changes back to `main` after stable succeeds.
|
||||
30. For beta releases, publish the mac assets only when intentionally requested;
|
||||
expect no shared production
|
||||
`appcast.xml` artifact and do not update the shared production feed unless a
|
||||
separate beta feed exists.
|
||||
22. After publish, verify npm and the attached release artifacts.
|
||||
31. After publish, verify npm and the attached release artifacts.
|
||||
|
||||
## GHSA advisory work
|
||||
|
||||
|
||||
134
.agents/skills/openclaw-test-performance/SKILL.md
Normal file
134
.agents/skills/openclaw-test-performance/SKILL.md
Normal file
@@ -0,0 +1,134 @@
|
||||
---
|
||||
name: openclaw-test-performance
|
||||
description: Benchmark, diagnose, and optimize OpenClaw test performance without losing coverage. Use when Codex needs to reassess `pnpm test`, compare grouped Vitest reports, identify CPU/memory/import hotspots, fix slow tests or cold runtime paths, preserve behavior proofs, update the performance report, add AGENTS guardrails, and make scoped commits/pushes for OpenClaw test-speed work.
|
||||
---
|
||||
|
||||
# OpenClaw Test Performance
|
||||
|
||||
Use evidence first. The goal is real `pnpm test` speed/RSS improvement with
|
||||
coverage intact, not runner tuning by guesswork.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. Read the relevant local `AGENTS.md` files before editing:
|
||||
- `src/agents/AGENTS.md` for agent/import hotspots.
|
||||
- `src/channels/AGENTS.md` and `src/plugins/AGENTS.md` for plugin/channel
|
||||
laziness.
|
||||
- `src/gateway/AGENTS.md` for server lifecycle tests.
|
||||
- `test/helpers/AGENTS.md` and `test/helpers/channels/AGENTS.md` for shared
|
||||
contract helpers.
|
||||
- `src/infra/outbound/AGENTS.md` for outbound/media/action tests.
|
||||
2. Establish a baseline before changing code:
|
||||
- Prefer `pnpm test:perf:groups --full-suite --allow-failures --output <file>`
|
||||
for full-suite ranking.
|
||||
- For a scoped hotspot use:
|
||||
`/usr/bin/time -l pnpm test <file-or-files> --maxWorkers=1 --reporter=verbose`
|
||||
- For import-heavy suspicion add:
|
||||
`OPENCLAW_VITEST_IMPORT_DURATIONS=1 OPENCLAW_VITEST_PRINT_IMPORT_BREAKDOWN=1`.
|
||||
3. Separate wall/runner noise from real file cost:
|
||||
- Compare Vitest duration, test body timing, import breakdown, wall time, and
|
||||
max RSS.
|
||||
- Re-run single files when grouped/full-suite numbers look stale or noisy.
|
||||
- If a full-suite grouped run reports a lane failure but JSON says tests
|
||||
passed, capture that as harness/noise and verify the suspect file directly.
|
||||
4. Pick the next attack by return and risk:
|
||||
- High return: one file/test dominates seconds or RSS and has a clear root.
|
||||
- Lower risk: static descriptors, target parsing, routing, auth bypass,
|
||||
setup hints, registry fixtures, or test server lifecycle.
|
||||
- Higher risk: real memory/runtime behavior, live providers, protocol
|
||||
contracts, or broad production refactors.
|
||||
5. Fix the root cause, not the symptom:
|
||||
- Move static metadata/parsing into narrow helpers or lightweight artifacts
|
||||
reused by full runtime and fast paths.
|
||||
- Prefer dependency injection, loaded-plugin-only lookup, explicit fixtures,
|
||||
and pure helpers over broad mocks.
|
||||
- Reuse suite-level servers/clients when a fresh handshake is irrelevant.
|
||||
- Keep schedulers/background loops off unless the test proves scheduling.
|
||||
6. Preserve coverage shape:
|
||||
- Do not delete a slow integration proof unless the exact production
|
||||
composition is extracted into a named helper and tested.
|
||||
- Keep one cheap integration smoke when cross-component wiring matters.
|
||||
- State explicitly what incidental coverage was removed, if any.
|
||||
7. Re-benchmark the same command after the change and compute seconds plus
|
||||
percent gain.
|
||||
8. Update the running report when requested or when this thread is tracking one.
|
||||
Include before/after commands, artifacts, coverage notes, verification, and
|
||||
next attack order.
|
||||
9. Commit with `scripts/committer "<message>" <paths...>` and push when the
|
||||
user asked for commits/pushes. Stage only files touched for this attack.
|
||||
|
||||
## Common Root Causes
|
||||
|
||||
- Full bundled channel/plugin runtime loaded for static data.
|
||||
- `getChannelPlugin()` fallback used when an already-loaded fixture or pure
|
||||
parser would suffice.
|
||||
- Broad `api.ts`, `runtime-api.ts`, `test-api.ts`, or plugin-sdk barrels pulled
|
||||
into hot tests.
|
||||
- Partial-real mocks using `importActual()` around broad modules.
|
||||
- `vi.resetModules()` plus fresh imports in per-test loops.
|
||||
- Test plugin registry seeded in `beforeAll` while runtime state resets in
|
||||
`afterEach`.
|
||||
- Per-test gateway/server/client startup when state reset would suffice.
|
||||
- Runtime/default model/auth selection paid by idle snapshots or fixtures.
|
||||
- Plugin-owned media/action discovery triggered before checking whether args
|
||||
contain plugin-owned fields.
|
||||
|
||||
## Benchmark Commands
|
||||
|
||||
Scoped file:
|
||||
|
||||
```bash
|
||||
timeout 240 /usr/bin/time -l pnpm test <file> --maxWorkers=1 --reporter=verbose
|
||||
```
|
||||
|
||||
Scoped file with import breakdown:
|
||||
|
||||
```bash
|
||||
timeout 240 /usr/bin/time -l env \
|
||||
OPENCLAW_VITEST_IMPORT_DURATIONS=1 \
|
||||
OPENCLAW_VITEST_PRINT_IMPORT_BREAKDOWN=1 \
|
||||
pnpm test <file> --maxWorkers=1 --reporter=verbose
|
||||
```
|
||||
|
||||
Grouped suite:
|
||||
|
||||
```bash
|
||||
pnpm test:perf:groups --full-suite --allow-failures \
|
||||
--output .artifacts/test-perf/<name>.json
|
||||
```
|
||||
|
||||
Reuse an existing Vitest JSON report:
|
||||
|
||||
```bash
|
||||
pnpm test:perf:groups --report <vitest-json> \
|
||||
--output .artifacts/test-perf/<name>.json
|
||||
```
|
||||
|
||||
## Verification
|
||||
|
||||
- Always run the targeted test surface that proves the change.
|
||||
- Run `pnpm check` before commit unless the change is docs-only and the hook
|
||||
handles it.
|
||||
- Run `pnpm build` when touching lazy-loading, bundled artifacts, package
|
||||
boundaries, dynamic imports, build output, or public surfaces.
|
||||
- If deps are missing/stale, run `pnpm install` and retry the exact failed
|
||||
command once.
|
||||
- Use the report format:
|
||||
|
||||
```markdown
|
||||
| Metric | Before | After | Gain |
|
||||
| -------------- | -----: | ----: | ------------: |
|
||||
| File wall time | `Xs` | `Ys` | `-Zs` (`P%`) |
|
||||
| Max RSS | `XMB` | `YMB` | `-ZMB` (`P%`) |
|
||||
```
|
||||
|
||||
## Handoff
|
||||
|
||||
Keep the final concise:
|
||||
|
||||
- Root cause.
|
||||
- Files changed.
|
||||
- Before/after numbers.
|
||||
- Coverage retained.
|
||||
- Verification commands.
|
||||
- Commit hash and push status.
|
||||
@@ -0,0 +1,6 @@
|
||||
interface:
|
||||
display_name: "OpenClaw Test Performance"
|
||||
short_description: "Benchmark and fix slow OpenClaw tests"
|
||||
default_prompt: "Use $openclaw-test-performance to reassess the OpenClaw test benchmark, identify the next real hotspot, fix it without losing coverage, update the report, and commit scoped changes."
|
||||
policy:
|
||||
allow_implicit_invocation: false
|
||||
12
.github/actions/setup-node-env/action.yml
vendored
12
.github/actions/setup-node-env/action.yml
vendored
@@ -19,10 +19,6 @@ inputs:
|
||||
description: Whether to install Bun alongside Node.
|
||||
required: false
|
||||
default: "true"
|
||||
use-sticky-disk:
|
||||
description: Request Blacksmith sticky-disk pnpm caching on trusted runs; pull_request runs fall back to actions/cache.
|
||||
required: false
|
||||
default: "false"
|
||||
install-deps:
|
||||
description: Whether to run pnpm install after environment setup.
|
||||
required: false
|
||||
@@ -45,7 +41,6 @@ runs:
|
||||
with:
|
||||
pnpm-version: ${{ inputs.pnpm-version }}
|
||||
cache-key-suffix: ${{ inputs.cache-key-suffix }}
|
||||
use-sticky-disk: ${{ inputs.use-sticky-disk }}
|
||||
|
||||
- name: Setup Bun
|
||||
if: inputs.install-bun == 'true'
|
||||
@@ -64,7 +59,12 @@ runs:
|
||||
- name: Capture node path
|
||||
if: inputs.install-deps == 'true'
|
||||
shell: bash
|
||||
run: echo "NODE_BIN=$(dirname "$(node -p "process.execPath")")" >> "$GITHUB_ENV"
|
||||
run: |
|
||||
node_bin="$(dirname "$(node -p 'process.execPath')")"
|
||||
if command -v cygpath >/dev/null 2>&1; then
|
||||
node_bin="$(cygpath -u "$node_bin")"
|
||||
fi
|
||||
echo "NODE_BIN=$node_bin" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Install dependencies
|
||||
if: inputs.install-deps == 'true'
|
||||
|
||||
@@ -9,16 +9,12 @@ inputs:
|
||||
description: Suffix appended to the cache key.
|
||||
required: false
|
||||
default: "node24"
|
||||
use-sticky-disk:
|
||||
description: Use Blacksmith sticky disks instead of actions/cache for pnpm store on trusted runs; pull_request runs fall back to actions/cache.
|
||||
required: false
|
||||
default: "false"
|
||||
use-restore-keys:
|
||||
description: Whether to use restore-keys fallback for actions/cache.
|
||||
required: false
|
||||
default: "true"
|
||||
use-actions-cache:
|
||||
description: Whether to restore/save pnpm store with actions/cache, including pull_request fallback when sticky disks are disabled.
|
||||
description: Whether to restore/save pnpm store with actions/cache.
|
||||
required: false
|
||||
default: "true"
|
||||
runs:
|
||||
@@ -50,24 +46,15 @@ runs:
|
||||
shell: bash
|
||||
run: echo "path=$(pnpm store path --silent)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Mount pnpm store sticky disk
|
||||
# Keep persistent sticky-disk state off untrusted PR runs.
|
||||
if: inputs.use-sticky-disk == 'true' && github.event_name != 'pull_request'
|
||||
uses: useblacksmith/stickydisk@v1
|
||||
with:
|
||||
key: ${{ github.repository }}-pnpm-store-${{ runner.os }}-${{ github.ref_name }}-${{ inputs.cache-key-suffix }}-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
path: ${{ steps.pnpm-store.outputs.path }}
|
||||
|
||||
- name: Restore pnpm store cache (exact key only)
|
||||
# PRs that request sticky disks still need a safe cache restore path.
|
||||
if: inputs.use-actions-cache == 'true' && (inputs.use-sticky-disk != 'true' || github.event_name == 'pull_request') && inputs.use-restore-keys != 'true'
|
||||
if: inputs.use-actions-cache == 'true' && inputs.use-restore-keys != 'true'
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ${{ steps.pnpm-store.outputs.path }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ inputs.cache-key-suffix }}-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
|
||||
- name: Restore pnpm store cache (with fallback keys)
|
||||
if: inputs.use-actions-cache == 'true' && (inputs.use-sticky-disk != 'true' || github.event_name == 'pull_request') && inputs.use-restore-keys == 'true'
|
||||
if: inputs.use-actions-cache == 'true' && inputs.use-restore-keys == 'true'
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ${{ steps.pnpm-store.outputs.path }}
|
||||
|
||||
6
.github/instructions/copilot.instructions.md
vendored
6
.github/instructions/copilot.instructions.md
vendored
@@ -49,14 +49,14 @@
|
||||
- TypeScript (ESM), strict typing, avoid `any`
|
||||
- Keep files under ~700 LOC - extract helpers when larger
|
||||
- Colocated tests: `*.test.ts` next to source files
|
||||
- Run `pnpm check` before commits (lint + format)
|
||||
- Run `pnpm tsgo` for type checking
|
||||
- Run `pnpm check` before commits (production type check + lint + format)
|
||||
- Run `pnpm check:test-types` when you need test type coverage, or `pnpm tsgo:all` for a full production plus test type sweep
|
||||
|
||||
## Stack & Commands
|
||||
|
||||
- **Package manager**: pnpm (`pnpm install`)
|
||||
- **Dev**: `pnpm openclaw ...` or `pnpm dev`
|
||||
- **Type-check**: `pnpm tsgo`
|
||||
- **Type-check**: `pnpm tsgo` (core production), `pnpm tsgo:prod` (core + extension production), `pnpm check:test-types` (tests)
|
||||
- **Lint/format**: `pnpm check`
|
||||
- **Tests**: `pnpm test`
|
||||
- **Build**: `pnpm build`
|
||||
|
||||
679
.github/workflows/ci.yml
vendored
679
.github/workflows/ci.yml
vendored
File diff suppressed because it is too large
Load Diff
1
.github/workflows/codeql.yml
vendored
1
.github/workflows/codeql.yml
vendored
@@ -81,7 +81,6 @@ jobs:
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
install-bun: "false"
|
||||
use-sticky-disk: "false"
|
||||
|
||||
- name: Setup Python
|
||||
if: matrix.needs_python
|
||||
|
||||
@@ -121,7 +121,6 @@ jobs:
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
install-bun: "false"
|
||||
use-sticky-disk: "false"
|
||||
|
||||
- name: Ensure translation provider secrets exist
|
||||
env:
|
||||
|
||||
2
.github/workflows/docs-sync-publish.yml
vendored
2
.github/workflows/docs-sync-publish.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout source repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
12
.github/workflows/install-smoke.yml
vendored
12
.github/workflows/install-smoke.yml
vendored
@@ -64,7 +64,6 @@ jobs:
|
||||
with:
|
||||
install-bun: "false"
|
||||
install-deps: "false"
|
||||
use-sticky-disk: "false"
|
||||
|
||||
- name: Build install-smoke CI manifest
|
||||
id: manifest
|
||||
@@ -86,7 +85,7 @@ jobs:
|
||||
install-smoke:
|
||||
needs: [preflight]
|
||||
if: needs.preflight.outputs.run_install_smoke == 'true'
|
||||
runs-on: blacksmith-16vcpu-ubuntu-2404
|
||||
runs-on: blacksmith-32vcpu-ubuntu-2404
|
||||
env:
|
||||
DOCKER_BUILD_SUMMARY: "false"
|
||||
DOCKER_BUILD_RECORD_UPLOAD: "false"
|
||||
@@ -94,11 +93,11 @@ jobs:
|
||||
- name: Checkout CLI
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Docker Builder
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
||||
- name: Set up Blacksmith Docker Builder
|
||||
uses: useblacksmith/setup-docker-builder@ac083cc84672d01c60d5e8561d0a939b697de542 # v1
|
||||
|
||||
# Blacksmith can fall back to the local docker driver, which rejects gha
|
||||
# cache export/import. Keep smoke builds driver-agnostic.
|
||||
# Blacksmith's builder owns the Docker layer cache; keep smoke builds off
|
||||
# explicit gha cache directives so local tags still load cleanly.
|
||||
- name: Build root Dockerfile smoke image
|
||||
uses: useblacksmith/build-push-action@cbd1f60d194a98cb3be5523b15134501eaf0fbf3 # v2
|
||||
with:
|
||||
@@ -202,7 +201,6 @@ jobs:
|
||||
with:
|
||||
install-bun: "false"
|
||||
install-deps: "true"
|
||||
use-sticky-disk: "false"
|
||||
|
||||
- name: Run installer docker tests
|
||||
env:
|
||||
|
||||
1
.github/workflows/macos-release.yml
vendored
1
.github/workflows/macos-release.yml
vendored
@@ -50,7 +50,6 @@ jobs:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
pnpm-version: ${{ env.PNPM_VERSION }}
|
||||
install-bun: "false"
|
||||
use-sticky-disk: "false"
|
||||
|
||||
- name: Ensure matching GitHub release exists
|
||||
env:
|
||||
|
||||
@@ -230,7 +230,6 @@ jobs:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
pnpm-version: ${{ env.PNPM_VERSION }}
|
||||
install-bun: "true"
|
||||
use-sticky-disk: "false"
|
||||
|
||||
- name: Validate live cache credentials
|
||||
run: |
|
||||
@@ -267,7 +266,6 @@ jobs:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
pnpm-version: ${{ env.PNPM_VERSION }}
|
||||
install-bun: "true"
|
||||
use-sticky-disk: "false"
|
||||
|
||||
- name: Build dist for repo E2E
|
||||
run: pnpm build
|
||||
@@ -313,7 +311,6 @@ jobs:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
pnpm-version: ${{ env.PNPM_VERSION }}
|
||||
install-bun: "true"
|
||||
use-sticky-disk: "false"
|
||||
|
||||
- name: Build dist for special E2E
|
||||
if: |
|
||||
@@ -384,6 +381,12 @@ jobs:
|
||||
timeout_minutes: 75
|
||||
release_path: true
|
||||
openwebui_only: false
|
||||
- suite_id: docker-bundled-channel-deps
|
||||
label: Bundled Channel Runtime Deps Docker E2E
|
||||
command: pnpm test:docker:bundled-channel-deps
|
||||
timeout_minutes: 75
|
||||
release_path: true
|
||||
openwebui_only: false
|
||||
- suite_id: docker-doctor-switch
|
||||
label: Doctor Install Switch Docker E2E
|
||||
command: pnpm test:docker:doctor-switch
|
||||
@@ -465,7 +468,6 @@ jobs:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
pnpm-version: ${{ env.PNPM_VERSION }}
|
||||
install-bun: "true"
|
||||
use-sticky-disk: "false"
|
||||
|
||||
- name: Hydrate live auth/profile inputs
|
||||
run: bash scripts/ci-hydrate-live-auth.sh
|
||||
@@ -608,7 +610,6 @@ jobs:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
pnpm-version: ${{ env.PNPM_VERSION }}
|
||||
install-bun: "true"
|
||||
use-sticky-disk: "false"
|
||||
|
||||
- name: Hydrate live auth/profile inputs
|
||||
run: bash scripts/ci-hydrate-live-auth.sh
|
||||
|
||||
51
.github/workflows/openclaw-npm-release.yml
vendored
51
.github/workflows/openclaw-npm-release.yml
vendored
@@ -4,7 +4,7 @@ on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: Release tag to publish, or a full 40-character main commit SHA for validation-only preflight (for example v2026.3.22 or 0123456789abcdef0123456789abcdef01234567)
|
||||
description: Release tag to publish, or a full 40-character workflow-branch commit SHA for validation-only preflight (for example v2026.3.22 or 0123456789abcdef0123456789abcdef01234567)
|
||||
required: true
|
||||
type: string
|
||||
preflight_only:
|
||||
@@ -85,7 +85,6 @@ jobs:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
pnpm-version: ${{ env.PNPM_VERSION }}
|
||||
install-bun: "true"
|
||||
use-sticky-disk: "false"
|
||||
|
||||
- name: Ensure version is not already published
|
||||
env:
|
||||
@@ -110,6 +109,16 @@ jobs:
|
||||
OPENCLAW_LOCAL_CHECK: "0"
|
||||
run: pnpm check
|
||||
|
||||
- name: Check test types
|
||||
env:
|
||||
OPENCLAW_LOCAL_CHECK: "0"
|
||||
run: pnpm check:test-types
|
||||
|
||||
- name: Check architecture
|
||||
env:
|
||||
OPENCLAW_LOCAL_CHECK: "0"
|
||||
run: pnpm check:architecture
|
||||
|
||||
- name: Build
|
||||
run: pnpm build
|
||||
|
||||
@@ -122,19 +131,20 @@ jobs:
|
||||
OPENCLAW_NPM_RELEASE_SKIP_PACK_CHECK: "1"
|
||||
RELEASE_REF: ${{ inputs.tag }}
|
||||
PREFLIGHT_ONLY: ${{ inputs.preflight_only }}
|
||||
RELEASE_MAIN_REF: origin/main
|
||||
WORKFLOW_REF_NAME: ${{ github.ref_name }}
|
||||
OPENCLAW_NPM_PUBLISH_TAG: ${{ inputs.npm_dist_tag }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
RELEASE_SHA=$(git rev-parse HEAD)
|
||||
export RELEASE_SHA RELEASE_MAIN_REF
|
||||
# Fetch the full main ref so merge-base ancestry checks keep working
|
||||
# for older tagged commits that are still contained in main.
|
||||
git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main
|
||||
RELEASE_BRANCH_REF="refs/remotes/origin/${WORKFLOW_REF_NAME}"
|
||||
export RELEASE_SHA RELEASE_BRANCH_REF
|
||||
# Fetch the workflow branch so merge-base ancestry checks keep working
|
||||
# for older tagged commits contained in a release branch.
|
||||
git fetch --no-tags origin "+refs/heads/${WORKFLOW_REF_NAME}:refs/remotes/origin/${WORKFLOW_REF_NAME}"
|
||||
if [[ "${RELEASE_REF}" =~ ^[0-9a-fA-F]{40}$ ]]; then
|
||||
MAIN_SHA="$(git rev-parse origin/main)"
|
||||
if [[ "${RELEASE_SHA}" != "${MAIN_SHA}" ]]; then
|
||||
echo "Validation-only SHA mode only supports the current origin/main HEAD." >&2
|
||||
BRANCH_SHA="$(git rev-parse "${RELEASE_BRANCH_REF}")"
|
||||
if [[ "${RELEASE_SHA}" != "${BRANCH_SHA}" ]]; then
|
||||
echo "Validation-only SHA mode only supports the current ${WORKFLOW_REF_NAME} HEAD." >&2
|
||||
exit 1
|
||||
fi
|
||||
RELEASE_TAG="v$(node -p "require('./package.json').version")"
|
||||
@@ -144,6 +154,8 @@ jobs:
|
||||
RELEASE_TAG="${RELEASE_REF}"
|
||||
export RELEASE_TAG
|
||||
fi
|
||||
RELEASE_MAIN_REF="${RELEASE_BRANCH_REF}"
|
||||
export RELEASE_MAIN_REF
|
||||
pnpm release:openclaw:npm:check
|
||||
|
||||
# KEEP THIS LANE LIMITED TO FAST, REPEATABLE RELEASE READINESS CHECKS.
|
||||
@@ -244,13 +256,13 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Require main workflow ref for publish
|
||||
- name: Require main or release workflow ref for publish
|
||||
env:
|
||||
WORKFLOW_REF: ${{ github.ref }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ "${WORKFLOW_REF}" != "refs/heads/main" ]]; then
|
||||
echo "Real publish runs must be dispatched from main. Use preflight_only=true for branch validation."
|
||||
if [[ "${WORKFLOW_REF}" != "refs/heads/main" ]] && [[ ! "${WORKFLOW_REF}" =~ ^refs/heads/release/[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*$ ]]; then
|
||||
echo "Real publish runs must be dispatched from main or release/YYYY.M.D. Use preflight_only=true for other branch validation."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -303,7 +315,6 @@ jobs:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
pnpm-version: ${{ env.PNPM_VERSION }}
|
||||
install-bun: "false"
|
||||
use-sticky-disk: "false"
|
||||
|
||||
- name: Ensure version is not already published
|
||||
run: |
|
||||
@@ -321,10 +332,11 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
PREFLIGHT_RUN_ID: ${{ inputs.preflight_run_id }}
|
||||
EXPECTED_PREFLIGHT_BRANCH: ${{ github.ref_name }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
RUN_JSON="$(gh run view "$PREFLIGHT_RUN_ID" --repo "$GITHUB_REPOSITORY" --json workflowName,headBranch,event,conclusion,url)"
|
||||
printf '%s' "$RUN_JSON" | node -e 'const fs = require("node:fs"); const run = JSON.parse(fs.readFileSync(0, "utf8")); const checks = [["workflowName", "OpenClaw NPM Release"], ["headBranch", "main"], ["event", "workflow_dispatch"], ["conclusion", "success"]]; for (const [key, expected] of checks) { if (run[key] !== expected) { console.error(`Referenced npm preflight run ${process.env.PREFLIGHT_RUN_ID} must have ${key}=${expected}, got ${run[key] ?? "<missing>"}.`); process.exit(1); } } console.log(`Using npm preflight run ${process.env.PREFLIGHT_RUN_ID}: ${run.url}`);'
|
||||
printf '%s' "$RUN_JSON" | node -e 'const fs = require("node:fs"); const run = JSON.parse(fs.readFileSync(0, "utf8")); const checks = [["workflowName", "OpenClaw NPM Release"], ["headBranch", process.env.EXPECTED_PREFLIGHT_BRANCH], ["event", "workflow_dispatch"], ["conclusion", "success"]]; for (const [key, expected] of checks) { if (run[key] !== expected) { console.error(`Referenced npm preflight run ${process.env.PREFLIGHT_RUN_ID} must have ${key}=${expected}, got ${run[key] ?? "<missing>"}.`); process.exit(1); } } console.log(`Using npm preflight run ${process.env.PREFLIGHT_RUN_ID}: ${run.url}`);'
|
||||
|
||||
- name: Download prepared npm tarball
|
||||
uses: actions/download-artifact@v8
|
||||
@@ -340,14 +352,15 @@ jobs:
|
||||
env:
|
||||
OPENCLAW_NPM_RELEASE_SKIP_PACK_CHECK: "1"
|
||||
RELEASE_TAG: ${{ inputs.tag }}
|
||||
RELEASE_MAIN_REF: origin/main
|
||||
WORKFLOW_REF_NAME: ${{ github.ref_name }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
RELEASE_SHA=$(git rev-parse HEAD)
|
||||
RELEASE_MAIN_REF="refs/remotes/origin/${WORKFLOW_REF_NAME}"
|
||||
export RELEASE_SHA RELEASE_TAG RELEASE_MAIN_REF
|
||||
# Fetch the full main ref so merge-base ancestry checks keep working
|
||||
# for older tagged commits that are still contained in main.
|
||||
git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main
|
||||
# Fetch the workflow branch so merge-base ancestry checks keep working
|
||||
# for older tagged commits contained in a release branch.
|
||||
git fetch --no-tags origin "+refs/heads/${WORKFLOW_REF_NAME}:refs/remotes/origin/${WORKFLOW_REF_NAME}"
|
||||
pnpm release:openclaw:npm:check
|
||||
|
||||
- name: Verify prepared tarball provenance
|
||||
|
||||
24
.github/workflows/openclaw-release-checks.yml
vendored
24
.github/workflows/openclaw-release-checks.yml
vendored
@@ -4,7 +4,7 @@ on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
ref:
|
||||
description: Existing release tag or current full 40-character main commit SHA to validate (for example v2026.4.12 or 0123456789abcdef0123456789abcdef01234567)
|
||||
description: Existing release tag or current full 40-character workflow-branch commit SHA to validate (for example v2026.4.12 or 0123456789abcdef0123456789abcdef01234567)
|
||||
required: true
|
||||
type: string
|
||||
provider:
|
||||
@@ -45,13 +45,13 @@ jobs:
|
||||
provider: ${{ steps.inputs.outputs.provider }}
|
||||
mode: ${{ steps.inputs.outputs.mode }}
|
||||
steps:
|
||||
- name: Require main workflow ref for release checks
|
||||
- name: Require main or release workflow ref for release checks
|
||||
env:
|
||||
WORKFLOW_REF: ${{ github.ref }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ "${WORKFLOW_REF}" != "refs/heads/main" ]]; then
|
||||
echo "Release checks must be dispatched from main so the workflow logic and secrets stay canonical." >&2
|
||||
if [[ "${WORKFLOW_REF}" != "refs/heads/main" ]] && [[ ! "${WORKFLOW_REF}" =~ ^refs/heads/release/[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*$ ]]; then
|
||||
echo "Release checks must be dispatched from main or release/YYYY.M.D so workflow logic and secrets stay controlled." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -61,7 +61,7 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ ! "${RELEASE_REF}" =~ ^v[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*((-beta\.[1-9][0-9]*)|(-[1-9][0-9]*))?$ ]] && [[ ! "${RELEASE_REF}" =~ ^[0-9a-fA-F]{40}$ ]]; then
|
||||
echo "Expected an existing release tag or current full 40-character main commit SHA, got: ${RELEASE_REF}" >&2
|
||||
echo "Expected an existing release tag or current full 40-character workflow-branch commit SHA, got: ${RELEASE_REF}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -75,20 +75,22 @@ jobs:
|
||||
id: ref
|
||||
run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Validate selected ref is on main
|
||||
- name: Validate selected ref is on workflow branch
|
||||
env:
|
||||
RELEASE_REF: ${{ inputs.ref }}
|
||||
WORKFLOW_REF_NAME: ${{ github.ref_name }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main
|
||||
RELEASE_BRANCH_REF="refs/remotes/origin/${WORKFLOW_REF_NAME}"
|
||||
git fetch --no-tags origin "+refs/heads/${WORKFLOW_REF_NAME}:refs/remotes/origin/${WORKFLOW_REF_NAME}"
|
||||
if [[ "${RELEASE_REF}" =~ ^[0-9a-fA-F]{40}$ ]]; then
|
||||
MAIN_SHA="$(git rev-parse origin/main)"
|
||||
if [[ "$(git rev-parse HEAD)" != "${MAIN_SHA}" ]]; then
|
||||
echo "Commit SHA mode only supports the current origin/main HEAD. Use a release tag for older commits." >&2
|
||||
BRANCH_SHA="$(git rev-parse "${RELEASE_BRANCH_REF}")"
|
||||
if [[ "$(git rev-parse HEAD)" != "${BRANCH_SHA}" ]]; then
|
||||
echo "Commit SHA mode only supports the current ${WORKFLOW_REF_NAME} HEAD. Use a release tag for older commits." >&2
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
git merge-base --is-ancestor HEAD origin/main
|
||||
git merge-base --is-ancestor HEAD "${RELEASE_BRANCH_REF}"
|
||||
fi
|
||||
|
||||
- name: Capture selected inputs
|
||||
|
||||
2
.github/workflows/parity-gate.yml
vendored
2
.github/workflows/parity-gate.yml
vendored
@@ -48,7 +48,7 @@ jobs:
|
||||
OPENCLAW_LIVE_SETUP_TOKEN_VALUE: ""
|
||||
steps:
|
||||
- name: Checkout PR
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
3
.github/workflows/plugin-clawhub-release.yml
vendored
3
.github/workflows/plugin-clawhub-release.yml
vendored
@@ -53,7 +53,6 @@ jobs:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
pnpm-version: ${{ env.PNPM_VERSION }}
|
||||
install-bun: "false"
|
||||
use-sticky-disk: "false"
|
||||
|
||||
- name: Resolve checked-out ref
|
||||
id: ref
|
||||
@@ -160,7 +159,6 @@ jobs:
|
||||
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
|
||||
@@ -220,7 +218,6 @@ jobs:
|
||||
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
|
||||
|
||||
3
.github/workflows/plugin-npm-release.yml
vendored
3
.github/workflows/plugin-npm-release.yml
vendored
@@ -63,7 +63,6 @@ jobs:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
pnpm-version: ${{ env.PNPM_VERSION }}
|
||||
install-bun: "false"
|
||||
use-sticky-disk: "false"
|
||||
|
||||
- name: Resolve checked-out ref
|
||||
id: ref
|
||||
@@ -161,7 +160,6 @@ jobs:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
pnpm-version: ${{ env.PNPM_VERSION }}
|
||||
install-bun: "false"
|
||||
use-sticky-disk: "false"
|
||||
install-deps: "false"
|
||||
|
||||
- name: Preview publish command
|
||||
@@ -196,7 +194,6 @@ jobs:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
pnpm-version: ${{ env.PNPM_VERSION }}
|
||||
install-bun: "false"
|
||||
use-sticky-disk: "false"
|
||||
install-deps: "false"
|
||||
|
||||
- name: Ensure version is not already published
|
||||
|
||||
1
.github/workflows/workflow-sanity.yml
vendored
1
.github/workflows/workflow-sanity.yml
vendored
@@ -92,7 +92,6 @@ jobs:
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
install-bun: "false"
|
||||
use-sticky-disk: "false"
|
||||
|
||||
- name: Check config docs drift statefile
|
||||
run: pnpm config:docs:check
|
||||
|
||||
@@ -9,20 +9,40 @@
|
||||
"rules": {
|
||||
"curly": "error",
|
||||
"eslint-plugin-unicorn/prefer-array-find": "error",
|
||||
"eslint/no-array-constructor": "error",
|
||||
"eslint/no-await-in-loop": "off",
|
||||
"eslint/no-new": "error",
|
||||
"eslint/no-object-constructor": "error",
|
||||
"eslint/no-return-assign": "error",
|
||||
"eslint/no-shadow": "off",
|
||||
"eslint/no-useless-call": "error",
|
||||
"eslint/no-useless-computed-key": "error",
|
||||
"eslint/no-useless-concat": "error",
|
||||
"eslint/no-useless-constructor": "error",
|
||||
"eslint/no-warning-comments": "error",
|
||||
"eslint/no-unmodified-loop-condition": "error",
|
||||
"eslint-plugin-unicorn/prefer-set-size": "error",
|
||||
"oxc/no-accumulating-spread": "error",
|
||||
"oxc/no-async-endpoint-handlers": "off",
|
||||
"oxc/no-map-spread": "off",
|
||||
"oxc/no-async-endpoint-handlers": "error",
|
||||
"oxc/no-map-spread": "error",
|
||||
"typescript/consistent-return": "error",
|
||||
"typescript/no-explicit-any": "error",
|
||||
"typescript/no-extraneous-class": "error",
|
||||
"typescript/no-meaningless-void-operator": "error",
|
||||
"typescript/no-unnecessary-type-assertion": "error",
|
||||
"typescript/no-unnecessary-type-arguments": "error",
|
||||
"typescript/no-unnecessary-type-constraint": "error",
|
||||
"typescript/no-unnecessary-type-conversion": "error",
|
||||
"typescript/no-unnecessary-type-parameters": "error",
|
||||
"typescript/no-unsafe-type-assertion": "off",
|
||||
"typescript/no-useless-default-assignment": "error",
|
||||
"typescript/prefer-ts-expect-error": "error",
|
||||
"unicorn/consistent-function-scoping": "off",
|
||||
"unicorn/no-unnecessary-array-flat-depth": "error",
|
||||
"unicorn/no-unnecessary-array-splice-count": "error",
|
||||
"unicorn/no-unnecessary-slice-end": "error",
|
||||
"unicorn/no-useless-promise-resolve-reject": "error",
|
||||
"unicorn/prefer-date-now": "error",
|
||||
"unicorn/prefer-set-size": "error",
|
||||
"unicorn/require-post-message-target-origin": "error"
|
||||
},
|
||||
@@ -47,6 +67,13 @@
|
||||
"**/node_modules/**"
|
||||
],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["src/security/**"],
|
||||
"rules": {
|
||||
"eslint/no-warning-comments": "off",
|
||||
"oxc/no-map-spread": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"**/*.test.ts",
|
||||
|
||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -17,5 +17,6 @@
|
||||
"typescript.preferences.importModuleSpecifierEnding": "js",
|
||||
"typescript.reportStyleChecksAsWarnings": false,
|
||||
"typescript.updateImportsOnFileMove.enabled": "always",
|
||||
"typescript.tsdk": "node_modules/typescript/lib"
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"makefile.configureOnOpen": false
|
||||
}
|
||||
|
||||
463
AGENTS.md
463
AGENTS.md
@@ -1,322 +1,199 @@
|
||||
# Repository Guidelines
|
||||
# AGENTS.MD
|
||||
|
||||
- Repo: https://github.com/openclaw/openclaw
|
||||
- In chat replies, file references must be repo-root relative only (example: `extensions/telegram/src/index.ts:80`); never absolute paths or `~/...`.
|
||||
- Do not edit files covered by security-focused `CODEOWNERS` rules unless a listed owner explicitly asked for the change or is already reviewing it with you. Treat those paths as restricted surfaces, not drive-by cleanup.
|
||||
Telegraph style. Root rules only. Read scoped `AGENTS.md` before touching a subtree.
|
||||
|
||||
## Project Structure & Module Organization
|
||||
## Start
|
||||
|
||||
- Source code: `src/` (CLI wiring in `src/cli`, commands in `src/commands`, infra in `src/infra`, media pipeline in `src/media`, web provider helpers in `src/web` and `src/plugins/web-*provider*.ts`).
|
||||
- Tests: colocated `*.test.ts`.
|
||||
- Docs: `docs/` (images, queue, Pi config). Built output lives in `dist/`.
|
||||
- Nomenclature: use "plugin" / "plugins" in docs, UI, changelogs, and contributor guidance. The bundled workspace plugin tree remains the internal package layout to avoid repo-wide churn from a rename.
|
||||
- Bundled plugin naming: for repo-owned workspace plugins, keep the canonical plugin id aligned across `openclaw.plugin.json:id`, the default workspace folder name, and package names anchored to the same id (`@openclaw/<id>` or approved suffix forms like `-provider`, `-plugin`, `-speech`, `-sandbox`, `-media-understanding`). Keep `openclaw.install.npmSpec` equal to the package name and `openclaw.channel.id` equal to the plugin id when present. Exceptions must be explicit and covered by the repo invariant test.
|
||||
- Plugins: live in the bundled workspace plugin tree (workspace packages). Keep plugin-only deps in the extension `package.json`; do not add them to the root `package.json` unless core uses them.
|
||||
- Plugins: install runs `npm install --omit=dev` in plugin dir; runtime deps must live in `dependencies`. Avoid `workspace:*` in `dependencies` (npm install breaks); put `openclaw` in `devDependencies` or `peerDependencies` instead (runtime resolves `openclaw/plugin-sdk` via jiti alias).
|
||||
- Import boundaries: extension production code should treat `openclaw/plugin-sdk/*` plus local `api.ts` / `runtime-api.ts` barrels as the public surface. Do not import core `src/**`, `src/plugin-sdk-internal/**`, or another extension's `src/**` directly.
|
||||
- Installers served from `https://openclaw.ai/*`: live in the sibling repo `../openclaw.ai` (`public/install.sh`, `public/install-cli.sh`, `public/install.ps1`).
|
||||
- Messaging channels: always consider **all** built-in + extension channels when refactoring shared logic (routing, allowlists, pairing, command gating, onboarding, docs).
|
||||
- Core channel docs: `docs/channels/`
|
||||
- Core channel code: `src/channels`, `src/routing`, `src/web`
|
||||
- Bundled plugin channels: `extensions/<channel>/` (for example Discord, Telegram, Slack, Matrix, Zalo, ZaloUser, Voice Call)
|
||||
- When adding channels/plugins/apps/docs, update `.github/labeler.yml` and create matching GitHub labels (use existing channel/plugin label colors).
|
||||
- Repo: `https://github.com/openclaw/openclaw`
|
||||
- Replies: repo-root file refs only, e.g. `extensions/telegram/src/index.ts:80`. No absolute paths, no `~/`.
|
||||
- CODEOWNERS: maintenance/refactors/tests are ok. For larger behavior, product, security, or ownership-sensitive changes, get a listed owner request/review first.
|
||||
- First pass: run docs list (`bin/docs-list` or `pnpm docs:list`; ignore if unavailable), then read only relevant docs/guides.
|
||||
- Missing deps: run `pnpm install`, rerun once, then report first actionable error.
|
||||
- Use "plugin/plugins" in docs/UI/changelog. `extensions/` remains internal workspace layout.
|
||||
- Add channel/plugin/app/doc surface: update `.github/labeler.yml` and matching GitHub labels.
|
||||
- New `AGENTS.md`: add sibling `CLAUDE.md` symlink to it.
|
||||
|
||||
## Architecture Boundaries
|
||||
## Repo Map
|
||||
|
||||
- Start here for the repo map:
|
||||
- bundled workspace plugin tree = bundled plugins and the closest example surface for third-party plugins
|
||||
- `src/plugin-sdk/*` = the public plugin contract that extensions are allowed to import
|
||||
- `src/channels/*` = core channel implementation details behind the plugin/channel boundary
|
||||
- `src/plugins/*` = plugin discovery, manifest validation, loader, registry, and contract enforcement
|
||||
- `src/gateway/protocol/*` = typed Gateway control-plane and node wire protocol
|
||||
- Progressive disclosure lives in local boundary guides:
|
||||
- repo root `AGENTS.md`
|
||||
- bundled-plugin-tree `extensions/AGENTS.md`
|
||||
- `src/plugin-sdk/AGENTS.md`
|
||||
- `src/channels/AGENTS.md`
|
||||
- `src/plugins/AGENTS.md`
|
||||
- `src/gateway/protocol/AGENTS.md`
|
||||
- Workflow hygiene:
|
||||
- Do not grep or existence-check every `docs/*.md`, `AGENTS.md`, or guide path mentioned in this file before starting work.
|
||||
- Read only the guides and docs that are directly relevant to the files or boundary you are touching.
|
||||
- Only do full broken-link or missing-guide sweeps when the task is explicitly about docs or repo-instruction maintenance.
|
||||
- Plugin and extension boundary:
|
||||
- Public docs: `docs/plugins/building-plugins.md`, `docs/plugins/architecture.md`, `docs/plugins/sdk-overview.md`, `docs/plugins/sdk-entrypoints.md`, `docs/plugins/sdk-runtime.md`, `docs/plugins/manifest.md`, `docs/plugins/sdk-channel-plugins.md`, `docs/plugins/sdk-provider-plugins.md`
|
||||
- Definition files: `src/plugin-sdk/plugin-entry.ts`, `src/plugin-sdk/core.ts`, `src/plugin-sdk/provider-entry.ts`, `src/plugin-sdk/channel-contract.ts`, `scripts/lib/plugin-sdk-entrypoints.json`, `package.json`
|
||||
- Invariant: core must stay extension-agnostic. Adding a bundled or third-party extension should not require unrelated core edits just to teach core that the extension exists.
|
||||
- Rule: extensions must cross into core only through `openclaw/plugin-sdk/*`, manifest metadata, and documented runtime helpers. Do not import `src/**` from extension production code.
|
||||
- Rule: core code and tests must not deep-import bundled plugin internals such as a plugin's `src/**` files or `onboard.js`. If core needs a bundled plugin helper, expose it through that plugin's `api.ts` and, when it is a real cross-package contract, through `src/plugin-sdk/<id>.ts`.
|
||||
- Rule: do not add hardcoded bundled extension/provider/channel/capability id lists, maps, or named special cases in core when a manifest, capability, registry, or plugin-owned contract can express the same behavior.
|
||||
- Rule: extension-owned compatibility behavior belongs to the owning extension. Core may orchestrate generic doctor/config flows, but extension-specific legacy repairs, detection rules, onboarding, auth detection, and provider defaults should live in plugin-owned contracts.
|
||||
- Rule: for legacy config specifically, prefer doctor-owned repair paths over startup/load-time core migrations. Do not add new plugin-specific legacy migration logic to shared core/runtime surfaces when `openclaw doctor --fix` can own it.
|
||||
- Rule: when a test is asserting extension-specific behavior, keep that coverage in the owning extension when feasible. Core tests should assert generic contracts and registry/capability behavior, not extension internals.
|
||||
- Refactor trigger: if you encounter core code or tests that name a specific extension/provider/channel for extension-owned behavior, refactor toward a generic registry/capability/plugin-owned seam instead of adding another special case.
|
||||
- Compatibility: new plugin seams are allowed, but they must be added as documented, backwards-compatible, versioned contracts. We have third-party plugins in the wild and do not break them casually.
|
||||
- Channel boundary:
|
||||
- Public docs: `docs/plugins/sdk-channel-plugins.md`, `docs/plugins/architecture.md`
|
||||
- Definition files: `src/channels/plugins/types.plugin.ts`, `src/channels/plugins/types.core.ts`, `src/channels/plugins/types.adapters.ts`, `src/plugin-sdk/core.ts`, `src/plugin-sdk/channel-contract.ts`
|
||||
- Rule: `src/channels/**` is core implementation. If plugin authors need a new seam, add it to the Plugin SDK instead of telling them to import channel internals.
|
||||
- Provider/model boundary:
|
||||
- Public docs: `docs/plugins/sdk-provider-plugins.md`, `docs/concepts/model-providers.md`, `docs/plugins/architecture.md`
|
||||
- Definition files: `src/plugins/types.ts`, `src/plugin-sdk/provider-entry.ts`, `src/plugin-sdk/provider-auth.ts`, `src/plugin-sdk/provider-catalog-shared.ts`, `src/plugin-sdk/provider-model-shared.ts`
|
||||
- Rule: core owns the generic inference loop; provider plugins own provider-specific behavior through registration and typed hooks. Do not solve provider needs by reaching into unrelated core internals.
|
||||
- Rule: avoid ad hoc reads of `plugins.entries.<id>.config` from unrelated core code. If core needs plugin-owned auth/config behavior, add or use a generic seam (`resolveSyntheticAuth`, public SDK/helper facades, manifest metadata, plugin auto-enable hooks) and honor plugin disablement plus SecretRef semantics.
|
||||
- Rule: vendor-owned tools and settings belong in the owning plugin. Do not add provider-specific tool config, secret collection, or runtime enablement to core `tools.*` surfaces unless the tool is intentionally core-owned.
|
||||
- Gateway protocol boundary:
|
||||
- Public docs: `docs/gateway/protocol.md`, `docs/gateway/bridge-protocol.md`, `docs/concepts/architecture.md`
|
||||
- Definition files: `src/gateway/protocol/schema.ts`, `src/gateway/protocol/schema/*.ts`, `src/gateway/protocol/index.ts`
|
||||
- Rule: protocol changes are contract changes. Prefer additive evolution; incompatible changes require explicit versioning, docs, and client/codegen follow-through.
|
||||
- Config contract boundary:
|
||||
- Canonical public config lives in exported config types, zod/schema surfaces, schema help/labels, generated config metadata, config baselines, and any user-facing gateway/config payloads. Keep those surfaces aligned.
|
||||
- When a legacy config key is retired from the public contract, remove it from every public config surface above. Keep backward compatibility only through raw-config migration/doctor seams unless explicit product policy says otherwise.
|
||||
- Do not reintroduce removed legacy aliases into public types/schema/help/baselines “for convenience”. If old configs still need to load, handle that in `legacy.migrations.*`, config ingest, or `openclaw doctor --fix`.
|
||||
- `hooks.internal.entries` is the canonical public hook config model. `hooks.internal.handlers` is compatibility-only input and must not be re-exposed in public schema/help/baseline surfaces.
|
||||
- Bundled plugin contract boundary:
|
||||
- Public docs: `docs/plugins/architecture.md`, `docs/plugins/manifest.md`, `docs/plugins/sdk-overview.md`
|
||||
- Definition files: `src/plugins/contracts/registry.ts`, `src/plugins/types.ts`, `src/plugins/public-surface-loader.ts`, `src/plugins/public-surface-runtime.ts`, `src/plugins/provider-public-artifacts.ts`, `src/plugins/web-provider-public-artifacts.ts`
|
||||
- Rule: keep manifest metadata, runtime registration, public SDK exports, and contract tests aligned. Do not create a hidden path around the declared plugin interfaces.
|
||||
- Extension test boundary:
|
||||
- Keep extension-owned onboarding/config/provider coverage under the owning bundled plugin package when feasible.
|
||||
- If core tests need bundled plugin behavior, consume it through public `src/plugin-sdk/<id>.ts` facades or the plugin's `api.ts`, not private extension modules.
|
||||
- Shared helpers under `test/helpers/**` are part of that same boundary. Do not hardcode repo-relative `extensions/**` imports there, and do not keep plugin-local deep mocks in shared helpers just because multiple tests use them.
|
||||
- When core tests or shared helpers need bundled plugin public surfaces, use `src/test-utils/bundled-plugin-public-surface.ts` for `api.ts`, `runtime-api.ts`, `contract-api.ts`, `test-api.ts`, plugin entrypoint `index.js`, and resolved module ids for dynamic import or mocking.
|
||||
- If a core test is asserting extension-specific behavior instead of a generic contract, move it to the owning extension package.
|
||||
- Scoped guides still matter:
|
||||
- `extensions/AGENTS.md` expands extension/plugin boundary rules.
|
||||
- `src/channels/AGENTS.md` expands core channel boundary and hot-path rules.
|
||||
- `src/plugin-sdk/AGENTS.md` expands public SDK contract rules.
|
||||
- `src/plugins/AGENTS.md` expands plugin loading, registry, and manifest rules.
|
||||
- `src/gateway/protocol/AGENTS.md` expands typed Gateway protocol rules.
|
||||
- `src/gateway/AGENTS.md` expands Gateway server hot-path and plugin artifact rules.
|
||||
- `src/agents/AGENTS.md` expands agent test/import performance rules.
|
||||
- `test/helpers/AGENTS.md` and `test/helpers/channels/AGENTS.md` expand shared test helper boundary rules.
|
||||
- Plugin architecture direction:
|
||||
- Keep a manifest-first control plane: discovery, validation, enablement, setup hints, and activation planning should stay metadata-driven by default.
|
||||
- Keep runtime execution separate: actual provider/channel/tool execution should resolve through narrow targeted loaders, not broad registry materialization.
|
||||
- Host loads plugins; plugins do not load host internals. Prefer a small versioned host/kernel seam plus documented SDK entrypoints over ambient reachability.
|
||||
- Treat broad runtime registries and mutable global plugin state as transitional compatibility surfaces, not the target architecture.
|
||||
- If a setup or config flow truly needs plugin runtime, make that explicit instead of silently importing runtime code on the cold path.
|
||||
- Core TS: `src/`, `ui/`, `packages/`
|
||||
- Bundled plugins: `extensions/`
|
||||
- Plugin SDK/public contract: `src/plugin-sdk/*`
|
||||
- Core channel internals: `src/channels/*`
|
||||
- Plugin loader/registry/contracts: `src/plugins/*`
|
||||
- Gateway protocol: `src/gateway/protocol/*`
|
||||
- Docs: `docs/`
|
||||
- Apps: `apps/`, `Swabble/`
|
||||
- Installers served from `openclaw.ai`: sibling `../openclaw.ai`
|
||||
|
||||
## Scoped Workflow Guides
|
||||
Scoped guides:
|
||||
|
||||
- `docs/AGENTS.md` owns Mintlify docs, docs links, and docs i18n rules.
|
||||
- `ui/AGENTS.md` owns Control UI i18n and generated locale rules.
|
||||
- `scripts/AGENTS.md` owns script-runner, local-check lock, and test/lint wrapper rules.
|
||||
- `extensions/AGENTS.md`: bundled plugin rules
|
||||
- `src/plugin-sdk/AGENTS.md`: public SDK rules
|
||||
- `src/channels/AGENTS.md`: channel core rules
|
||||
- `src/plugins/AGENTS.md`: plugin loader/registry rules
|
||||
- `src/gateway/AGENTS.md`, `src/gateway/protocol/AGENTS.md`: gateway/protocol rules
|
||||
- `src/agents/AGENTS.md`: agent import/test perf rules
|
||||
- `test/helpers/AGENTS.md`, `test/helpers/channels/AGENTS.md`: shared test helpers
|
||||
- `docs/AGENTS.md`, `ui/AGENTS.md`, `scripts/AGENTS.md`: docs/UI/scripts
|
||||
|
||||
## exe.dev VM ops (general)
|
||||
## Architecture
|
||||
|
||||
- Access: stable path is `ssh exe.dev` then `ssh vm-name` (assume SSH key already set).
|
||||
- SSH flaky: use exe.dev web terminal or Shelley (web agent); keep a tmux session for long ops.
|
||||
- Update: `sudo npm i -g openclaw@latest` (global install needs root on `/usr/lib/node_modules`).
|
||||
- Config: use `openclaw config set ...`; ensure `gateway.mode=local` is set.
|
||||
- Discord: store raw token only (no `DISCORD_BOT_TOKEN=` prefix).
|
||||
- Restart: stop old gateway and run:
|
||||
`pkill -9 -f openclaw-gateway || true; nohup openclaw gateway run --bind loopback --port 18789 --force > /tmp/openclaw-gateway.log 2>&1 &`
|
||||
- Verify: `openclaw channels status --probe`, `ss -ltnp | rg 18789`, `tail -n 120 /tmp/openclaw-gateway.log`.
|
||||
- Core must stay extension-agnostic. No core special cases for bundled plugin/provider/channel ids when manifest/registry/capability contracts can express it.
|
||||
- Extensions cross into core only via `openclaw/plugin-sdk/*`, manifest metadata, injected runtime helpers, and documented local barrels (`api.ts`, `runtime-api.ts`).
|
||||
- Extension production code must not import core `src/**`, `src/plugin-sdk-internal/**`, another extension's `src/**`, or relative paths outside its package.
|
||||
- Core code/tests must not deep-import plugin internals (`extensions/*/src/**`, `onboard.js`). Use plugin `api.ts` / public SDK facade / generic contract.
|
||||
- Extension-owned behavior stays in the extension: legacy repair, detection, onboarding, auth/provider defaults, provider tools/settings.
|
||||
- Legacy config repair: prefer doctor/fix paths over startup/load-time core migrations.
|
||||
- If a core test asserts extension-specific behavior, move it to the owning extension or a generic contract test.
|
||||
- New seams: backwards-compatible, documented, versioned. Third-party plugins exist.
|
||||
- Channels: `src/channels/**` is implementation. Plugin authors get SDK seams, not channel internals.
|
||||
- Providers: core owns generic inference loop; provider plugins own provider-specific auth/catalog/runtime hooks.
|
||||
- Gateway protocol changes are contract changes: additive first; incompatible needs versioning/docs/client follow-through.
|
||||
- Config contract: keep exported types, schema/help, generated metadata, baselines, docs aligned. Retired public keys stay retired; compatibility belongs in raw migration/doctor paths.
|
||||
- Plugin architecture direction: manifest-first control plane; targeted runtime loaders; no hidden paths around declared contracts; broad mutable registries are transitional.
|
||||
- Prompt-cache rule: deterministic ordering for maps/sets/registries/plugin lists/files/network results before model/tool payloads. Preserve old transcript bytes when possible.
|
||||
|
||||
## Build, Test, and Development Commands
|
||||
## Commands
|
||||
|
||||
- Runtime baseline: Node **22+** (keep Node + Bun paths working).
|
||||
- Install deps: `pnpm install`
|
||||
- If deps are missing (for example `node_modules` missing, `vitest not found`, or `command not found`), run the repo’s package-manager install command (prefer lockfile/README-defined PM), then rerun the exact requested command once. Apply this to test/build/lint/typecheck/dev commands; if retry still fails, report the command and first actionable error.
|
||||
- Pre-commit hooks are installed by the package `prepare` script (`git config core.hooksPath git-hooks`). The hook formats/lints staged source files and runs `pnpm check` unless the staged change is docs-only or `FAST_COMMIT=1` is set.
|
||||
- `FAST_COMMIT=1` skips the repo-wide `pnpm check` inside the pre-commit hook only. The hook still runs targeted formatting/linting for staged files and restages formatter changes. Use it when you intentionally want a faster commit path and are running equivalent targeted verification manually. It does not change CI and does not change what `pnpm check` itself does.
|
||||
- Also supported: `bun install` (keep `pnpm-lock.yaml` + Bun patching in sync when touching deps/patches).
|
||||
- Prefer Bun for TypeScript execution (scripts, dev, tests): `bun <file.ts>` / `bunx <tool>`.
|
||||
- Run CLI in dev: `pnpm openclaw ...` (bun) or `pnpm dev`.
|
||||
- Node remains supported for running built output (`dist/*`) and production installs.
|
||||
- Mac packaging (dev): `scripts/package-mac-app.sh` defaults to current arch.
|
||||
- Type-check/build: `pnpm build`
|
||||
- TypeScript checks: `pnpm tsgo`
|
||||
- Lint/format: `pnpm check`
|
||||
- Local agent/dev shells default to host-aware `OPENCLAW_LOCAL_CHECK=1` behavior for `pnpm tsgo` and `pnpm lint`; set `OPENCLAW_LOCAL_CHECK_MODE=throttled` to force the lower-memory profile, `OPENCLAW_LOCAL_CHECK_MODE=full` to keep lock-only behavior, or `OPENCLAW_LOCAL_CHECK=0` in CI/shared runs.
|
||||
- Format check: `pnpm format:check` (oxfmt --check)
|
||||
- Format fix: `pnpm format` or `pnpm format:fix` (oxfmt --write)
|
||||
- Terminology:
|
||||
- "gate" means a verification command or command set that must be green for the decision you are making.
|
||||
- A local dev gate is the fast default loop, usually `pnpm check` plus any scoped test you actually need.
|
||||
- A landing gate is the broader bar before pushing `main`, usually `pnpm check`, `pnpm test`, and `pnpm build` when the touched surface can affect build output, packaging, lazy-loading/module boundaries, or published surfaces.
|
||||
- A CI gate is whatever the relevant workflow enforces for that lane (for example `check`, `check-additional`, `build-smoke`, or release validation).
|
||||
- Local dev gate: prefer `pnpm check` for the normal edit loop. It keeps the repo-architecture policy guards out of the default local loop.
|
||||
- CI architecture gate: `check-additional` enforces architecture and boundary policy guards that are intentionally kept out of the default local loop.
|
||||
- Formatting gate: the pre-commit hook runs targeted formatting on staged source files before `pnpm check`. If you want a repo-wide formatting-only preflight locally, run `pnpm format:check` explicitly.
|
||||
- If you need a fast commit loop, `FAST_COMMIT=1 git commit ...` skips the hook’s repo-wide `pnpm check`; targeted formatting/linting still runs, so use that only when you are deliberately covering the touched surface some other way.
|
||||
- Tests: `pnpm test` (vitest); coverage: `pnpm test:coverage`
|
||||
- Generated baseline drift detection uses SHA-256 hash files under `docs/.generated/` (`.sha256` files tracked in git; full JSON baselines are gitignored, generated locally for inspection).
|
||||
- Config schema drift uses `pnpm config:docs:gen` / `pnpm config:docs:check`.
|
||||
- Plugin SDK API drift uses `pnpm plugin-sdk:api:gen` / `pnpm plugin-sdk:api:check`.
|
||||
- If you change config schema/help or the public Plugin SDK surface, run the matching gen command and commit the updated `.sha256` hash file. Keep the two drift-check flows adjacent in scripts/workflows/docs guidance rather than inventing a third pattern.
|
||||
- When `pnpm tsgo` fails, triage by coherent surface instead of by raw error count: rerun the gate, group failures by package/module/type contract, open the source-of-truth type or export file first, fix the root mismatch, then rerun `pnpm tsgo` before widening into downstream consumers. Check `origin/main` before doing broad cleanup because some apparent type debt is already fixed upstream.
|
||||
- For narrowly scoped changes, prefer narrowly scoped tests that directly validate the touched behavior. If no meaningful scoped test exists, say so explicitly and use the next most direct validation available.
|
||||
- Verification modes for work on `main`:
|
||||
- Default mode: `main` is relatively stable. Count pre-commit hook coverage when it already verified the current tree, avoid rerunning the exact same checks just for ceremony, and prefer keeping CI/main green before landing.
|
||||
- Fast-commit mode: `main` is moving fast and you intentionally optimize for shorter commit loops. Prefer explicit local verification close to the final landing point, and it is acceptable to use `--no-verify` for intermediate or catch-up commits after equivalent checks have already run locally.
|
||||
- Preferred landing bar for pushes to `main`: in Default mode, favor `pnpm check` and `pnpm test` near the final rebase/push point when feasible. In fast-commit mode, verify the touched surface locally near landing without insisting every intermediate commit replay the full hook.
|
||||
- Scoped tests prove the change itself. `pnpm test` remains the default `main` landing bar; scoped tests do not replace full-suite gates by default.
|
||||
- Hard gate: if the change can affect build output, packaging, lazy-loading/module boundaries, or published surfaces, `pnpm build` MUST be run and MUST pass before pushing `main`.
|
||||
- Default rule: do not land changes with failing format, lint, type, build, or required test checks when those failures are caused by the change or plausibly related to the touched surface. Fast-commit mode changes how verification is sequenced; it does not lower the requirement to validate and clean up the touched surface before final landing.
|
||||
- For narrowly scoped changes, if unrelated failures already exist on latest `origin/main`, state that clearly, report the scoped tests you ran, and ask before broadening scope into unrelated fixes or landing despite those failures.
|
||||
- Do not use scoped tests as permission to ignore plausibly related failures.
|
||||
- Runtime: Node 22+. Keep Node and Bun paths working.
|
||||
- Install: `pnpm install` (Bun supported; keep lockfiles/patches aligned if touched).
|
||||
- Dev CLI: `pnpm openclaw ...` or `pnpm dev`.
|
||||
- Build: `pnpm build`
|
||||
- Smart local gate: `pnpm check:changed` (scoped typecheck/lint/guards + relevant tests)
|
||||
- Explain smart gate: `pnpm changed:lanes --json`
|
||||
- Pre-commit view: `pnpm check:changed --staged`
|
||||
- Normal full prod sweep: `pnpm check` (prod typecheck/lint/guards, no tests)
|
||||
- Full tests: `pnpm test`
|
||||
- Changed tests only: `pnpm test:changed`
|
||||
- Extension tests: `pnpm test:extensions` or `pnpm test extensions` = all extension shards; `pnpm test extensions/<id>` = one extension lane. Heavy channels/OpenAI have dedicated shards.
|
||||
- Shard timing artifact: `.artifacts/vitest-shard-timings.json`; auto-used for balanced shard ordering. Disable with `OPENCLAW_TEST_PROJECTS_TIMINGS=0`.
|
||||
- Targeted tests: `pnpm test <path-or-filter> [vitest args...]`; do not call raw `vitest`.
|
||||
- Coverage: `pnpm test:coverage`
|
||||
- Format check/fix: `pnpm format:check` / `pnpm format`
|
||||
- Typecheck:
|
||||
- `pnpm tsgo`: fastest core prod graph
|
||||
- `pnpm tsgo:prod`: core + extensions prod graphs; used by `pnpm check`
|
||||
- `pnpm check:test-types` / `pnpm tsgo:test`: all test graphs
|
||||
- `pnpm tsgo:all`: all prod + test project refs
|
||||
- Debug slices exist; do not present as normal user flow.
|
||||
- Profile: `pnpm tsgo:profile [core-test|extensions-test|--all]`
|
||||
- Type policy: use `tsgo`; do not add `tsc --noEmit`, `typecheck`, or `check:types` lanes. `tsc` only for declaration/package-boundary emit gaps.
|
||||
- Lint:
|
||||
- `pnpm lint`: core/extensions/scripts shards
|
||||
- `pnpm lint:core`, `pnpm lint:extensions`, `pnpm lint:scripts`
|
||||
- `pnpm lint:apps`: Swift/app surface, separate from TS lint
|
||||
- `pnpm lint:all`: legacy comparison lane
|
||||
- Local heavy-check behavior: `OPENCLAW_LOCAL_CHECK=1` default; `OPENCLAW_LOCAL_CHECK_MODE=throttled|full`; `OPENCLAW_LOCAL_CHECK=0` for CI/shared runs.
|
||||
|
||||
## Prompt Cache Stability
|
||||
## Gates
|
||||
|
||||
- Treat prompt-cache stability as correctness/perf-critical, not cosmetic.
|
||||
- Any code that assembles model or tool payloads from maps, sets, registries, plugin lists, MCP catalogs, filesystem reads, or network results must make ordering deterministic before building the request.
|
||||
- Do not rewrite older transcript/history bytes on every turn unless you intentionally want to invalidate the cached prefix. Legacy cleanup, pruning, normalization, and migration logic should preserve recent prompt bytes when possible.
|
||||
- If truncation or compaction is required, prefer mutating newest or tail content first so the cached prefix stays byte-identical for as long as possible.
|
||||
- For cache-sensitive changes, require a regression test that proves turn-to-turn prefix stability or deterministic request assembly; helper-local tests alone are not enough.
|
||||
- Pre-commit hook: staged format/lint, then `pnpm check:changed --staged`; docs/markdown-only skips changed-scope check; `FAST_COMMIT=1` skips changed-scope check only.
|
||||
- Changed lanes:
|
||||
- core prod => core prod typecheck + core tests
|
||||
- core tests => core test typecheck/tests only
|
||||
- extension prod => extension prod typecheck + extension tests
|
||||
- extension tests => extension test typecheck/tests only
|
||||
- public SDK/plugin contract => extension prod/test validation too
|
||||
- unknown root/config => all lanes
|
||||
- Local loop: prefer `pnpm check:changed`; use `pnpm test:changed` for tests only; use `pnpm check` for full prod TS/lint sweep without tests.
|
||||
- Landing on `main`: verify touched surface near landing; default bar is `pnpm check` + `pnpm test` when feasible.
|
||||
- Hard build gate: run/pass `pnpm build` before push if build output, packaging, lazy/module boundaries, or published surfaces can change.
|
||||
- Do not land related failing format/lint/type/build/tests. If failures are unrelated on latest `origin/main`, say so and give scoped proof.
|
||||
- CI architecture gate: `check-additional`; local equivalent `pnpm check:architecture`.
|
||||
- Config docs drift: `pnpm config:docs:gen/check`
|
||||
- Plugin SDK API drift: `pnpm plugin-sdk:api:gen/check`
|
||||
- Generated docs baselines: tracked `docs/.generated/*.sha256`; full JSON ignored.
|
||||
|
||||
## Coding Style & Naming Conventions
|
||||
## Code Style
|
||||
|
||||
- Language: TypeScript (ESM). Prefer strict typing; avoid `any`.
|
||||
- Formatting/linting via Oxlint and Oxfmt.
|
||||
- Never add `@ts-nocheck` and do not add inline lint suppressions by default. Fix root causes first; only keep a suppression when the code is intentionally correct, the rule cannot express that safely, and the comment explains why.
|
||||
- Do not disable `no-explicit-any`; prefer real types, `unknown`, or a narrow adapter/helper instead. Update Oxlint/Oxfmt config only when required.
|
||||
- Prefer `zod` or existing schema helpers at external boundaries such as config, webhook payloads, CLI/JSON output, persisted JSON, and third-party API responses.
|
||||
- Prefer discriminated unions when parameter shape changes runtime behavior.
|
||||
- Prefer `Result<T, E>`-style outcomes and closed error-code unions for recoverable runtime decisions.
|
||||
- Keep human-readable strings for logs, CLI output, and UI; do not use freeform strings as the source of truth for internal branching.
|
||||
- Avoid `?? 0`, empty-string, empty-object, or magic-string sentinels when they can change runtime meaning silently.
|
||||
- If introducing a new optional field or nullable semantic in core logic, prefer an explicit union or dedicated type when the value changes behavior.
|
||||
- New runtime control-flow code should not branch on `error: string` or `reason: string` when a closed code union would be reasonable.
|
||||
- Dynamic import guardrail: do not mix `await import("x")` and static `import ... from "x"` for the same module in production code paths. If you need lazy loading, create a dedicated `*.runtime.ts` boundary (that re-exports from `x`) and dynamically import that boundary from lazy callers only.
|
||||
- Dynamic import verification: after refactors that touch lazy-loading/module boundaries, run `pnpm build` and check for `[INEFFECTIVE_DYNAMIC_IMPORT]` warnings before submitting.
|
||||
- Circular dependencies: keep both `pnpm check:import-cycles` and `pnpm check:madge-import-cycles` green; do not reintroduce runtime import cycles or madge-detected import loops.
|
||||
- Extension SDK self-import guardrail: inside an extension package, do not import that same extension via `openclaw/plugin-sdk/<extension>` from production files. Route internal imports through a local barrel such as `./api.ts` or `./runtime-api.ts`, and keep the `plugin-sdk/<extension>` path as the external contract only.
|
||||
- Extension package boundary guardrail: inside a bundled plugin package, do not use relative imports/exports that resolve outside that same package root. If shared code belongs in the plugin SDK, import `openclaw/plugin-sdk/<subpath>` instead of reaching into `src/plugin-sdk/**` or other repo paths via `../`.
|
||||
- Extension API surface rule: `openclaw/plugin-sdk/<subpath>` is the only public cross-package contract for extension-facing SDK code. If an extension needs a new seam, add a public subpath first; do not reach into `src/plugin-sdk/**` by relative path.
|
||||
- Never share class behavior via prototype mutation (`applyPrototypeMixins`, `Object.defineProperty` on `.prototype`, or exporting `Class.prototype` for merges). Use explicit inheritance/composition (`A extends B extends C`) or helper composition so TypeScript can typecheck.
|
||||
- If this pattern is needed, stop and get explicit approval before shipping; default behavior is to split/refactor into an explicit class hierarchy and keep members strongly typed.
|
||||
- In tests, prefer per-instance stubs over prototype mutation (`SomeClass.prototype.method = ...`) unless a test explicitly documents why prototype-level patching is required.
|
||||
- Add brief code comments for tricky or non-obvious logic.
|
||||
- Keep files concise; extract helpers instead of “V2” copies. Use existing patterns for CLI options and dependency injection via `createDefaultDeps`.
|
||||
- Aim to keep files under ~700 LOC; guideline only (not a hard guardrail). Split/refactor when it improves clarity or testability.
|
||||
- Naming: use **OpenClaw** for product/app/docs headings; use `openclaw` for CLI command, package/binary, paths, and config keys.
|
||||
- Written English: use American spelling and grammar in code, comments, docs, and UI strings (e.g. "color" not "colour", "behavior" not "behaviour", "analyze" not "analyse").
|
||||
- TypeScript ESM. Strict types. Avoid `any`; prefer real types/`unknown`/narrow adapters.
|
||||
- No `@ts-nocheck`. No lint suppressions unless intentional and explained.
|
||||
- External boundaries: prefer `zod` or existing schema helpers.
|
||||
- Runtime branching: prefer discriminated unions / closed codes over freeform strings.
|
||||
- Avoid magic sentinels like `?? 0`, empty object/string when semantics change.
|
||||
- Dynamic import: do not mix static and dynamic import for same module in prod path. Use dedicated `*.runtime.ts` lazy boundary. After lazy-boundary edits, run `pnpm build` and check `[INEFFECTIVE_DYNAMIC_IMPORT]`.
|
||||
- Cycles: keep `pnpm check:import-cycles` and architecture/madge cycle checks green.
|
||||
- Classes: no prototype mixins/mutations. Use explicit inheritance/composition. Tests prefer per-instance stubs.
|
||||
- Comments: brief only for non-obvious logic.
|
||||
- File size: split around ~700 LOC when it improves clarity/testability.
|
||||
- Product naming: **OpenClaw** product/docs; `openclaw` CLI/package/path/config.
|
||||
- Written English: American spelling.
|
||||
|
||||
## Release / Advisory Workflows
|
||||
## Tests
|
||||
|
||||
- Use `$openclaw-release-maintainer` at `.agents/skills/openclaw-release-maintainer/SKILL.md` for release naming, version coordination, release auth, and changelog-backed release-note workflows.
|
||||
- Use `$openclaw-ghsa-maintainer` at `.agents/skills/openclaw-ghsa-maintainer/SKILL.md` for GHSA advisory inspection, patch/publish flow, private-fork checks, and GHSA API validation.
|
||||
- Release and publish remain explicit-approval actions even when using the skill.
|
||||
- Vitest. Tests colocated `*.test.ts`; e2e `*.e2e.test.ts`.
|
||||
- Example models in tests: `sonnet-4.6`, `gpt-5.4`.
|
||||
- Clean up timers/env/globals/mocks/sockets/temp dirs/module state; `--isolate=false` must stay safe.
|
||||
- Hot tests: avoid per-test `vi.resetModules()` + fresh heavy imports; prefer static or `beforeAll` imports and reset state directly.
|
||||
- Measure first: `pnpm test:perf:imports <file>` for import drag; `pnpm test:perf:hotspots --limit N` for suite targets.
|
||||
- Keep tests at seam depth: unit-test pure helpers/contracts; one integration smoke per boundary, not per branch.
|
||||
- Mock expensive runtime seams directly: scanners, manifests, package registries, filesystem crawls, provider SDKs, network/process launch.
|
||||
- Prefer injected deps over module mocks; if mocking modules, mock narrow local `*.runtime.ts` seams, not broad barrels.
|
||||
- Share fixtures/builders; do not recreate temp dirs, package manifests, or plugin workspaces in every case unless state isolation needs it.
|
||||
- Delete duplicate assertions when another test owns the boundary; assert only the behavior that can regress here.
|
||||
- Avoid broad `importOriginal()` / broad `openclaw/plugin-sdk/*` partial mocks in hot tests. Add narrow local `*.runtime.ts` seam and mock it.
|
||||
- Use existing deps/callback/runtime injection seams before module mocks.
|
||||
- Import-dominated test time is a boundary smell; shrink import surface before adding cases.
|
||||
- Replacing slow integration coverage: extract production composition into a named helper and test that helper.
|
||||
- Do not modify baseline/inventory/ignore/snapshot/expected-failure files to silence checks without explicit approval.
|
||||
- Do not set test workers above 16. For memory pressure: `OPENCLAW_VITEST_MAX_WORKERS=1 pnpm test`.
|
||||
- Live: `OPENCLAW_LIVE_TEST=1 pnpm test:live`; full logs `OPENCLAW_LIVE_TEST_QUIET=0`.
|
||||
- Full testing guide: `docs/help/testing.md`.
|
||||
|
||||
## Testing Guidelines
|
||||
## Docs / Changelog
|
||||
|
||||
- Framework: Vitest with V8 coverage thresholds (70% lines/branches/functions/statements).
|
||||
- Naming: match source names with `*.test.ts`; e2e in `*.e2e.test.ts`.
|
||||
- When tests need example Anthropic/OpenAI model constants, prefer `sonnet-4.6` and `gpt-5.4`; update older Anthropic/GPT examples when you touch those tests.
|
||||
- Run `pnpm test` (or `pnpm test:coverage`) before pushing when you touch logic.
|
||||
- Write tests to clean up timers, env, globals, mocks, sockets, temp dirs, and module state so `--isolate=false` stays green.
|
||||
- Test performance guardrail: do not put `vi.resetModules()` plus `await import(...)` in `beforeEach`/per-test loops for heavy modules unless module state truly requires it. Prefer static imports or one-time `beforeAll` imports, then reset mocks/runtime state directly.
|
||||
- Test performance guardrail: if a test file uses stable `vi.mock(...)` hoists or other static module mocks, do not pair them with `vi.resetModules()` and a fresh `await import(...)` in every `beforeEach`. Import the heavy module once in `beforeAll`, then reset/prime mocks in `beforeEach` so Browser/Matrix-style hotspot tests do not pay the module graph cost per case.
|
||||
- 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.
|
||||
- Test performance guardrail: when replacing a slow integration test with helper-level coverage, extract the exact production composition into a named helper and test that helper. Do not trade coverage shape for speed without preserving the behavior proof somewhere cheaper.
|
||||
- Test performance guardrail: for plugin-owned static descriptors used by core tests or cold paths, prefer lightweight public artifacts with full-runtime fallback over loading broad bundled plugin barrels.
|
||||
- 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, use the native root-project entrypoint: `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 the repo's default config/profile/pool routing.
|
||||
- Do not set test workers above 16; tried already.
|
||||
- Vitest now defaults to native root-project `threads`, with hard `forks` exceptions for `gateway`, `agents`, and `commands`. Keep new pool changes explicit and justified; use `OPENCLAW_VITEST_POOL=forks` for full local fork debugging.
|
||||
- If local Vitest runs cause memory pressure, the default worker budget now derives from host capabilities (CPU, memory band, current load). For a conservative explicit override during land/gate runs, use `OPENCLAW_VITEST_MAX_WORKERS=1 pnpm test`.
|
||||
- Live tests (real keys): `OPENCLAW_LIVE_TEST=1 pnpm test:live` (OpenClaw-only) or `LIVE=1 pnpm test:live` (includes provider live tests). Docker: `pnpm test:docker:live-models`, `pnpm test:docker:live-gateway`. Onboarding Docker E2E: `pnpm test:docker:onboard`.
|
||||
- `pnpm test:live` defaults quiet now. Keep `[live]` progress; suppress profile/gateway chatter. Full logs: `OPENCLAW_LIVE_TEST_QUIET=0 pnpm test:live`.
|
||||
- Full kit + what’s covered: `docs/help/testing.md`.
|
||||
- Changelog: user-facing changes only; no internal/meta notes (version alignment, appcast reminders, release process).
|
||||
- Changelog placement: in the active version block, append new entries to the end of the target section (`### Changes` or `### Fixes`); do not insert new entries at the top of a section.
|
||||
- Changelog attribution: use at most one contributor mention per line; prefer `Thanks @author` and do not also add `by @author` on the same entry.
|
||||
- Pure test additions/fixes generally do **not** need a changelog entry unless they alter user-facing behavior or the user asks for one.
|
||||
- Mobile: before using a simulator, check for connected real devices (iOS + Android) and prefer them when available.
|
||||
- Update docs when behavior/API changes. Use docs list/read_when hints.
|
||||
- Docs links: see `docs/AGENTS.md`.
|
||||
- Changelog: user-facing only. Pure test/internal changes usually no entry.
|
||||
- Changelog placement: append to active version `### Changes`/`### Fixes`; at most one contributor mention, prefer `Thanks @user`.
|
||||
|
||||
## Commit & Pull Request Guidelines
|
||||
## Git
|
||||
|
||||
- Use `$openclaw-pr-maintainer` at `.agents/skills/openclaw-pr-maintainer/SKILL.md` for maintainer PR triage, review, close, search, and landing workflows.
|
||||
- This includes auto-close labels, bug-fix evidence gates, GitHub comment/search footguns, and maintainer PR decision flow.
|
||||
- For the repo's end-to-end maintainer PR workflow, use `$openclaw-pr-maintainer` at `.agents/skills/openclaw-pr-maintainer/SKILL.md`.
|
||||
- Use `scripts/committer "<msg>" <file...>`; stage only intended files.
|
||||
- Commits: conventional-ish, concise/action-oriented. Group related changes.
|
||||
- No manual stash/autostash unless explicitly requested. No branch/worktree changes unless requested.
|
||||
- No merge commits on `main`; rebase on latest `origin/main` before push.
|
||||
- User says "commit": commit your changes only. "commit all": commit everything in grouped chunks. "push": may `git pull --rebase` first.
|
||||
- Do not delete/rename unexpected files; ask if it blocks. Otherwise ignore unrelated WIP.
|
||||
- If bulk PR close/reopen affects >5 PRs, ask with exact count/scope.
|
||||
- PR/issue workflows: use `$openclaw-pr-maintainer`.
|
||||
- `/landpr`: use `~/.codex/prompts/landpr.md`.
|
||||
|
||||
- `/landpr` lives in the global Codex prompts (`~/.codex/prompts/landpr.md`); when landing or merging any PR, always follow that `/landpr` process.
|
||||
- Create commits with `scripts/committer "<msg>" <file...>`; avoid manual `git add`/`git commit` so staging stays scoped.
|
||||
- Follow concise, action-oriented commit messages (e.g., `CLI: add verbose flag to send`).
|
||||
- Group related changes; avoid bundling unrelated refactors.
|
||||
- PR submission template (canonical): `.github/pull_request_template.md`
|
||||
- Issue submission templates (canonical): `.github/ISSUE_TEMPLATE/`
|
||||
## Security / Release
|
||||
|
||||
## Git Notes
|
||||
- Never commit real phone numbers, videos, credentials, live config.
|
||||
- Secrets: channel/provider credentials under `~/.openclaw/credentials/`; model auth profiles under `~/.openclaw/agents/<agentId>/agent/auth-profiles.json`.
|
||||
- Env keys: check `~/.profile`.
|
||||
- Dependency patches/overrides/vendor changes require explicit approval. `pnpm.patchedDependencies` must use exact versions.
|
||||
- Carbon pins owner-only: do not change `@buape/carbon` versions unless Shadow (`@thewilloftheshadow`, verified by `gh`) asks.
|
||||
- Releases/publish/version bumps require explicit approval.
|
||||
- Release docs: `docs/reference/RELEASING.md`; use `$openclaw-release-maintainer`.
|
||||
- GHSA/advisories: use `$openclaw-ghsa-maintainer`.
|
||||
- Beta tag/version must match, e.g. `vYYYY.M.D-beta.N` => npm `YYYY.M.D-beta.N --tag beta`.
|
||||
|
||||
- If `git branch -d/-D <branch>` is policy-blocked, delete the local ref directly: `git update-ref -d refs/heads/<branch>`.
|
||||
- Agents MUST NOT create or push merge commits on `main`. If `main` has advanced, rebase local commits onto the latest `origin/main` before pushing.
|
||||
- Bulk PR close/reopen safety: if a close action would affect more than 5 PRs, first ask for explicit user confirmation with the exact PR count and target scope/query.
|
||||
## Apps / Platform
|
||||
|
||||
## Security & Configuration Tips
|
||||
- Before simulator/emulator testing, check connected real iOS/Android devices first.
|
||||
- "restart iOS/Android apps" = rebuild/reinstall/relaunch, not kill/launch.
|
||||
- SwiftUI: prefer Observation (`@Observable`, `@Bindable`) over new `ObservableObject`.
|
||||
- mac gateway: use app or `openclaw gateway restart/status --deep`; avoid ad-hoc tmux gateway sessions. Rebuild mac app locally, not over SSH.
|
||||
- mac logs: `./scripts/clawlog.sh`.
|
||||
- Version bump touches: `package.json`, `apps/android/app/build.gradle.kts`, `apps/ios/version.json` then `pnpm ios:version:sync`, `apps/macos/.../Info.plist`, `docs/install/updating.md`. Appcast only for Sparkle release.
|
||||
- iOS Team ID: `security find-identity -p codesigning -v`; fallback `defaults read com.apple.dt.Xcode IDEProvisioningTeamIdentifiers`.
|
||||
- Mobile LAN pairing: plaintext `ws://` is loopback-only by default. Trusted private-network `ws://` needs `OPENCLAW_ALLOW_INSECURE_PRIVATE_WS=1`; Tailscale/public use `wss://` or a tunnel.
|
||||
- A2UI hash `src/canvas-host/a2ui/.bundle.hash`: generated; ignore unless running `pnpm canvas:a2ui:bundle`; commit separately.
|
||||
|
||||
- Channel/provider state lives under `~/.openclaw/credentials/`; rerun `openclaw channels login` if logged out. Model auth profiles live under `~/.openclaw/agents/<agentId>/agent/auth-profiles.json`; legacy OAuth import still reads `~/.openclaw/credentials/oauth.json`.
|
||||
- Pi sessions live under `~/.openclaw/agents/<agentId>/sessions/` by default; `session.store` can override the session store path.
|
||||
- Environment variables: see `~/.profile`.
|
||||
- Never commit or publish real phone numbers, videos, or live configuration values. Use obviously fake placeholders in docs, tests, and examples.
|
||||
- Release flow: use the private [maintainer release docs](https://github.com/openclaw/maintainers/blob/main/release/README.md) for the actual runbook, `docs/reference/RELEASING.md` for the public release policy, and `$openclaw-release-maintainer` for the maintainership workflow.
|
||||
## External Ops
|
||||
|
||||
## Local Runtime / Platform Notes
|
||||
- Remote install docs: `docs/install/exe-dev.md`, `docs/install/fly.md`, `docs/install/hetzner.md`.
|
||||
- Parallels smoke: `$openclaw-parallels-smoke`; Discord roundtrip: `parallels-discord-roundtrip`.
|
||||
|
||||
- Vocabulary: "makeup" = "mac app".
|
||||
- Rebrand/migration issues or legacy config/service warnings: run `openclaw doctor` (see `docs/gateway/doctor.md`).
|
||||
- Use `$openclaw-parallels-smoke` at `.agents/skills/openclaw-parallels-smoke/SKILL.md` for Parallels smoke, rerun, upgrade, debug, and result-interpretation workflows across macOS, Windows, and Linux guests.
|
||||
- For the macOS Discord roundtrip deep dive, use the narrower `.agents/skills/parallels-discord-roundtrip/SKILL.md` companion skill.
|
||||
- Never edit `node_modules` (global/Homebrew/npm/git installs too). Updates overwrite. Skill notes go in `tools.md` or `AGENTS.md`.
|
||||
- If you need local-only `.agents` ignores, use `.git/info/exclude` instead of repo `.gitignore`.
|
||||
- When adding a new `AGENTS.md` anywhere in the repo, also add a `CLAUDE.md` symlink pointing to it (example: `ln -s AGENTS.md CLAUDE.md`).
|
||||
- Signal: "update fly" => `fly ssh console -a flawd-bot -C "bash -lc 'cd /data/clawd/openclaw && git pull --rebase origin main'"` then `fly machines restart e825232f34d058 -a flawd-bot`.
|
||||
- CLI progress: use `src/cli/progress.ts` (`osc-progress` + `@clack/prompts` spinner); don’t hand-roll spinners/bars.
|
||||
- Status output: keep tables + ANSI-safe wrapping (`src/terminal/table.ts`); `status --all` = read-only/pasteable, `status --deep` = probes.
|
||||
- Gateway may run as an app-managed launchd job. Restart the gateway via the app or `openclaw gateway restart`; inspect with `openclaw gateway status --deep` or, for the default profile, `launchctl print gui/$UID/ai.openclaw.gateway`. Use `scripts/restart-mac.sh` when you need to rebuild/relaunch the local macOS app itself. The app LaunchAgent uses `ai.openclaw.mac`. **When debugging on macOS, start/stop the gateway via the app or gateway CLI, not ad-hoc tmux sessions; kill any temporary tunnels before handoff.**
|
||||
- macOS logs: use `./scripts/clawlog.sh` to query unified logs for the OpenClaw subsystem; it supports follow/tail/category filters and expects passwordless sudo for `/usr/bin/log`.
|
||||
- If shared guardrails are available locally, review them; otherwise follow this repo's guidance.
|
||||
- SwiftUI state management (iOS/macOS): prefer the `Observation` framework (`@Observable`, `@Bindable`) over `ObservableObject`/`@StateObject`; don’t introduce new `ObservableObject` unless required for compatibility, and migrate existing usages when touching related code.
|
||||
- Connection providers: when adding a new connection, update every UI surface and docs (macOS app, web UI, mobile if applicable, onboarding/overview docs) and add matching status + configuration forms so provider lists and settings stay in sync.
|
||||
- Version locations: `package.json` (CLI), `apps/android/app/build.gradle.kts` (versionName/versionCode), `apps/ios/version.json` (source for generated iOS config and Fastlane metadata), `apps/macos/Sources/OpenClaw/Resources/Info.plist` (CFBundleShortVersionString/CFBundleVersion), and `docs/install/updating.md` (pinned npm version).
|
||||
- "Bump version everywhere" means all version locations above, then run `pnpm ios:version:sync` for iOS generated outputs. Only touch appcast metadata when cutting a new macOS Sparkle release.
|
||||
- **Restart apps:** “restart iOS/Android apps” means rebuild (recompile/install) and relaunch, not just kill/launch.
|
||||
- **Device checks:** before testing, verify connected real devices (iOS/Android) before reaching for simulators/emulators.
|
||||
- Mobile pairing: `ws://` (cleartext) is allowed for private LAN addresses (RFC 1918, link-local, mDNS `.local`) and loopback. Private LAN hosts typically lack PKI-backed identity, so requiring TLS there adds complexity without meaningful security gain. `wss://` is required for Tailscale and public endpoints.
|
||||
- Security report scope: reports that treat cleartext `ws://` mobile pairing over private LAN as a vulnerability are out of scope unless they demonstrate a trust-boundary bypass beyond passive network observation on the same LAN.
|
||||
- iOS Team ID lookup: `security find-identity -p codesigning -v` → use Apple Development (…) TEAMID. Fallback: `defaults read com.apple.dt.Xcode IDEProvisioningTeamIdentifiers`.
|
||||
- A2UI bundle hash: `src/canvas-host/a2ui/.bundle.hash` is auto-generated; ignore unexpected changes, and only regenerate via `pnpm canvas:a2ui:bundle` (or `scripts/bundle-a2ui.sh`) when needed. Commit the hash as a separate commit.
|
||||
- Release signing/notary credentials are managed outside the repo; maintainers keep that setup in the private [maintainer release docs](https://github.com/openclaw/maintainers/tree/main/release).
|
||||
- Lobster palette: use the shared CLI palette in `src/terminal/palette.ts` (no hardcoded colors); apply palette to onboarding/config prompts and other TTY UI output as needed.
|
||||
- When asked to open a “session” file, open the Pi session logs under `~/.openclaw/agents/<agentId>/sessions/*.jsonl` (use the `agent=<id>` value in the Runtime line of the system prompt; newest unless a specific ID is given), not the default `sessions.json`. If logs are needed from another machine, SSH via Tailscale and read the same path there.
|
||||
- Do not rebuild the macOS app over SSH; rebuilds must be run directly on the Mac.
|
||||
- Voice wake forwarding tips:
|
||||
- Command template should stay `openclaw-mac agent --message "${text}" --thinking low`; `VoiceWakeForwarder` already shell-escapes `${text}`. Don’t add extra quotes.
|
||||
- launchd PATH is minimal; ensure the app’s launch agent PATH includes standard system paths plus your pnpm bin (typically `$HOME/Library/pnpm`) so `pnpm`/`openclaw` binaries resolve when invoked via `openclaw-mac`.
|
||||
## Misc Footguns
|
||||
|
||||
## Collaboration / Safety Notes
|
||||
|
||||
- When working on a GitHub Issue or PR, print the full URL at the end of the task.
|
||||
- When answering questions, respond with high-confidence answers only: verify in code; do not guess.
|
||||
- Carbon version edits are owner-only: do not change `@buape/carbon` version pins unless you are Shadow (@thewilloftheshadow) as verified by gh.
|
||||
- Any dependency with `pnpm.patchedDependencies` must use an exact version (no `^`/`~`).
|
||||
- Patching dependencies (pnpm patches, overrides, or vendored changes) requires explicit approval; do not do this by default.
|
||||
- **Multi-agent safety:** do **not** create/apply/drop `git stash` entries unless explicitly requested (this includes `git pull --rebase --autostash`). Assume other agents may be working; keep unrelated WIP untouched and avoid cross-cutting state changes.
|
||||
- **Multi-agent safety:** when the user says "push", you may `git pull --rebase` to integrate latest changes (never discard other agents' work). When the user says "commit", scope to your changes only. When the user says "commit all", commit everything in grouped chunks.
|
||||
- **Multi-agent safety:** prefer grouped `commit` / `pull --rebase` / `push` cycles for related work instead of many tiny syncs.
|
||||
- **Multi-agent safety:** do **not** create/remove/modify `git worktree` checkouts (or edit `.worktrees/*`) unless explicitly requested.
|
||||
- **Multi-agent safety:** do **not** switch branches / check out a different branch unless explicitly requested.
|
||||
- **Multi-agent safety:** running multiple agents is OK as long as each agent has its own session.
|
||||
- **Multi-agent safety:** when you see unrecognized files, keep going; focus on your changes and commit only those.
|
||||
- Lint/format churn:
|
||||
- If staged+unstaged diffs are formatting-only, auto-resolve without asking.
|
||||
- If commit/push already requested, auto-stage and include formatting-only follow-ups in the same commit (or a tiny follow-up commit if needed), no extra confirmation.
|
||||
- Only ask when changes are semantic (logic/data/behavior).
|
||||
- **Multi-agent safety:** focus reports on your edits; avoid guard-rail disclaimers unless truly blocked; when multiple agents touch the same file, continue if safe; end with a brief “other files present” note only if relevant.
|
||||
- Bug investigations: read source code of relevant npm dependencies and all related local code before concluding; aim for high-confidence root cause.
|
||||
- Code style: add brief comments for tricky logic; keep files under ~700 LOC when feasible (split/refactor as needed).
|
||||
- Tool schema guardrails (google-antigravity): avoid `Type.Union` in tool input schemas; no `anyOf`/`oneOf`/`allOf`. Use `stringEnum`/`optionalStringEnum` (Type.Unsafe enum) for string lists, and `Type.Optional(...)` instead of `... | null`. Keep top-level tool schema as `type: "object"` with `properties`.
|
||||
- Tool schema guardrails: avoid raw `format` property names in tool schemas; some validators treat `format` as a reserved keyword and reject the schema.
|
||||
- Never send streaming/partial replies to external messaging surfaces (WhatsApp, Telegram); only final replies should be delivered there. Streaming/tool events may still go to internal UIs/control channel.
|
||||
- For manual `openclaw message send` messages that include `!`, use the heredoc pattern noted below to avoid the Bash tool’s escaping.
|
||||
- Release guardrails: do not change version numbers without operator’s explicit consent; always ask permission before running any npm publish/release step.
|
||||
- Beta release guardrail: when using a beta Git tag (for example `vYYYY.M.D-beta.N`), publish npm with a matching beta version suffix (for example `YYYY.M.D-beta.N`) rather than a plain version on `--tag beta`; otherwise the plain version name gets consumed/blocked.
|
||||
- Rebrand/migration/config warnings: run `openclaw doctor`.
|
||||
- Never edit `node_modules`.
|
||||
- Local-only `.agents` ignores: use `.git/info/exclude`, not repo `.gitignore`.
|
||||
- CLI progress: use `src/cli/progress.ts`; status tables: `src/terminal/table.ts`.
|
||||
- Connection/provider additions: update all UI surfaces + docs + status/config forms.
|
||||
- Provider-facing tool schemas: prefer flat string enum helpers over `Type.Union([Type.Literal(...)])`; some providers reject generated `anyOf`. Do not treat this as a repo-wide protocol/schema ban.
|
||||
- External messaging surfaces: no token-delta channel messages. Follow `docs/concepts/streaming.md`; preview/block streaming uses message edits/chunks and must preserve final/fallback delivery.
|
||||
|
||||
157
CHANGELOG.md
157
CHANGELOG.md
@@ -2,33 +2,171 @@
|
||||
|
||||
Docs: https://docs.openclaw.ai
|
||||
|
||||
## Unreleased
|
||||
## 2026.4.20
|
||||
|
||||
### Changes
|
||||
|
||||
- Onboard/wizard: restyle the setup security disclaimer with a single yellow warning banner, section headings and bulleted checklists, and un-dim the note body so key guidance is easy to scan; add a loading spinner during the initial model catalog load so the wizard no longer goes blank while it runs; add an "API key" placeholder to provider API key prompts. (#69553) Thanks @Patrick-Erichsen.
|
||||
- Agents/prompts: strengthen the default system prompt and OpenAI GPT-5 overlay with clearer completion bias, live-state checks, weak-result recovery, and verification-before-final guidance.
|
||||
- Models/costs: support tiered model pricing from cached catalogs and configured models, and include bundled Moonshot Kimi K2.6/K2.5 cost estimates for token-usage reports. (#67605) Thanks @sliverp.
|
||||
- Sessions/Maintenance: enforce the built-in entry cap and age prune by default, and prune oversized stores at load time so accumulated cron/executor session backlogs cannot OOM the gateway before the write path runs. (#69404) Thanks @bobrenze-bot.
|
||||
- Plugins/tests: reuse plugin loader alias and Jiti config resolution across repeated same-context loads, reducing import-heavy test overhead. (#69316) Thanks @amknight.
|
||||
- Cron: split runtime execution state into `jobs-state.json` so `jobs.json` stays stable for git-tracked job definitions. (#63105) Thanks @Feelw00.
|
||||
- Agents/compaction: send opt-in start and completion notices during context compaction. (#67830) Thanks @feniix.
|
||||
- Moonshot/Kimi: default bundled Moonshot setup, web search, and media-understanding surfaces to `kimi-k2.6` while keeping `kimi-k2.5` available for compatibility. (#69477) Thanks @scoootscooob.
|
||||
- Moonshot/Kimi: allow `thinking.keep = "all"` on `moonshot/kimi-k2.6`, and strip it for other Moonshot models or requests where pinned `tool_choice` disables thinking. (#68816) Thanks @aniaan.
|
||||
- BlueBubbles/groups: forward per-group `systemPrompt` config into inbound context `GroupSystemPrompt` so configured group-specific behavioral instructions (for example threaded-reply and tapback conventions) are injected on every turn. Supports `"*"` wildcard fallback matching the existing `requireMention` pattern. Closes #60665. (#69198) Thanks @omarshahine.
|
||||
- Plugins/tasks: add a detached runtime registration contract so plugin executors can own detached task lifecycle and cancellation without reaching into core task internals. (#68915) Thanks @mbelinky.
|
||||
- Terminal/logging: optimize `sanitizeForLog()` by replacing the iterative control-character stripping loop with a single regex pass while preserving the existing ANSI-first sanitization behavior. (#67205) Thanks @bulutmuf.
|
||||
- QA/CI: make `openclaw qa suite` and `openclaw qa telegram` fail by default when scenarios fail, add `--allow-failures` for artifact-only runs, and tighten live-lane defaults for CI automation. (#69122) Thanks @joshavant.
|
||||
- Mattermost: stream thinking, tool activity, and partial reply text into a single draft preview post that finalizes in place when safe. (#47838) thanks @ninjaa.
|
||||
|
||||
### Fixes
|
||||
|
||||
- Exec/YOLO: stop rejecting gateway-host exec in `security=full` plus `ask=off` mode via the Python/Node script preflight hardening path, so promptless YOLO exec once again runs direct interpreter stdin and heredoc forms such as `node <<'NODE' ... NODE`.
|
||||
- OpenAI Codex: normalize legacy `openai-completions` transport overrides on default OpenAI/Codex and GitHub Copilot-compatible hosts back to the native Codex Responses transport while leaving custom proxies untouched. (#45304, #42194) Thanks @dyss1992 and @DeadlySilent.
|
||||
- Anthropic/plugins: scope Anthropic `api: "anthropic-messages"` defaulting to Anthropic-owned providers, so `openai-codex` and other providers without an explicit `api` no longer get rewritten to the wrong transport. Fixes #64534.
|
||||
- fix(qqbot): add SSRF guard to direct-upload URL paths in uploadC2CMedia and uploadGroupMedia [AI-assisted]. (#69595) Thanks @pgondhi987.
|
||||
- fix(gateway): enforce allowRequestSessionKey gate on template-rendered mapping sessionKeys. (#69381) Thanks @pgondhi987.
|
||||
- Browser/Chrome MCP: surface `DevToolsActivePort` attach failures as browser-connectivity errors instead of a generic "waiting for tabs" timeout, and point signed-out fallbacks toward the managed `openclaw` profile.
|
||||
- Webchat/images: treat inline image attachments as media for empty-turn gating while still ignoring metadata-only blank turns. (#69474) Thanks @Jaswir.
|
||||
- Discord/think: only show `adaptive` in `/think` autocomplete for provider/model pairs that actually support provider-managed adaptive thinking, so GPT/OpenAI models no longer advertise an Anthropic-only option.
|
||||
- Thinking: only expose `max` for models that explicitly support provider max reasoning, and remap stored `max` settings to the largest supported thinking mode when users switch to another model.
|
||||
- Thinking/UI: drive `/think` options and chat/Sessions pickers from provider-owned thinking profiles, so custom model level sets such as binary `on/off`, Gemini 3 Pro `off/low/high`, Anthropic `adaptive/max`, and OpenAI `xhigh` stay in one runtime contract.
|
||||
- Gateway/usage: bound the cost usage cache with FIFO eviction so date/range lookups cannot grow unbounded. (#68842) Thanks @Feelw00.
|
||||
- OpenAI/Responses: resolve `/think` levels against each GPT model's supported reasoning efforts so `/think off` no longer becomes high reasoning or sends unsupported `reasoning.effort: "none"` payloads.
|
||||
- Lobster/TaskFlow: allow managed approval resumes to use `approvalId` without a resume token, and persist that id in approval wait state. (#69559) Thanks @kirkluokun.
|
||||
- Plugins/startup: install bundled runtime dependencies into each plugin's own runtime directory, reuse source-checkout repair caches after rebuilds, and log only packages that were actually installed so repeated Gateway starts stay quiet once deps are present.
|
||||
- Plugins/startup: ignore pnpm's `npm_execpath` when repairing bundled plugin runtime dependencies and skip workspace-only package specs so npm-only install flags or local workspace links do not break packaged plugin startup.
|
||||
- MCP: block interpreter-startup env keys such as `NODE_OPTIONS` for stdio servers while preserving ordinary credential and proxy env vars. (#69540) Thanks @drobison00.
|
||||
- Agents/shell: ignore non-interactive placeholder shells like `/usr/bin/false` and `/sbin/nologin`, falling back to `sh` so service-user exec runs no longer exit immediately. (#69308) Thanks @sk7n4k3d.
|
||||
- Setup/TUI: relaunch the setup hatch TUI in a fresh process while preserving the configured gateway target and auth source, so onboarding recovers terminal state cleanly without exposing gateway secrets on command-line args. (#69524) Thanks @shakkernerd.
|
||||
- Codex: avoid re-exposing the image-generation tool on native vision turns with inbound images, and keep bare image-model overrides on the configured image provider. (#65061) Thanks @zhulijin1991.
|
||||
- Sessions/reset: clear auto-sourced model, provider, and auth-profile overrides on `/new` and `/reset` while preserving explicit user selections, so channel sessions stop staying pinned to runtime fallback choices. (#69419) Thanks @sk7n4k3d.
|
||||
- Sessions/costs: snapshot `estimatedCostUsd` like token counters so repeated persist paths no longer compound the same run cost by up to dozens of times. (#69403) Thanks @MrMiaigi.
|
||||
- OpenAI Codex: route ChatGPT/Codex OAuth Responses requests through the `/backend-api/codex` endpoint so `openai-codex/gpt-5.4` no longer hits the removed `/backend-api/responses` alias. (#69336) Thanks @mzogithub.
|
||||
- OpenAI/Responses: omit disabled reasoning payloads when `/think off` is active, so GPT reasoning models no longer receive unsupported `reasoning.effort: "none"` requests. (#61982) Thanks @a-tokyo.
|
||||
- Gateway/pairing: treat loopback shared-secret node-host, TUI, and gateway clients as local for pairing decisions, so trusted local tools no longer reconnect as remote clients and fail with `pairing required`. (#69431) Thanks @SARAMALI15792.
|
||||
- Active Memory: degrade gracefully when memory recall fails during prompt building, logging a warning and letting the reply continue without memory context instead of failing the whole turn. (#69485) Thanks @Magicray1217.
|
||||
- Ollama: add provider-policy defaults for `baseUrl` and `models` so implicit local discovery can run before config validation rejects a minimal Ollama provider config. (#69370) Thanks @PratikRai0101.
|
||||
- Agents/model selection: clear transient auto-failover session overrides before each turn so recovered primary models are retried immediately without emitting user-override reset warnings. (#69365) Thanks @hitesh-github99.
|
||||
- Auto-reply: apply silent `NO_REPLY` policy per conversation type, so direct chats get a helpful rewritten reply while groups and internal deliveries can remain quiet. (#68644) Thanks @Takhoffman.
|
||||
- Telegram/status reactions: honor `messages.removeAckAfterReply` when lifecycle status reactions are enabled, clearing or restoring the reaction after success/error using the configured hold timings. (#68067) Thanks @poiskgit.
|
||||
- Web search/plugins: resolve plugin-scoped SecretRef API keys for bundled Exa, Firecrawl, Gemini, Kimi, Perplexity, Tavily, and Grok web-search providers when they are selected through the shared web-search config. (#68424) Thanks @afurm.
|
||||
- Telegram/polling: raise the default polling watchdog threshold from 90s to 120s and add configurable `channels.telegram.pollingStallThresholdMs` (also per-account) so long-running Telegram work gets more room before polling is treated as stalled. (#57737) Thanks @Vitalcheffe.
|
||||
- Telegram/polling: bound the persisted-offset confirmation `getUpdates` probe with a client-side timeout so a zombie socket cannot hang polling recovery before the runner watchdog starts. (#50368) Thanks @boticlaw.
|
||||
- Agents/Pi runner: retry silent `stopReason=error` turns with no output when no side effects ran, so non-frontier providers that briefly return empty error turns get another chance instead of ending the session early. (#68310) Thanks @Chased1k.
|
||||
- Plugins/memory: preserve the active memory capability when read-only snapshot plugin loads run, so status and provider discovery paths no longer wipe memory public artifacts. (#69219) Thanks @zeroaltitude.
|
||||
- Plugins: keep only the highest-precedence manifest when distinct discovered plugins share an id, so lower-precedence global or workspace duplicates no longer load beside bundled or config-selected plugins. (#41626) Thanks @Tortes.
|
||||
- fix(security): block MINIMAX_API_HOST workspace env injection and remove env-driven URL routing [AI-assisted]. (#67300) Thanks @pgondhi987.
|
||||
- Cron/delivery: treat explicit `delivery.mode: "none"` runs as not requested even if the runner reports `delivered: false`, so no-delivery cron jobs no longer persist false delivery failures or errors. (#69285) Thanks @matsuri1987.
|
||||
- Plugins/install: repair active and default-enabled bundled plugin runtime dependencies before import in packaged installs, so bundled Discord, WhatsApp, Slack, Telegram, and provider plugins work without putting their dependency trees in core.
|
||||
- BlueBubbles: raise the outbound `/api/v1/message/text` send timeout default from 10s to 30s, and add a configurable `channels.bluebubbles.sendTimeoutMs` (also per-account) so macOS 26 setups where Private API iMessage sends stall for 60+ seconds no longer silently lose messages at the 10s abort. Probes, chat lookups, and health checks keep the shorter 10s default. Fixes #67486. (#69193) Thanks @omarshahine.
|
||||
- Agents/bootstrap: budget truncation markers against per-file caps, preserve source content instead of silently wasting bootstrap bytes, and avoid marker-only output in tiny-budget truncation cases. (#69114) Thanks @BKF-Gitty.
|
||||
- Context engine/plugins: stop rejecting third-party context engines whose `info.id` differs from the registered plugin slot id. The strict-match contract added in 2026.4.14 broke `lossless-claw` and other plugins whose internal engine id does not equal the slot id they are registered under, producing repeated `info.id must match registered id` lane failures on every turn. Fixes #66601. (#66678) Thanks @GodsBoy.
|
||||
- Agents/compaction: rename embedded Pi compaction lifecycle events to `compaction_start` / `compaction_end` so OpenClaw stays aligned with `pi-coding-agent` 0.66.1 event naming. (#67713) Thanks @mpz4life.
|
||||
- Security/dotenv: block all `OPENCLAW_*` keys from untrusted workspace `.env` files so workspace-local env loading fails closed for new runtime-control variables instead of silently inheriting them. (#473)
|
||||
- Gateway/device pairing: restrict non-admin paired-device sessions (device-token auth) to their own pairing list, approve, and reject actions so a paired device cannot enumerate other devices or approve/reject pairing requests authored by another device. Admin and shared-secret operator sessions retain full visibility. (#69375) Thanks @eleqtrizit.
|
||||
- Agents/gateway tool: extend the agent-facing `gateway` tool's config mutation guard so model-driven `config.patch` and `config.apply` cannot rewrite operator-trusted paths (sandbox, plugin trust, gateway auth/TLS, hook routing and tokens, SSRF policy, MCP servers, workspace filesystem hardening) and cannot bypass the guard by editing per-agent sandbox, tools, or embedded-Pi overrides in place under `agents.list[]`. (#69377) Thanks @eleqtrizit.
|
||||
- Gateway/websocket broadcasts: require `operator.read` (or higher) for chat, agent, and tool-result event frames so pairing-scoped and node-role sessions no longer passively receive session chat content, and scope-gate unknown broadcast events by default. Plugin-defined `plugin.*` broadcasts are scoped to operator.write/admin, and status/transport events (`heartbeat`, `presence`, `tick`, etc.) remain unrestricted. Per-client sequence numbers preserve per-connection monotonicity. (#69373) Thanks @eleqtrizit.
|
||||
- Agents/compaction: always reload embedded Pi resources through an explicit loader and reapply reserve-token overrides so runs without extension factories no longer silently lose compaction settings before session start. (#67146) Thanks @ly85206559.
|
||||
- Memory-core/dreaming: normalize sweep timestamps and reuse hashed narrative session keys for fallback cleanup so Dreaming narrative sub-sessions stop leaking. (#67023) Thanks @chiyouYCH.
|
||||
- Gateway/startup: delay HTTP bind until websocket handlers are attached, so immediate post-startup websocket health/connect probes no longer hit the startup race window. (#43392) Thanks @dalefrieswthat.
|
||||
- Codex/app-server: release the session lane when a downstream consumer throws while draining the `turn/completed` notification, so follow-up messages after a Codex plugin reply stop queueing behind a stale lane lock. Fixes #67996. (#69072) Thanks @ayeshakhalid192007-dev.
|
||||
- Codex/app-server: default approval handling to `on-request` so Codex harness sessions do not start with overly permissive tool approvals. (#68721) Thanks @Lucenx9.
|
||||
- Cron/delivery: keep isolated cron chat delivery tools available, resolve `channel: "last"` targets from the gateway, show delivery previews in `cron list/show`, and avoid duplicate fallback sends after direct message-tool delivery. (#69587) Thanks @obviyus.
|
||||
- BlueBubbles: add opt-in `channels.bluebubbles.coalesceSameSenderDms` so a single composed message with text + pasted URL (which Apple splits into two webhooks ~0.8-2.0 s apart) arrives as one agent turn instead of two. When enabled, DM messages that are not linked via `associatedMessageGuid` hash to `dm:<chat>:<sender>` so the inbound debounce window merges them into a single merged turn — including URL-preview balloon events, DM control-command sends (which normally bypass debouncing), and rapid same-sender follow-ups. The default inbound debounce window widens from 500 ms to 2500 ms when the flag is set without an explicit `messages.inbound.byChannel.bluebubbles`, covering the observed Apple split-send cadence. Every source `messageId` folded into the merged view is committed to the inbound dedupe store after processing, so a later MessagePoller replay of any individual source event is recognized as a duplicate. Merged output is bounded (≤4000 chars text with an explicit `…[truncated]` marker, ≤20 attachments, first-plus-latest sampling beyond 10 source entries) so a rapid-fire flood inside the window cannot amplify the downstream prompt. Group chats and existing text+balloon follow-ups continue to key per-message. See [Coalescing split-send DMs](https://docs.openclaw.ai/channels/bluebubbles#coalescing-split-send-dms-command--url-in-one-composition) for scenarios, tuning, and troubleshooting. (#69258) Thanks @omarshahine.
|
||||
- Cron/Telegram: key isolated direct-delivery dedupe to each cron execution instead of the reused session id, so recurring Telegram announce runs no longer report delivered while silently skipping later sends. (#69000) Thanks @obviyus.
|
||||
- Models/Kimi: default bundled Kimi thinking to off and normalize Anthropic-compatible `thinking` payloads so stale session `/think` state no longer silently re-enables reasoning on Kimi runs. (#68907) Thanks @frankekn.
|
||||
- Control UI/cron: keep the runtime-only `last` delivery sentinel from being materialized into persisted cron delivery and failure-alert channel configs when jobs are created or edited. (#68829) Thanks @tianhaocui.
|
||||
- OpenAI/Responses: strip orphaned reasoning blocks before outbound Responses API calls so compacted or restored histories no longer fail on standalone reasoning items. (#55787) Thanks @suboss87.
|
||||
- Cron/CLI: parse PowerShell-style `--tools` allow-lists the same way as comma-separated input, so `cron add` and `cron edit` no longer persist `exec read write` as one combined tool entry on Windows. (#68858) Thanks @chen-zhang-cs-code.
|
||||
- Browser/user-profile: let existing-session `profile="user"` tool calls auto-route to a connected browser node or use explicit `target="node"`, while still honoring explicit `target="host"` pinning. (#48677)
|
||||
- Discord/slash commands: tolerate partial Discord channel metadata in slash-command and model-picker flows so partial channel objects no longer crash when channel names, topics, or thread parent metadata are unavailable. (#68953) Thanks @dutifulbob.
|
||||
- BlueBubbles: consolidate outbound HTTP through a typed `BlueBubblesClient` that resolves the SSRF policy once at construction so image attachments stop getting blocked on localhost and reactions stop getting blocked on private-IP BB deployments. Fixes #34749 and #59722. (#68234) Thanks @omarshahine.
|
||||
- Cron/gateway: reject ambiguous announce delivery config at add/update time so invalid multi-channel or target-id provider settings fail early instead of persisting broken cron jobs. (#69015) Thanks @obviyus.
|
||||
- Cron/main-session delivery: preserve `heartbeat.target="last"` through deferred wake queuing, gateway wake forwarding, and same-target wake coalescing so queued cron replies still return to the last active chat. (#69021) Thanks @obviyus.
|
||||
- Cron/gateway: ignore disabled channels when announce delivery ambiguity is checked, and validate main-session delivery patches against the live cron service default agent so hot-reloaded agent config does not falsely reject valid updates. (#69040) Thanks @obviyus.
|
||||
- Matrix/allowlists: hot-reload `dm.allowFrom` and `groupAllowFrom` entries on inbound messages while keeping config removals authoritative, so Matrix allowlist changes no longer require a channel restart to add or revoke a sender. (#68546) Thanks @johnlanni.
|
||||
- BlueBubbles: always set `method` explicitly on outbound text sends (`"private-api"` when available, `"apple-script"` otherwise), and prefer Private API on macOS 26 even for plain text. Fixes silent delivery failure on macOS setups without Private API where an omitted `method` let BB Server fall back to version-dependent default behavior that silently drops the message (#64480), and the AppleScript `-1700` error on macOS 26 Tahoe plain text sends (#53159). (#69070) Thanks @xqing3.
|
||||
- Matrix/commands: recognize slash commands that are prefixed with the bot's Matrix mention, so room messages like `@bot:server /new` trigger the command path without requiring custom mention regexes. (#68570) Thanks @nightq and @johnlanni.
|
||||
- Gateway/pairing: return reason-specific `PAIRING_REQUIRED` details, remediation hints, and request ids so unapproved-device and scope-upgrade failures surface actionable recovery guidance in the CLI and Control UI. (#69227) Thanks @obviyus.
|
||||
- Agents/subagents: include requested role and runtime timing on subagent failure payloads so parent agents can correlate failed or timed-out child work. (#68726) Thanks @BKF-Gitty.
|
||||
- Gateway/sessions: reject stale agent-scoped sessions after an agent is removed from config while preserving legacy default-agent main-session aliases. (#65986) Thanks @bittoby.
|
||||
- Doctor/gateway: surface pending device pairing requests, scope-upgrade approval drift, and stale device-token mismatch repair steps so `openclaw doctor --fix` no longer leaves pairing/auth setup failures unexplained. (#69210) Thanks @obviyus.
|
||||
- Cron/isolated-agent: preserve explicit `delivery.mode: "none"` message targets for isolated runs without inheriting implicit `last` routing, so agent-initiated Telegram sends keep their authored destination while bare `mode:none` jobs stay targetless. (#69153) Thanks @obviyus.
|
||||
- Cron/isolated-agent: keep `delivery.mode: "none"` account-only or thread-only configs from inheriting a stale implicit recipient, so isolated runs only resolve message routing when the job authored an explicit `to` target. (#69163) Thanks @obviyus.
|
||||
- Gateway/TUI: retry session history while the local gateway is still finishing startup, so `openclaw tui` reconnects no longer fail on transient `chat.history unavailable during gateway startup` errors. (#69164) Thanks @shakkernerd.
|
||||
- BlueBubbles/reactions: fall back to `love` when an agent reacts with an emoji outside the iMessage tapback set (`love`/`like`/`dislike`/`laugh`/`emphasize`/`question`), so wider-vocabulary model reactions like `👀` still produce a visible tapback instead of failing the whole reaction request. Configured ack reactions still validate strictly via the new `normalizeBlueBubblesReactionInputStrict` path. (#64693) Thanks @zqchris.
|
||||
- BlueBubbles: prefer iMessage over SMS when both chats exist for the same handle, honor explicit `sms:` targets, and never silently downgrade iMessage-available recipients. (#61781) Thanks @rmartin.
|
||||
- Telegram/setup: require numeric `allowFrom` user IDs during setup instead of offering unsupported `@username` DM resolution, and point operators to `from.id`/`getUpdates` for discovery. (#69191) Thanks @obviyus.
|
||||
- GitHub Copilot/onboarding: default GitHub Copilot setup to `claude-opus-4.6` and keep the bundled default model list aligned, so new Copilot setups no longer start on the older `gpt-4o` default. (#69207) Thanks @obviyus.
|
||||
- Gateway/status: separate reachability, capability, and read-probe reporting so connect-only or scope-limited sessions no longer look fully healthy, and normalize SSH targets entered as `ssh user@host`. (#69215) Thanks @obviyus.
|
||||
- Slack: fix outbound replies failing with "unresolved SecretRef" for accounts configured via `file` or `exec` secret sources; the send path now tolerates the runtime snapshot retaining an unresolved channel SecretRef when a boot-resolved token override is already available. (#68954) Thanks @openperf.
|
||||
- Control UI/device pairing: explain scope and role approval upgrades during reconnects, and show requested versus approved access in the Control UI and `openclaw devices` so broader reconnects no longer look like lost pairings. (#69221) Thanks @obviyus.
|
||||
- Gateway/Control UI: surface pending scope, role, and device-metadata pairing approvals in auth errors and Control UI hints so broader reconnects no longer look like random auth breakage. (#69226) Thanks @obviyus.
|
||||
- Telegram/media: parse lowercase media directives in block replies and preserve outbound attachment filenames, so generated files send once with their original names. (#69641) Thanks @obviyus.
|
||||
- Agents/Anthropic: honor explicit `cacheRetention: "long"` for custom `anthropic-messages` endpoints by applying the 1-hour ephemeral cache TTL independently of the Anthropic/Vertex hostname allowlist. Implicit and env-driven long retention still require an allowlisted host. (#67800) Thanks @MonkeyLeeT.
|
||||
|
||||
## 2026.4.19-beta.2
|
||||
|
||||
### Fixes
|
||||
|
||||
- Agents/openai-completions: always send `stream_options.include_usage` on streaming requests, so local and custom OpenAI-compatible backends report real context usage instead of showing 0%. (#68746) Thanks @kagura-agent.
|
||||
- Agents/nested lanes: scope nested agent work per target session so a long-running nested run on one session no longer head-of-line blocks unrelated sessions across the gateway. (#67785) Thanks @stainlu.
|
||||
- Agents/status: preserve carried-forward session token totals for providers that omit usage metadata, so `/status` and `openclaw sessions` keep showing the last known context usage instead of dropping back to unknown/0%. (#67695) Thanks @stainlu.
|
||||
- Install/update: keep legacy update verification compatible with the QA Lab runtime shim, so updating older global installs to beta no longer fails after npm installs the package successfully.
|
||||
|
||||
## 2026.4.19-beta.1
|
||||
|
||||
### Fixes
|
||||
|
||||
- Agents/channels: route cross-agent subagent spawns through the target agent's bound channel account while preserving peer and workspace/role-scoped bindings, so child sessions no longer inherit the caller's account in shared rooms, workspaces, or multi-account setups. (#67508) Thanks @lukeboyett and @gumadeiras.
|
||||
- Telegram/callbacks: treat permanent callback edit errors as completed updates so stale command pagination buttons no longer wedge the update watermark and block newer Telegram updates. (#68588) Thanks @Lucenx9.
|
||||
- Browser/CDP: allow the selected remote CDP profile host for CDP health and control checks without widening browser navigation SSRF policy, so WSL-to-Windows Chrome endpoints no longer appear offline under strict defaults. Fixes #68108. (#68207) Thanks @Mlightsnow.
|
||||
- Codex: stop cumulative app-server token totals from being treated as fresh context usage, so session status no longer reports inflated context percentages after long Codex threads. (#64669) Thanks @cyrusaf.
|
||||
- Browser/CDP: add phase-specific CDP readiness diagnostics and normalize loopback WebSocket host aliases, so Windows browser startup failures surface whether HTTP discovery, WebSocket discovery, SSRF validation, or the `Browser.getVersion` health check failed.
|
||||
- Browser/CDP: discover Chrome’s real DevTools websocket from bare `ws://host:port` attach-only roots before declaring the profile down, while still falling back to direct websocket providers that do not expose `/json/version`. Fixes #68027. (#68715) Thanks @visionik.
|
||||
|
||||
## 2026.4.18
|
||||
|
||||
### Changes
|
||||
|
||||
- Anthropic/models: add Claude Opus 4.7 `xhigh` reasoning effort support and keep it separate from adaptive thinking.
|
||||
- Control UI/settings: overhaul the settings and slash-command experience with faster presets, quick-create flows, and refreshed command discovery. (#67819) Thanks @BunsDev.
|
||||
- macOS/gateway: add `screen.snapshot` support for macOS app nodes, including runtime plumbing, default macOS allowlisting, and docs for monitor preview flows. (#67954) Thanks @BunsDev.
|
||||
|
||||
### Fixes
|
||||
|
||||
- Codex/gateway: fix gateway crashes when the codex-acp subprocess terminates abruptly; pending requests now shut down gracefully instead of propagating an uncaught EPIPE through the gateway daemon and connected channels. Fixes #67886. (#67947) Thanks @openperf.
|
||||
- Agents/bootstrap: resolve bootstrap from workspace truth instead of stale session transcript markers, keep embedded bootstrap instructions on a hidden user-context prelude, suppress normal `/new` and `/reset` greetings while `BOOTSTRAP.md` is still pending, and make the embedded runner read the bootstrap ritual before replying normally.
|
||||
- Agents/bootstrap: dedupe repeated bootstrap-truncation warnings so startup logs stay actionable. (#67906) Thanks @rubencu.
|
||||
- WhatsApp/multi-account: centralize named-account inbound policy, isolate per-account group activation and scoped session keys, preserve legacy activation backfill, and keep `accounts.default` shared defaults aligned across runtime, setup, and compat migration paths. Thanks @mcaxtr.
|
||||
- Cron/delivery: clean up isolated sessions after direct deliveries when `deleteAfterRun` is enabled, covering structured and threaded branches that previously bypassed cleanup. (#67807) Thanks @MonkeyLeeT.
|
||||
- Gateway/hello-ok: always report negotiated auth metadata for successful shared-auth handshakes, including control-ui bypass coverage when no device token is issued. (#67810) Thanks @BunsDev.
|
||||
- Gateway/hello-ok: always report negotiated auth metadata and preserve scopes for reused device tokens on successful shared-auth handshakes, including control-ui bypass coverage when no device token is issued. (#67810, #68039) Thanks @BunsDev.
|
||||
- Onboarding/non-interactive: preserve existing gateway auth tokens during re-onboard so active local gateway clients are not disconnected by an implicit token rotation. (#67821) Thanks @BKF-Gitty.
|
||||
- OpenAI Codex/Responses: unify native Responses API capability detection so Codex OAuth requests emit the required `store: false` field on the native Responses path. (#67918) Thanks @obviyus.
|
||||
- WhatsApp/setup: guard personal-phone and allowlist prompt values so setup fails with clear validation errors instead of crashing on undefined prompt text. (#67895) Thanks @lawrence3699.
|
||||
- Models/config: preserve an existing `models.json` provider `baseUrl` during merge-mode regeneration so custom endpoints do not get reset on restart. (#67893) Thanks @lawrence3699.
|
||||
- Plugin SDK: preserve `secret-input-runtime` function exports in published builds so provider plugins can read SecretRef-backed setup inputs.
|
||||
- Plugins/discovery: reuse bundled and global plugin discovery results across workspace cache misses so Windows multi-workspace startup stops redoing the shared synchronous scan. (#67940) Thanks @obviyus.
|
||||
- Bundled plugins/install: keep staged bundled plugin runtime imports resolving through the packaged Plugin SDK while omitting checkout-only aliases from the dist inventory, so published installs do not fail on repo-local paths.
|
||||
- Plugins/webhooks: enforce synchronous plugin registration with full rollback of failed plugin side effects, and cache SecretRef-backed webhook auth per route so plugin startup and inbound webhook auth stay deterministic. (#67941) Thanks @obviyus.
|
||||
- Telegram/polling transport: give the Telegram undici dispatcher pool bounded keep-alive defaults and an explicit lifecycle. Previously every recoverable network error and stall watchdog trip silently replaced the transport, abandoning the old dispatcher pool and its sockets; long-running gateway processes accumulated hundreds of ESTABLISHED connections to `api.telegram.org`, saturating per-IP upstream proxy quotas and causing the actively-used outbound proxy node to time out while every other node still tested healthy. Transports now expose `close()`, `TelegramPollingTransportState` destroys the stale transport on dirty-rebuild, and `TelegramPollingSession` disposes the transport when polling exits — backed by a strict per-origin pool cap on every constructed `Agent`, `ProxyAgent`, and `EnvHttpProxyAgent` as defence in depth.
|
||||
- Telegram/polling: publish successful `getUpdates` calls as account health liveness, avoid false stall restarts after recoverable `getUpdates` errors, and force Telegram API dispatchers to HTTP/1.1 so stalled polling recovers instead of sitting connected-but-dead.
|
||||
- Telegram/ACP bindings: drop persisted DM bindings that still point at missing or failed ACP sessions on restart, while preserving plugin-owned bindings and uncertain store reads. (#67822) Thanks @chinar-amrutkar.
|
||||
- Telegram/streaming: keep a transient preview on the same Telegram message when auto-compaction retries an in-flight answer, so streamed replies no longer appear duplicated after compaction. (#66939) Thanks @rubencu.
|
||||
- Memory/sqlite-vec: emit the degraded sqlite-vec warning once per degraded episode instead of repeating it for every file write, while preserving the latch across safe-reindex rollback and resetting it when vector state is genuinely rebuilt. (#67898) Thanks @rubencu.
|
||||
- Memory-core: preserve stored vector dimensions during read-only recovery so memory indexes do not lose vector metadata while repairing read-only state.
|
||||
- Reply/block streaming: preserve post-stream incomplete-turn error payloads after block streaming already emitted content, so users get the warning instead of silence. (#67991) Thanks @obviyus.
|
||||
- Telegram/streaming: clear the compaction replay guard after visible non-final boundaries so a post-tool assistant reply rotates to a fresh preview instead of editing the pre-compaction message. (#67993) Thanks @obviyus.
|
||||
- Matrix: fix `sessions_spawn --thread` subagent session spawning — thread binding creation, cleanup on session end, and completion-message delivery target resolution now work end-to-end. (#67643) Thanks @eejohnso-ops and @gumadeiras.
|
||||
- Slack/streaming: resolve native streaming recipient teams from the inbound user when available, with a monitor-team fallback, so DM and shared-workspace streams target the right recipient more reliably.
|
||||
- macOS/webchat: enable Undo and Redo in the composer text input by turning on the native `NSTextView` undo manager. (#34962) Thanks @tylerbittner.
|
||||
- macOS/remote SSH: require an already-trusted host key on the macOS remote command, gateway probe, port tunnel, and pairing probe paths by switching `StrictHostKeyChecking=accept-new` to `StrictHostKeyChecking=yes` and centralizing the shared SSH option fragments in `CommandResolver`, so first-time macOS remote connections no longer silently accept an unknown host key and must be trusted ahead of time via `~/.ssh/known_hosts`. (#68199)
|
||||
- CLI/configure: show the channel picker before probing statuses and let remove mode delete configured channel blocks directly from config. (#68007) Thanks @gumadeiras.
|
||||
- Control UI/settings: reset scroll position when switching settings pages and align details headers. (#68150) Thanks @BunsDev.
|
||||
- WhatsApp/gateway: harden WhatsApp auth persistence and backup recovery, model unstable auth state explicitly in setup/status/health, recover backup-backed login without forcing a fresh QR, and keep local gateway handoff and channel restarts truthful after login. Thanks @mcaxtr.
|
||||
- OpenAI Codex/OAuth: keep OpenClaw as the canonical owner for imported Codex CLI OAuth sessions, stop writing refreshed credentials back into `.codex`, and prefer fresher OpenClaw credentials over stale imported CLI state so refresh recovery stays stable. Thanks @vincentkoc.
|
||||
- OpenAI Codex/OAuth: treat the OpenAI TLS prerequisites probe as advisory instead of a hard blocker, so Codex sign-in can still proceed when the speculative Node/OpenSSL precheck fails but the real OAuth flow still works. Thanks @vincentkoc.
|
||||
- Models status/OAuth health: align OAuth health reporting with the same effective credential view runtime uses, so expired refreshable sessions stop showing healthy by default and fresher imported Codex CLI credentials surface correctly in `models status`, doctor, and gateway auth status. Thanks @vincentkoc.
|
||||
@@ -43,9 +181,11 @@ Docs: https://docs.openclaw.ai
|
||||
- Failover/google: only treat `INTERNAL` status payloads as retryable timeouts when they also carry a `500` code, so malformed non-500 payloads do not enter the retry path. (#68238) Thanks @altaywtf and @Openbling.
|
||||
- Agents/tools: filter bundled MCP/LSP tools through the final owner-only and tool-policy pipeline after merging them into the effective tool list, so existing allowlists, deny rules, sandbox policy, subagent policy, and owner-only restrictions apply to bundled tools the same way they apply to core tools. (#68195)
|
||||
- Gateway/assistant media: require `operator.read` scope for assistant-media file and metadata requests on identity-bearing HTTP auth paths so callers without a read scope can no longer access assistant media. (#68175) Thanks @eleqtrizit.
|
||||
- Gateway/web: allow same-origin microphone access in the Permissions-Policy header so browser voice capture can work from the Control UI and webchat origin. (#68368)
|
||||
- Exec approvals/display: escape raw control characters (including newline and carriage return) in the shared and macOS approval-prompt command sanitizers, so trailing command payloads no longer render on hidden extra lines in the approval UI. (#68198)
|
||||
- Telegram/streaming: fence same-session stale preview and finalization work after aborts so Telegram no longer replays an older reply or flushes a hidden short preview after the abort confirmation lands. (#68100) Thanks @rubencu.
|
||||
- OpenAI Codex/OAuth + Pi: keep imported Codex CLI OAuth bootstrap, Pi auth export, and runtime overlay handling aligned so Codex sessions survive refresh and health checks without leaking transient CLI state into saved auth files. Thanks @vincentkoc.
|
||||
- OpenAI Codex/OAuth: keep Codex-specific auth bridging inside the owning plugins, preserve canonical imported CLI profiles, and allow legacy identity-less main-store OAuth sessions to upgrade during refresh mirroring. (#68284) Thanks @vincentkoc.
|
||||
- Config/redact: add `browser.cdpUrl` and `browser.profiles.*.cdpUrl` to sensitive URL config paths so embedded credentials (query tokens and HTTP Basic auth) are properly redacted in `config.get` API responses and availability error messages. (#67679) Thanks @Ziy1-Tan.
|
||||
- Agents/TTS: report failed speech synthesis as a real tool error so unconfigured providers no longer feed successful TTS failure output back into agent loops. (#67980) Thanks @lawrence3699.
|
||||
- Gateway/wake: allow unknown properties on wake payloads so external senders like Paperclip can attach opaque metadata without failing schema validation. (#68355) Thanks @kagura-agent.
|
||||
@@ -54,6 +194,16 @@ Docs: https://docs.openclaw.ai
|
||||
- Agents/failover: avoid treating bare leading `402 ...` prose as billing errors while still recognizing proxy subscription failures. (#45827) Thanks @junyuc25.
|
||||
- Config/$schema: preserve root-authored `$schema` during partial config rewrites without injecting include-only schema URLs into the root config. (#47322) Thanks @EfeDurmaz16.
|
||||
- Agents/CLI delivery: run the same reply-media path normalizer the auto-reply flow uses before shipping `openclaw agent --deliver` payloads, so relative `MEDIA:./out/photo.png` tokens resolve against the agent workspace instead of being rejected downstream with `LocalMediaAccessError: Local media path is not under an allowed directory`. Thanks @frankekn.
|
||||
- Agents/Google: strip `thinkingBudget=0` for the thinking-required `gemini-2.5-pro` model in embedded-runner and native Google payloads, so requests no longer fail with `Budget 0 is invalid. This model only works in thinking mode.` and the API uses its default thinking behavior instead. (#68607) Thanks @josmithiii.
|
||||
- Slack/threads: log failed thread starter and history fetches at verbose level while preserving best-effort fallback behavior, so missing Slack thread context is diagnosable without interrupting inbound handling. (#68594) Thanks @martingarramon.
|
||||
- Gateway/restart: keep stale-gateway cleanup from terminating the current process's parent or ancestors, so plugin sidecars like WeChat no longer kill the active gateway and trigger an infinite supervisor restart loop. Fixes #68451. (#68517) Thanks @openperf.
|
||||
- Gateway/auth: reject gateway auth credentials that match published example placeholders at startup and secret reload, and keep cloud install snippets from publishing copy-paste gateway/keyring secrets. (#68404) Thanks @coygeek.
|
||||
- CLI/update: preserve macOS restart helper launchctl failures in the update restart log without letting log setup block the restart path. (#68492) Thanks @hclsys.
|
||||
- Slack/threads: keep file-only root messages as starter context so first thread replies can still hydrate starter media. (#68594) Thanks @martingarramon.
|
||||
- Google/Antigravity: resolve forward-compatible Gemini 3.1 Pro custom-tools and Flash variants from the bundled Google plugin templates, so `google-antigravity/gemini-3.1-pro-preview-customtools` no longer falls through to an unknown-model error. Fixes #35512.
|
||||
- Active Memory: raise the blocking recall timeout ceiling to 120 seconds and reject larger config values during plugin schema validation. Fixes #68410. (#68480) Thanks @Bartok9.
|
||||
- Control UI/chat: keep history-backed user image uploads visible after chat reload while filtering blocked or non-image transcript media paths. (#68415) Thanks @mraleko.
|
||||
- Matrix/plugins: keep remaining Matrix event helpers on the canonical `matrix-js-sdk` subpath so build and plugin-load entrypoint checks stay consistent. (#68498) Thanks @masatohoshino.
|
||||
|
||||
## 2026.4.15
|
||||
|
||||
@@ -162,6 +312,9 @@ Docs: https://docs.openclaw.ai
|
||||
- Node-host/tools.exec: let approval binding distinguish known native binaries from mutable shell payload files, while still fail-closing unknown or racy file probes so absolute-path node-host commands like `/usr/bin/whoami` no longer get rejected as unsafe interpreter/runtime commands. (#66731) Thanks @tmimmanuel.
|
||||
- Codex/gateway: fix gateway crash when the codex-acp subprocess terminates abruptly; an unhandled EPIPE on the child stdin stream now routes through graceful client shutdown, rejecting pending requests instead of propagating as an uncaught exception that crashes the entire gateway daemon and all connected channels. Fixes #67886. (#67947) thanks @openperf
|
||||
- Slack/streaming: resolve native streaming recipient teams from the inbound user when available, with a monitor-team fallback, so DM and shared-workspace streams target the right recipient more reliably.
|
||||
- OpenRouter/streaming: treat `reasoning_details.response.output_text` and `reasoning_details.response.text` as visible assistant output on OpenRouter-compatible completions streams, while keeping `reasoning.text` hidden and refusing to surface ambiguous bare `text` items by default so visible replies, thinking blocks, and tool calls can coexist in the same chunk. (#67410) Thanks @neeravmakwana.
|
||||
- Models/OpenRouter aliases: resolve `openrouter:auto` to the canonical `openrouter/auto` model and map `openrouter:free` to the first configured concrete `openrouter/...:free` model instead of mis-resolving these compatibility aliases under the default provider. (#57066) Thanks @sumiisiaran.
|
||||
- OpenRouter/Arcee: canonicalize stale OpenRouter `https://openrouter.ai/v1` base URLs during provider config normalization and runtime model/transport resolution, so fresh `models.json` writes and previously discovered rows self-heal back to `https://openrouter.ai/api/v1` instead of breaking OpenRouter-routed requests. (#67295) Thanks @achalkov.
|
||||
|
||||
## 2026.4.14
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# syntax=docker/dockerfile:1.7
|
||||
|
||||
FROM debian:bookworm-slim@sha256:98f4b71de414932439ac6ac690d7060df1f27161073c5036a7553723881bffbe
|
||||
FROM debian:bookworm-slim@sha256:4724b8cc51e33e398f0e2e15e18d5ec2851ff0c2280647e1310bc1642182655d
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# syntax=docker/dockerfile:1.7
|
||||
|
||||
FROM debian:bookworm-slim@sha256:98f4b71de414932439ac6ac690d7060df1f27161073c5036a7553723881bffbe
|
||||
FROM debian:bookworm-slim@sha256:4724b8cc51e33e398f0e2e15e18d5ec2851ff0c2280647e1310bc1642182655d
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
|
||||
91
README.md
91
README.md
@@ -157,9 +157,9 @@ Run `openclaw doctor` to surface risky/misconfigured DM policies.
|
||||
## Security model (important)
|
||||
|
||||
- Default: tools run on the host for the `main` session, so the agent has full access when it is just you.
|
||||
- Group/channel safety: set `agents.defaults.sandbox.mode: "non-main"` to run non-`main` sessions inside per-session Docker sandboxes.
|
||||
- Group/channel safety: set `agents.defaults.sandbox.mode: "non-main"` to run non-`main` sessions inside sandboxes. Docker is the default sandbox backend; SSH and OpenShell backends are also available.
|
||||
- Typical sandbox default: allow `bash`, `process`, `read`, `write`, `edit`, `sessions_list`, `sessions_history`, `sessions_send`, `sessions_spawn`; deny `browser`, `canvas`, `nodes`, `cron`, `discord`, `gateway`.
|
||||
- Before exposing anything remotely, read [Security](https://docs.openclaw.ai/gateway/security), [Docker sandboxing](https://docs.openclaw.ai/install/docker), and [Configuration](https://docs.openclaw.ai/gateway/configuration).
|
||||
- Before exposing anything remotely, read [Security](https://docs.openclaw.ai/gateway/security), [Sandboxing](https://docs.openclaw.ai/gateway/sandboxing), and [Configuration](https://docs.openclaw.ai/gateway/configuration).
|
||||
|
||||
## Operator quick refs
|
||||
|
||||
@@ -173,7 +173,7 @@ Run `openclaw doctor` to surface risky/misconfigured DM policies.
|
||||
- New here: [Getting started](https://docs.openclaw.ai/start/getting-started), [Onboarding](https://docs.openclaw.ai/start/wizard), [Updating](https://docs.openclaw.ai/install/updating)
|
||||
- Channel setup: [Channels index](https://docs.openclaw.ai/channels), [WhatsApp](https://docs.openclaw.ai/channels/whatsapp), [Telegram](https://docs.openclaw.ai/channels/telegram), [Discord](https://docs.openclaw.ai/channels/discord), [Slack](https://docs.openclaw.ai/channels/slack)
|
||||
- Apps + nodes: [macOS](https://docs.openclaw.ai/platforms/macos), [iOS](https://docs.openclaw.ai/platforms/ios), [Android](https://docs.openclaw.ai/platforms/android), [Nodes](https://docs.openclaw.ai/nodes)
|
||||
- Config + security: [Configuration](https://docs.openclaw.ai/gateway/configuration), [Security](https://docs.openclaw.ai/gateway/security), [Docker sandboxing](https://docs.openclaw.ai/install/docker)
|
||||
- Config + security: [Configuration](https://docs.openclaw.ai/gateway/configuration), [Security](https://docs.openclaw.ai/gateway/security), [Sandboxing](https://docs.openclaw.ai/gateway/sandboxing)
|
||||
- Remote + web: [Gateway](https://docs.openclaw.ai/gateway), [Remote access](https://docs.openclaw.ai/gateway/remote), [Tailscale](https://docs.openclaw.ai/gateway/tailscale), [Web surfaces](https://docs.openclaw.ai/web)
|
||||
- Tools + automation: [Tools](https://docs.openclaw.ai/tools), [Skills](https://docs.openclaw.ai/tools/skills), [Cron jobs](https://docs.openclaw.ai/automation/cron-jobs), [Webhooks](https://docs.openclaw.ai/automation/webhook), [Gmail Pub/Sub](https://docs.openclaw.ai/automation/gmail-pubsub)
|
||||
- Internals: [Architecture](https://docs.openclaw.ai/concepts/architecture), [Agent](https://docs.openclaw.ai/concepts/agent), [Session model](https://docs.openclaw.ai/concepts/session), [Gateway protocol](https://docs.openclaw.ai/reference/rpc)
|
||||
@@ -212,21 +212,34 @@ Runbook: [iOS connect](https://docs.openclaw.ai/platforms/ios).
|
||||
|
||||
Prefer `pnpm` for builds from source. Bun is optional for running TypeScript directly.
|
||||
|
||||
For the dev loop:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/openclaw/openclaw.git
|
||||
cd openclaw
|
||||
|
||||
pnpm install
|
||||
pnpm ui:build # auto-installs UI deps on first run
|
||||
pnpm build
|
||||
|
||||
pnpm openclaw onboard --install-daemon
|
||||
# First run only (or after resetting local OpenClaw config/workspace)
|
||||
pnpm openclaw setup
|
||||
|
||||
# Optional: prebuild Control UI before first startup
|
||||
pnpm ui:build
|
||||
|
||||
# Dev loop (auto-reload on source/config changes)
|
||||
pnpm gateway:watch
|
||||
```
|
||||
|
||||
Note: `pnpm openclaw ...` runs TypeScript directly (via `tsx`). `pnpm build` produces `dist/` for running via Node / the packaged `openclaw` binary.
|
||||
If you need a built `dist/` from the checkout (for Node, packaging, or release validation), run:
|
||||
|
||||
```bash
|
||||
pnpm build
|
||||
pnpm ui:build
|
||||
```
|
||||
|
||||
`pnpm openclaw setup` writes the local config/workspace needed for `pnpm gateway:watch`. It is safe to re-run, but you normally only need it on first setup or after resetting local state. `pnpm gateway:watch` does not rebuild `dist/control-ui`, so rerun `pnpm ui:build` after `ui/` changes or use `pnpm ui:dev` when iterating on the Control UI. If you want this checkout to run onboarding directly, use `pnpm openclaw onboard --install-daemon`.
|
||||
|
||||
Note: `pnpm openclaw ...` runs TypeScript directly (via `tsx`). `pnpm build` produces `dist/` for running via Node / the packaged `openclaw` binary, while `pnpm gateway:watch` rebuilds the runtime on demand during the dev loop.
|
||||
|
||||
## Development channels
|
||||
|
||||
@@ -285,133 +298,69 @@ Thanks to all clawtributors:
|
||||
<!-- clawtributors:start -->
|
||||
|
||||
[](https://github.com/steipete) [](https://github.com/vincentkoc) [](https://github.com/Takhoffman) [](https://github.com/obviyus) [](https://github.com/gumadeiras) [](https://github.com/mbelinky) [](https://github.com/vignesh07) [](https://github.com/joshavant) [](https://github.com/scoootscooob) [](https://github.com/jacobtomlinson)
|
||||
|
||||
[](https://github.com/shakkernerd) [](https://github.com/sebslight) [](https://github.com/tyler6204) [](https://github.com/ngutman) [](https://github.com/thewilloftheshadow) [](https://github.com/Sid-Qin) [](https://github.com/mcaxtr) [](https://github.com/eleqtrizit) [](https://github.com/BunsDev) [](https://github.com/cpojer)
|
||||
|
||||
[](https://github.com/Glucksberg) [](https://github.com/osolmaz) [](https://github.com/bmendonca3) [](https://github.com/jalehman) [](https://github.com/huntharo) [](https://github.com/neeravmakwana) [](https://github.com/openperf) [](https://github.com/joshp123) [](https://github.com/pgondhi987) [](https://github.com/altaywtf)
|
||||
|
||||
[](https://github.com/quotentiroler) [](https://github.com/liuxiaopai-ai) [](https://github.com/rodrigouroz) [](https://github.com/frankekn) [](https://github.com/drobison00) [](https://github.com/zerone0x) [](https://github.com/onutc) [](https://github.com/ademczuk) [](https://github.com/ImLukeF) [](https://github.com/hydro13)
|
||||
|
||||
[](https://github.com/hxy91819) [](https://github.com/coygeek) [](https://github.com/dutifulbob) [](https://github.com/sliverp) [](https://github.com/0xRaini) [](https://github.com/robbyczgw-cla) [](https://github.com/joelnishanth) [](https://github.com/echoVic) [](https://github.com/sallyom) [](https://github.com/yinghaosang)
|
||||
|
||||
[](https://github.com/BradGroux) [](https://github.com/christianklotz) [](https://github.com/odysseus0) [](https://github.com/hclsys) [](https://github.com/byungsker) [](https://github.com/pashpashpash) [](https://github.com/stakeswky) [![github-actions[bot]](https://avatars.githubusercontent.com/in/15368?v=4&s=48)](https://github.com/apps/github-actions) [](https://github.com/xinhuagu) [](https://github.com/MonkeyLeeT)
|
||||
|
||||
[](https://github.com/100yenadmin) [](https://github.com/mcinteerj) [](https://github.com/samzong) [](https://github.com/chilu18) [](https://github.com/darkamenosa) [](https://github.com/widingmarcus-cyber) [](https://github.com/cgdusek) [](https://github.com/Lukavyi) [](https://github.com/davidrudduck) [](https://github.com/VACInc)
|
||||
|
||||
[](https://github.com/MoerAI) [](https://github.com/velvet-shark) [](https://github.com/HenryLoenwind) [](https://github.com/omarshahine) [](https://github.com/bohdanpodvirnyi) [](https://github.com/VeriteIgiraneza) [](https://github.com/akramcodez) [](https://github.com/Kaneki-x) [](https://github.com/aether-ai-agent) [](https://github.com/joaohlisboa)
|
||||
|
||||
[](https://github.com/MaudeBot) [](https://github.com/davidguttman) [](https://github.com/justinhuangcode) [](https://github.com/lml2468) [](https://github.com/wirjo) [](https://github.com/iHildy) [](https://github.com/mudrii) [](https://github.com/advaitpaliwal) [](https://github.com/czekaj) [](https://github.com/dlauer)
|
||||
|
||||
[](https://github.com/Solvely-Colin) [](https://github.com/feiskyer) [](https://github.com/brandonwise) [](https://github.com/conroywhitney) [](https://github.com/mneves75) [](https://github.com/jaydenfyi) [](https://github.com/davemorin) [](https://github.com/joeykrug) [](https://github.com/kevinWangSheng) [](https://github.com/pejmanjohn)
|
||||
|
||||
[](https://github.com/Lanfei) [](https://github.com/liuy) [](https://github.com/lc0rp) [](https://github.com/teconomix) [](https://github.com/omair445) [](https://github.com/dorukardahan) [](https://github.com/mmaps) [](https://github.com/tobiasbischoff) [](https://github.com/adhitShet) [](https://github.com/pandego)
|
||||
|
||||
[](https://github.com/bradleypriest) [](https://github.com/bjesuiter) [](https://github.com/grp06) [](https://github.com/shadril238) [](https://github.com/kesku) [](https://github.com/YuriNachos) [](https://github.com/vrknetha) [](https://github.com/smartprogrammer93) [](https://github.com/Nachx639) [](https://github.com/jnMetaCode)
|
||||
|
||||
[](https://github.com/Phineas1500) [](https://github.com/dingn42) [](https://github.com/geekhuashan) [](https://github.com/Nanako0129) [](https://github.com/AytuncYildizli) [](https://github.com/BruceMacD) [](https://github.com/jjjojoj) [](https://github.com/mvanhorn) [](https://github.com/bugkill3r) [](https://github.com/rahthakor)
|
||||
|
||||
[](https://github.com/GodsBoy) [](https://github.com/SARAMALI15792) [](https://github.com/radek-paclt) [](https://github.com/Elarwei001) [](https://github.com/ingyukoh) [](https://github.com/SnowSky1) [](https://github.com/lewiswigmore) [](https://github.com/solavrc) [](https://github.com/aldoeliacim) [](https://github.com/jrusz)
|
||||
|
||||
[](https://github.com/tonydehnke) [](https://github.com/roshanasingh4) [](https://github.com/zssggle-rgb) [](https://github.com/adam91holt) [](https://github.com/graysurf) [](https://github.com/xadenryan) [](https://github.com/sfo2001) [](https://github.com/orlyjamie) [](https://github.com/hsrvc) [](https://github.com/tomsun28)
|
||||
|
||||
[](https://github.com/BillChirico) [](https://github.com/carrotRakko) [](https://github.com/ranausmanai) [](https://github.com/arkyu2077) [](https://github.com/hoyyeva) [](https://github.com/luoyanglang) [](https://github.com/sibbl) [](https://github.com/gregmousseau) [](https://github.com/sahilsatralkar) [](https://github.com/akoscz)
|
||||
|
||||
[](https://github.com/rrenamed) [](https://github.com/YuzuruS) [](https://github.com/Marvae) [](https://github.com/mitchmcalister) [](https://github.com/juanpablodlc) [](https://github.com/shtse8) [](https://github.com/thebenignhacker) [](https://github.com/nimbleenigma) [](https://github.com/Linux2010) [](https://github.com/shichangs)
|
||||
|
||||
[](https://github.com/efe-arv) [](https://github.com/hsiaoa) [](https://github.com/nabbilkhan) [](https://github.com/ayanesakura) [](https://github.com/lupuletic) [](https://github.com/polooooo) [](https://github.com/xaeon2026) [](https://github.com/shrey150) [](https://github.com/taw0002) [](https://github.com/dinakars777)
|
||||
|
||||
[](https://github.com/giulio-leone) [](https://github.com/nyanjou) [](https://github.com/meaningfool) [](https://github.com/kunalk16) [](https://github.com/ide-rea) [](https://github.com/JonathanJing) [](https://github.com/yelog) [](https://github.com/markmusson) [](https://github.com/kiranvk-2011) [](https://github.com/Sathvik-Chowdary-Veerapaneni)
|
||||
|
||||
[](https://github.com/rogerdigital) [](https://github.com/artwalker) [](https://github.com/azade-c) [](https://github.com/chinar-amrutkar) [](https://github.com/maxsumrall) [](https://github.com/Minidoracat) [](https://github.com/unisone) [](https://github.com/ly85206559) [](https://github.com/theSamPadilla) [](https://github.com/AnonO6)
|
||||
|
||||
[](https://github.com/afurm) [](https://github.com/jwchmodx) [](https://github.com/leszekszpunar) [](https://github.com/Mrseenz) [](https://github.com/Yida-Dev) [](https://github.com/kesor) [](https://github.com/mazhe-nerd) [](https://github.com/buerbaumer) [](https://github.com/magimetal) [](https://github.com/patelhiren)
|
||||
|
||||
[](https://github.com/BinHPdev) [](https://github.com/RyanLee-Dev) [](https://github.com/cathrynlavery) [](https://github.com/al3mart) [](https://github.com/JustYannicc) [](https://github.com/AbhisekBasu1) [](https://github.com/dbhurley) [](https://github.com/mpz4life) [](https://github.com/tmimmanuel) [](https://github.com/JustasMonkev)
|
||||
|
||||
[](https://github.com/simantak-dabhade) [](https://github.com/NicholasSpisak) [](https://github.com/natefikru) [](https://github.com/dunamismax) [](https://github.com/simonemacario) [](https://github.com/ENCHIGO) [](https://github.com/xingsy97) [](https://github.com/emonty) [](https://github.com/jadilson12) [](https://github.com/kirisame-wang)
|
||||
|
||||
[](https://github.com/mathiasnagler) [](https://github.com/Oceanswave) [](https://github.com/gumclaw) [](https://github.com/RichardCao) [](https://github.com/MKV21) [](https://github.com/petter-b) [](https://github.com/CodeForgeNet) [](https://github.com/johnsonshi) [](https://github.com/durenzidu) [](https://github.com/dougvk)
|
||||
|
||||
[](https://github.com/Whoaa512) [](https://github.com/zimeg) [](https://github.com/TsekaLuk) [](https://github.com/Ryan-Haines) [](https://github.com/uf-hy) [](https://github.com/Daanvdplas) [](https://github.com/bittoby) [](https://github.com/xuhao1) [](https://github.com/Lucenx9) [](https://github.com/HeMuling)
|
||||
|
||||
[](https://github.com/AaronLuo00) [](https://github.com/YUJIE2002) [](https://github.com/DhruvBhatia0) [](https://github.com/divanoli) [](https://github.com/derbronko) [](https://github.com/rubyrunsstuff) [](https://github.com/rabsef-bicrym) [](https://github.com/IVY-AI-gif) [](https://github.com/pvtclawn) [](https://github.com/stephenschoettler)
|
||||
|
||||
[](https://github.com/minupla) [](https://github.com/xzq-xu) [](https://github.com/mousberg) [](https://github.com/arifahmedjoy) [](https://github.com/harhogefoo) [](https://github.com/2233admin) [](https://github.com/ameno-) [](https://github.com/battman21) [](https://github.com/bcherny) [](https://github.com/bobashopcashier)
|
||||
|
||||
[](https://github.com/dguido) [](https://github.com/druide67) [](https://github.com/guirguispierre) [](https://github.com/jzakirov) [](https://github.com/loganprit) [](https://github.com/martinfrancois) [](https://github.com/neo1027144-creator) [](https://github.com/RealKai42) [](https://github.com/schumilin) [](https://github.com/shuofengzhang)
|
||||
|
||||
[](https://github.com/solstead) [](https://github.com/hengm3467) [](https://github.com/chziyue) [](https://github.com/jameslcowan) [](https://github.com/scifantastic) [](https://github.com/ryan-crabbe) [](https://github.com/alexfilatov) [](https://github.com/Luckymingxuan) [](https://github.com/Hollychou924) [](https://github.com/badlogic)
|
||||
|
||||
[](https://github.com/hnykda) [](https://github.com/dbachelder) [](https://github.com/heavenlost) [](https://github.com/shad0wca7) [](https://github.com/jared596) [](https://github.com/kiranjd) [](https://github.com/Mellowambience) [](https://github.com/KimGLee) [](https://github.com/seheepeak) [](https://github.com/TSavo)
|
||||
|
||||
[](https://github.com/mcrolly) [](https://github.com/dashed) [](https://github.com/Shuai-DaiDai) [](https://github.com/suboss87) [](https://github.com/emanuelst) [](https://github.com/magendary) [](https://github.com/PeterShanxin) [](https://github.com/j2h4u) [](https://github.com/bsormagec) [](https://github.com/mjamiv)
|
||||
|
||||
[](https://github.com/aerolalit) [](https://github.com/jessy2027) [](https://github.com/buddyh) [](https://github.com/aaron-he-zhu) [](https://github.com/hhhhao28) [](https://github.com/benostein) [](https://github.com/LyleLiu666) [](https://github.com/pingren) [](https://github.com/popomore) [](https://github.com/Dithilli)
|
||||
|
||||
[](https://github.com/fal3) [](https://github.com/mkbehr) [](https://github.com/mteam88) [](https://github.com/gupsammy) [](https://github.com/gut-puncture) [](https://github.com/garnetlyx) [](https://github.com/miloudbelarebia) [](https://github.com/Protocol-zero-0) [](https://github.com/pvoo) [](https://github.com/patrick-yingxi-pan)
|
||||
|
||||
[](https://github.com/ptahdunbar) [](https://github.com/keepitmello) [](https://github.com/artuskg) [](https://github.com/Anandesh-Sharma) [](https://github.com/zidongdesign) [](https://github.com/Innocent-children) [](https://github.com/El-Fitz) [](https://github.com/arthurbr11) [](https://github.com/jackheuberger) [](https://github.com/serkonyc)
|
||||
|
||||
[](https://github.com/guxu11) [](https://github.com/hyojin) [](https://github.com/jeann2013) [](https://github.com/jogelin) [](https://github.com/rmorse) [](https://github.com/scz2011) [](https://github.com/andyliu) [](https://github.com/benithors) [](https://github.com/xiwuqi) [](https://github.com/TigerInYourDream)
|
||||
|
||||
[](https://github.com/aaronagent) [](https://github.com/TonyDerek-dot) [](https://github.com/Zitzak) [](https://github.com/ruypang) [](https://github.com/stainlu) [](https://github.com/OpenCils) [](https://github.com/stefangalescu) [](https://github.com/sp-hk2ldn) [](https://github.com/MikeORed) [](https://github.com/graciegould)
|
||||
|
||||
[](https://github.com/cash-echo-bot) [](https://github.com/visionik) [](https://github.com/WalterSumbon) [](https://github.com/SubtleSpark) [](https://github.com/krizpoon) [](https://github.com/rodbland2021) [](https://github.com/thomasxm) [](https://github.com/sar618) [](https://github.com/fagemx) [](https://github.com/daymade)
|
||||
|
||||
[](https://github.com/tysoncung) [](https://github.com/pycckuu) [](https://github.com/omniwired) [](https://github.com/connorshea) [](https://github.com/bonald) [](https://github.com/BeeSting50) [](https://github.com/nachoiacovino) [](https://github.com/zhumengzhu) [](https://github.com/Vitalcheffe) [](https://github.com/zhoulongchao77)
|
||||
|
||||
[](https://github.com/navarrotech) [](https://github.com/CommanderCrowCode) [](https://github.com/paceyw) [](https://github.com/Aftabbs) [](https://github.com/Alex-Alaniz) [](https://github.com/jarvis-medmatic) [](https://github.com/tomron87) [](https://github.com/day253) [](https://github.com/Jaaneek) [](https://github.com/AnCoSONG)
|
||||
|
||||
[](https://github.com/ziomancer) [](https://github.com/shayan919293) [](https://github.com/edwluo) [](https://github.com/rjchien728) [](https://github.com/TinyTb) [](https://github.com/No898) [](https://github.com/ianderrington) [](https://github.com/L-U-C-K-Y) [](https://github.com/peschee) [](https://github.com/Kepler2024)
|
||||
|
||||
[](https://github.com/julianengel) [](https://github.com/markfietje) [](https://github.com/dakshaymehta) [](https://github.com/DavidNitZ) [](https://github.com/dominicnunez) [](https://github.com/danielwanwx) [](https://github.com/hongsw) [](https://github.com/Youyou972) [](https://github.com/boris721) [](https://github.com/damoahdominic)
|
||||
|
||||
[](https://github.com/dan-dr) [](https://github.com/doodlewind) [](https://github.com/kkarimi) [](https://github.com/brokemac79) [](https://github.com/ozbillwang) [](https://github.com/ravyg) [](https://github.com/jasonhargrove) [](https://github.com/BrianWang1990) [](https://github.com/hackersifu) [](https://github.com/Fologan)
|
||||
|
||||
[](https://github.com/AnonAmit) [](https://github.com/v1p0r) [](https://github.com/ajay99511) [](https://github.com/Iranb) [](https://github.com/yhyatt) [](https://github.com/codexGW) [](https://github.com/ShaunTsai) [](https://github.com/papago2355) [](https://github.com/cdorsey) [](https://github.com/tda1017)
|
||||
|
||||
[](https://github.com/0xJonHoldsCrypto) [](https://github.com/akyourowngames) [![clawdinator[bot]](https://avatars.githubusercontent.com/in/2607181?v=4&s=48)](https://github.com/apps/clawdinator) [](https://github.com/koala73) [](https://github.com/sircrumpet) [](https://github.com/thesomewhatyou) [](https://github.com/zats) [](https://github.com/duqaXxX) [](https://github.com/Joly0) [](https://github.com/hannasdev)
|
||||
|
||||
[](https://github.com/jlowin) [](https://github.com/peetzweg) [](https://github.com/adao-max) [](https://github.com/tumf) [](https://github.com/Huntterxx) [](https://github.com/nk1tz) [](https://github.com/lidamao633) [](https://github.com/liebertar) [](https://github.com/CornBrother0x) [](https://github.com/DukeDeSouth)
|
||||
|
||||
[](https://github.com/sahancava) [](https://github.com/CashWilliams) [](https://github.com/lumpinif) [](https://github.com/AdeboyeDN) [](https://github.com/Rohan5commit) [](https://github.com/srinivaspavan9) [](https://github.com/h0tp-ftw) [](https://github.com/neooriginal) [](https://github.com/Tianworld) [](https://github.com/Bermudarat)
|
||||
|
||||
[](https://github.com/asklee-klawd) [](https://github.com/yuting0624) [](https://github.com/constansino) [](https://github.com/ghsmc) [](https://github.com/ibrahimq21) [](https://github.com/irtiq7) [](https://github.com/kelvinCB) [](https://github.com/mitsuhiko) [](https://github.com/nohat) [](https://github.com/santiagomed)
|
||||
|
||||
[](https://github.com/suminhthanh) [](https://github.com/svkozak) [](https://github.com/zhangzhefang-github) [](https://github.com/HOYALIM) [](https://github.com/ping-Toven) [](https://github.com/0-CYBERDYNE-SYSTEMS-0) [](https://github.com/ylc0919) [](https://github.com/reed1898) [](https://github.com/ItsAditya-xyz) [](https://github.com/samrusani)
|
||||
|
||||
[](https://github.com/andyk-ms) [](https://github.com/18-RAJAT) [](https://github.com/cyb1278588254) [](https://github.com/zoherghadyali) [](https://github.com/manikv12) [](https://github.com/manueltarouca) [](https://github.com/GaosCode) [](https://github.com/pahdo) [](https://github.com/detecti1) [](https://github.com/JasonOA888)
|
||||
|
||||
[](https://github.com/sumukhj1219) [](https://github.com/bakhtiersizhaev) [](https://github.com/kyleok) [](https://github.com/AkashKobal) [](https://github.com/zhuisDEV) [](https://github.com/wu-tian807) [](https://github.com/vsabavat) [](https://github.com/kinfey) [](https://github.com/crimeacs) [](https://github.com/VibhorGautam)
|
||||
|
||||
[](https://github.com/John-Rood) [](https://github.com/velamints2) [](https://github.com/benjipeng) [](https://github.com/divisonofficer) [](https://github.com/Rahulkumar070) [](https://github.com/rockcent) [](https://github.com/Limitless2023) [](https://github.com/24601) [](https://github.com/awkoy) [](https://github.com/dawondyifraw)
|
||||
|
||||
[![google-labs-jules[bot]](https://avatars.githubusercontent.com/in/842251?v=4&s=48)](https://github.com/apps/google-labs-jules) [](https://github.com/henrino3) [](https://github.com/Kansodata) [](https://github.com/kaonash) [](https://github.com/p6l-richard) [](https://github.com/pi0) [](https://github.com/skainguyen1412) [](https://github.com/Starhappysh) [](https://github.com/xdanger) [](https://github.com/p3nchan)
|
||||
|
||||
[](https://github.com/scald) [](https://github.com/kashevk0) [](https://github.com/Yuandiaodiaodiao) [](https://github.com/doguabaris) [](https://github.com/ysqander) [](https://github.com/andranik-sahakyan) [](https://github.com/Wangnov) [](https://github.com/rixau) [](https://github.com/lisitan) [](https://github.com/kaizen403)
|
||||
|
||||
[](https://github.com/hirefrank) [](https://github.com/kennyklee) [](https://github.com/dddabtc) [](https://github.com/edincampara) [](https://github.com/fellanH) [](https://github.com/VarunChopra11) [](https://github.com/wangai-studio) [](https://github.com/sleontenko) [](https://github.com/yassine20011) [](https://github.com/ant1eicher)
|
||||
|
||||
[](https://github.com/ThomsenDrake) [](https://github.com/kakuteki) [](https://github.com/andreabadesso) [](https://github.com/chenxin-yan) [](https://github.com/cordx56) [](https://github.com/dvrshil) [](https://github.com/MarvinCui) [](https://github.com/Yeom-JinHo) [](https://github.com/17jmumford) [](https://github.com/KnHack)
|
||||
|
||||
[](https://github.com/SharoonSharif) [](https://github.com/orenyomtov) [](https://github.com/mattqdev) [](https://github.com/parkertoddbrooks) [](https://github.com/he-yufeng) [](https://github.com/Milofax) [](https://github.com/stevebot-alive) [](https://github.com/zhoulf1006) [](https://github.com/jrrcdev) [](https://github.com/feniix)
|
||||
|
||||
[](https://github.com/ZetiMente) [](https://github.com/QuantDeveloperUSA) [](https://github.com/alexstyl) [](https://github.com/ethanpalm) [](https://github.com/qkal) [](https://github.com/cygaar) [](https://github.com/U-C4N) [](https://github.com/jakobdylanc) [](https://github.com/antons) [](https://github.com/austinm911)
|
||||
|
||||
[](https://github.com/mahmoudashraf93) [](https://github.com/philipp-spiess) [](https://github.com/pkrmf) [](https://github.com/joshrad-dev) [](https://github.com/factnest365-ops) [](https://github.com/yingchunbai) [](https://github.com/aj47) [](https://github.com/Alg0rix) [](https://github.com/futhgar) [](https://github.com/YonganZhang)
|
||||
|
||||
[](https://github.com/remusao) [](https://github.com/danballance) [](https://github.com/GHesericsu) [](https://github.com/kimitaka) [](https://github.com/itsjling) [](https://github.com/RayBB) [](https://github.com/lutr0) [](https://github.com/claude) [](https://github.com/angrybirddd) [](https://github.com/fabianwilliams)
|
||||
|
||||
[](https://github.com/haoruilee) [](https://github.com/8BlT) [](https://github.com/atalovesyou) [](https://github.com/erikpr1994) [](https://github.com/jonasjancarik) [](https://github.com/longmaba) [](https://github.com/mitschabaude-bot) [](https://github.com/thesash) [](https://github.com/rdev) [](https://github.com/easternbloc)
|
||||
|
||||
[](https://github.com/chrisrodz) [](https://github.com/gabriel-trigo) [](https://github.com/manmal) [](https://github.com/neist) [](https://github.com/wes-davis) [](https://github.com/ManuelHettich) [](https://github.com/sktbrd) [](https://github.com/larlyssa) [](https://github.com/pcty-nextgen-service-account) [](https://github.com/Syhids)
|
||||
|
||||
[](https://github.com/tmchow) [](https://github.com/mgratch) [](https://github.com/xtao) [](https://github.com/JackyWay) [](https://github.com/j1philli) [](https://github.com/T5-AndyML) [](https://github.com/huohua-dev) [](https://github.com/imfing) [](https://github.com/RandyVentures) [](https://github.com/marcodd23)
|
||||
|
||||
[](https://github.com/Iamadig) [](https://github.com/humanwritten) [](https://github.com/robaxelsen) [](https://github.com/prathamdby) [](https://github.com/0oAstro) [](https://github.com/aaronn) [](https://github.com/afern247) [](https://github.com/Asleep123) [](https://github.com/dantelex) [](https://github.com/fcatuhe)
|
||||
|
||||
[](https://github.com/gtsifrikas) [](https://github.com/hrdwdmrbl) [](https://github.com/hugobarauna) [](https://github.com/jayhickey) [](https://github.com/jiulingyun) [](https://github.com/jdrhyne) [](https://github.com/jverdi) [](https://github.com/kitze) [](https://github.com/loukotal) [](https://github.com/minghinmatthewlam)
|
||||
|
||||
[](https://github.com/MSch) [](https://github.com/odrobnik) [](https://github.com/oswalpalash) [](https://github.com/ratulsarna) [](https://github.com/reeltimeapps) [](https://github.com/snopoke) [](https://github.com/sreekaransrinath) [](https://github.com/timkrase)
|
||||
|
||||
<!-- clawtributors:end -->
|
||||
|
||||
@@ -38,6 +38,7 @@ For fastest triage, include all of the following:
|
||||
- Tested version details (OpenClaw version and/or commit SHA).
|
||||
- Reproducible PoC against latest `main` or latest released version.
|
||||
- If the claim targets a released version, evidence from the shipped tag and published artifact/package for that exact version (not only `main`).
|
||||
- For dependency CVE reports, evidence that the shipped dependency version is actually affected, plus a PoC that reproduces impact through OpenClaw. Showing that OpenClaw can reach a native parser is not enough by itself.
|
||||
- Demonstrated impact tied to OpenClaw's documented trust boundaries.
|
||||
- For exposed-secret reports: proof the credential is OpenClaw-owned (or grants access to OpenClaw-operated infrastructure/services).
|
||||
- Explicit statement that the report does not rely on adversarial operators sharing one gateway host/config.
|
||||
@@ -62,6 +63,7 @@ These are frequently reported but are typically closed with no code change:
|
||||
- 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.
|
||||
- Reports that only show differences in heuristic detection/parity (for example obfuscation-pattern detection on one exec path but not another, such as `node.invoke -> system.run` parity gaps) without demonstrating bypass of auth, approvals, allowlist enforcement, sandboxing, or other documented trust boundaries.
|
||||
- Reports that only show an ACP tool can indirectly execute, mutate, orchestrate sessions, or reach another tool/runtime without demonstrating bypass of ACP prompt/approval, allowlist enforcement, sandboxing, or another documented trust boundary. ACP silent approval is intentionally limited to narrow readonly classes; parity-only indirect-command findings are hardening, not vulnerabilities.
|
||||
- Reports that only show untrusted media bytes reaching a maintained native decoder dependency (for example Sharp/libvips/libheif) without proving the shipped dependency version is vulnerable and demonstrating crash, memory corruption, data exposure, or a boundary bypass through OpenClaw. JavaScript header sniffing and image dimension fast-paths are preflight/UX checks, not the security boundary for native decoder correctness.
|
||||
- ReDoS/DoS claims that require trusted operator configuration input (for example catastrophic regex in `sessionFilter` or `logging.redactPatterns`) without a trust-boundary bypass.
|
||||
- Archive/install extraction claims that require pre-existing local filesystem priming in trusted state (for example planting symlink/hardlink aliases under destination directories such as skills/tools paths) without showing an untrusted path that can create/control that primitive.
|
||||
- Reports that depend on replacing or rewriting an already-approved executable path on a trusted host (same-path inode/content swap) without showing an untrusted path to perform that write.
|
||||
@@ -145,6 +147,7 @@ Plugins/extensions are part of OpenClaw's trusted computing base for a gateway.
|
||||
- Reports whose only claim is heuristic/parity drift in command-risk detection (for example obfuscation-pattern checks) across exec surfaces, without a demonstrated trust-boundary bypass. These are hardening-only findings and are not vulnerabilities; triage may close them as `invalid`/`no-action` or track them separately as low/informational hardening.
|
||||
- Reports whose only claim is that an ACP-exposed tool can indirectly execute commands, mutate host state, or reach another privileged tool/runtime without demonstrating a bypass of ACP prompt/approval, allowlist enforcement, sandboxing, or another documented trust boundary. These are hardening-only findings, not vulnerabilities.
|
||||
- Reports whose only claim is that exec approvals do not semantically model every interpreter/runtime loader form, subcommand, flag combination, package script, or transitive module/config import. Exec approvals bind exact request context and best-effort direct local file operands; they are not a complete semantic model of everything a runtime may load.
|
||||
- Reports whose only claim is parser reachability in an up-to-date maintained dependency without showing that the exact shipped dependency build is vulnerable. We keep native media dependencies current; dependency exposure alone is not a vulnerability.
|
||||
- Exposed secrets that are third-party/user-controlled credentials (not OpenClaw-owned and not granting access to OpenClaw-operated infrastructure/services) without demonstrated OpenClaw impact
|
||||
- Reports whose only claim is host-side exec when sandbox runtime is disabled/unavailable (documented default behavior in the trusted-operator model), without a boundary bypass.
|
||||
- Reports whose only claim is that a platform-provided upload destination URL is untrusted (for example Microsoft Teams `fileConsent/invoke` `uploadInfo.uploadUrl`) without proving attacker control in an authenticated production flow.
|
||||
|
||||
@@ -65,8 +65,8 @@ android {
|
||||
applicationId = "ai.openclaw.app"
|
||||
minSdk = 31
|
||||
targetSdk = 36
|
||||
versionCode = 2026041690
|
||||
versionName = "2026.4.16"
|
||||
versionCode = 2026042000
|
||||
versionName = "2026.4.20"
|
||||
ndk {
|
||||
// Support all major ABIs — native libs are tiny (~47 KB per ABI)
|
||||
abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
# OpenClaw iOS Changelog
|
||||
|
||||
## 2026.4.16 - 2026-04-17
|
||||
## 2026.4.20 - 2026-04-20
|
||||
|
||||
Maintenance update for the current OpenClaw release.
|
||||
|
||||
## 2026.4.19 - 2026-04-19
|
||||
|
||||
Maintenance update for the current OpenClaw beta release.
|
||||
|
||||
## 2026.4.18 - 2026-04-18
|
||||
|
||||
Maintenance update for the current OpenClaw release.
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// Source of truth: apps/ios/version.json
|
||||
// Generated by scripts/ios-sync-versioning.ts.
|
||||
|
||||
OPENCLAW_IOS_VERSION = 2026.4.16
|
||||
OPENCLAW_MARKETING_VERSION = 2026.4.16
|
||||
OPENCLAW_IOS_VERSION = 2026.4.20
|
||||
OPENCLAW_MARKETING_VERSION = 2026.4.20
|
||||
OPENCLAW_BUILD_VERSION = 1
|
||||
|
||||
#include? "../build/Version.xcconfig"
|
||||
|
||||
@@ -1060,7 +1060,8 @@ private final class GatewayTLSFingerprintProbe: NSObject, URLSessionDelegate, @u
|
||||
}
|
||||
|
||||
private func finish(_ fingerprint: String?) {
|
||||
let (shouldComplete, taskToCancel, sessionToInvalidate) = self.state.withLock { s -> (Bool, URLSessionWebSocketTask?, URLSession?) in
|
||||
typealias FinishState = (Bool, URLSessionWebSocketTask?, URLSession?)
|
||||
let (shouldComplete, taskToCancel, sessionToInvalidate) = self.state.withLock { s -> FinishState in
|
||||
guard !s.didFinish else { return (false, nil, nil) }
|
||||
s.didFinish = true
|
||||
let task = s.task
|
||||
|
||||
@@ -292,7 +292,9 @@ enum GatewaySettingsStore {
|
||||
let port = defaults.object(forKey: self.lastGatewayPortDefaultsKey) as? Int
|
||||
|
||||
let payload = LastGatewayConnectionData(
|
||||
kind: kind, stableID: stableID, useTLS: useTLS,
|
||||
kind: kind,
|
||||
stableID: stableID,
|
||||
useTLS: useTLS,
|
||||
host: kind == .manual ? host : nil,
|
||||
port: kind == .manual ? port : nil)
|
||||
guard self.saveLastGatewayConnectionData(payload) else { return }
|
||||
|
||||
@@ -136,7 +136,10 @@ private struct HomeToolbarStatusButton: View {
|
||||
.buttonStyle(.plain)
|
||||
.accessibilityLabel("Connection Status")
|
||||
.accessibilityValue(self.accessibilityValue)
|
||||
.accessibilityHint(self.gateway == .connected ? "Double tap for gateway actions" : "Double tap to open settings")
|
||||
.accessibilityHint(
|
||||
self.gateway == .connected
|
||||
? "Double tap for gateway actions"
|
||||
: "Double tap to open settings")
|
||||
.onAppear { self.updatePulse(for: self.gateway, scenePhase: self.scenePhase, reduceMotion: self.reduceMotion) }
|
||||
.onDisappear { self.pulse = false }
|
||||
.onChange(of: self.gateway) { _, newValue in
|
||||
|
||||
@@ -234,7 +234,9 @@ final class NodeAppModel {
|
||||
self.watchMessagingService.setStatusHandler { [weak self] status in
|
||||
Task { @MainActor in
|
||||
GatewayDiagnostics.log(
|
||||
"node app model: watch status callback reachable=\(status.reachable) activation=\(status.activationState) backgrounded=\(self?.isBackgrounded ?? false)")
|
||||
"node app model: watch status callback "
|
||||
+ "reachable=\(status.reachable) activation=\(status.activationState) "
|
||||
+ "backgrounded=\(self?.isBackgrounded ?? false)")
|
||||
await self?.handleWatchMessagingStatusChanged(status)
|
||||
}
|
||||
}
|
||||
@@ -924,7 +926,9 @@ final class NodeAppModel {
|
||||
self.screen.showDefaultCanvas()
|
||||
} else {
|
||||
let trustedA2UIURL = await self.resolveA2UIHostURL()
|
||||
self.screen.navigate(to: url, trustA2UIActions: trustedA2UIURL == Self.normalizeURLForTrustComparison(url))
|
||||
self.screen.navigate(
|
||||
to: url,
|
||||
trustA2UIActions: trustedA2UIURL == Self.normalizeURLForTrustComparison(url))
|
||||
}
|
||||
return BridgeInvokeResponse(id: req.id, ok: true)
|
||||
case OpenClawCanvasCommand.hide.rawValue:
|
||||
@@ -934,7 +938,9 @@ final class NodeAppModel {
|
||||
let params = try Self.decodeParams(OpenClawCanvasNavigateParams.self, from: req.paramsJSON)
|
||||
let trimmedURL = params.url.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let trustedA2UIURL = await self.resolveA2UIHostURL()
|
||||
self.screen.navigate(to: trimmedURL, trustA2UIActions: trustedA2UIURL == Self.normalizeURLForTrustComparison(trimmedURL))
|
||||
self.screen.navigate(
|
||||
to: trimmedURL,
|
||||
trustA2UIActions: trustedA2UIURL == Self.normalizeURLForTrustComparison(trimmedURL))
|
||||
return BridgeInvokeResponse(id: req.id, ok: true)
|
||||
case OpenClawCanvasCommand.evalJS.rawValue:
|
||||
let params = try Self.decodeParams(OpenClawCanvasEvalParams.self, from: req.paramsJSON)
|
||||
@@ -2562,8 +2568,8 @@ extension NodeAppModel {
|
||||
PendingForegroundNodeActionsResponse.self,
|
||||
from: payload)
|
||||
guard !decoded.actions.isEmpty else { return }
|
||||
self.pendingActionLogger.info(
|
||||
"Pending actions pulled trigger=\(trigger, privacy: .public) count=\(decoded.actions.count, privacy: .public)")
|
||||
// swiftlint:disable:next line_length
|
||||
self.pendingActionLogger.info("Pending actions pulled trigger=\(trigger, privacy: .public) count=\(decoded.actions.count, privacy: .public)")
|
||||
await self.applyPendingForegroundNodeActions(decoded.actions, trigger: trigger)
|
||||
} catch {
|
||||
// Best-effort only.
|
||||
@@ -2585,8 +2591,8 @@ extension NodeAppModel {
|
||||
command: action.command,
|
||||
paramsJSON: action.paramsJSON)
|
||||
let result = await self.handleInvoke(req)
|
||||
self.pendingActionLogger.info(
|
||||
"Pending action replay trigger=\(trigger, privacy: .public) id=\(action.id, privacy: .public) command=\(action.command, privacy: .public) ok=\(result.ok, privacy: .public)")
|
||||
// swiftlint:disable:next line_length
|
||||
self.pendingActionLogger.info("Pending action replay trigger=\(trigger, privacy: .public) id=\(action.id, privacy: .public) command=\(action.command, privacy: .public) ok=\(result.ok, privacy: .public)")
|
||||
guard result.ok else { return }
|
||||
let acked = await self.ackPendingForegroundNodeAction(
|
||||
id: action.id,
|
||||
@@ -2603,15 +2609,15 @@ extension NodeAppModel {
|
||||
{
|
||||
do {
|
||||
let payload = try JSONEncoder().encode(PendingForegroundNodeActionsAckRequest(ids: [id]))
|
||||
let paramsJSON = String(decoding: payload, as: UTF8.self)
|
||||
let paramsJSON = String(bytes: payload, encoding: .utf8) ?? "{}"
|
||||
_ = try await self.nodeGateway.request(
|
||||
method: "node.pending.ack",
|
||||
paramsJSON: paramsJSON,
|
||||
timeoutSeconds: 6)
|
||||
return true
|
||||
} catch {
|
||||
self.pendingActionLogger.error(
|
||||
"Pending action ack failed trigger=\(trigger, privacy: .public) id=\(id, privacy: .public) command=\(command, privacy: .public) error=\(String(describing: error), privacy: .public)")
|
||||
// swiftlint:disable:next line_length
|
||||
self.pendingActionLogger.error("Pending action ack failed trigger=\(trigger, privacy: .public) id=\(id, privacy: .public) command=\(command, privacy: .public) error=\(String(describing: error), privacy: .public)")
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -2623,7 +2629,7 @@ extension NodeAppModel {
|
||||
case .deduped(let replyId):
|
||||
self.watchReplyLogger.debug(
|
||||
"watch reply deduped replyId=\(replyId, privacy: .public)")
|
||||
case .queue(let replyId, let actionId):
|
||||
case let .queue(replyId, actionId):
|
||||
self.watchReplyLogger.info(
|
||||
"watch reply queued replyId=\(replyId, privacy: .public) action=\(actionId, privacy: .public)")
|
||||
case .forward:
|
||||
@@ -2737,7 +2743,9 @@ extension NodeAppModel {
|
||||
|
||||
private func handleWatchMessagingStatusChanged(_ status: WatchMessagingStatus) async {
|
||||
GatewayDiagnostics.log(
|
||||
"watch exec approval: status changed reachable=\(status.reachable) activation=\(status.activationState) backgrounded=\(self.isBackgrounded)")
|
||||
"watch exec approval: status changed "
|
||||
+ "reachable=\(status.reachable) activation=\(status.activationState) "
|
||||
+ "backgrounded=\(self.isBackgrounded)")
|
||||
guard self.isBackgrounded else { return }
|
||||
guard status.supported, status.paired, status.appInstalled else { return }
|
||||
guard status.reachable || status.activationState == "activated" else { return }
|
||||
@@ -2752,7 +2760,8 @@ extension NodeAppModel {
|
||||
self.pendingWatchExecApprovalRecoveryIDs.append(normalizedApprovalID)
|
||||
self.pendingWatchExecApprovalRecoveryIDs.sort()
|
||||
GatewayDiagnostics.log(
|
||||
"watch exec approval: queued recovery id=\(normalizedApprovalID) pendingCount=\(self.pendingWatchExecApprovalRecoveryIDs.count)")
|
||||
"watch exec approval: queued recovery "
|
||||
+ "id=\(normalizedApprovalID) pendingCount=\(self.pendingWatchExecApprovalRecoveryIDs.count)")
|
||||
self.persistWatchExecApprovalBridgeState()
|
||||
}
|
||||
|
||||
@@ -2763,7 +2772,8 @@ extension NodeAppModel {
|
||||
self.pendingWatchExecApprovalRecoveryIDs.removeAll { $0 == normalizedApprovalID }
|
||||
guard self.pendingWatchExecApprovalRecoveryIDs.count != originalCount else { return }
|
||||
GatewayDiagnostics.log(
|
||||
"watch exec approval: cleared recovery id=\(normalizedApprovalID) pendingCount=\(self.pendingWatchExecApprovalRecoveryIDs.count)")
|
||||
"watch exec approval: cleared recovery "
|
||||
+ "id=\(normalizedApprovalID) pendingCount=\(self.pendingWatchExecApprovalRecoveryIDs.count)")
|
||||
self.persistWatchExecApprovalBridgeState()
|
||||
}
|
||||
|
||||
@@ -2818,8 +2828,8 @@ extension NodeAppModel {
|
||||
self.watchExecApprovalLogger.debug(
|
||||
"watch exec approval prompt sent id=\(prompt.id, privacy: .public) reason=\(reason, privacy: .public)")
|
||||
} catch {
|
||||
self.watchExecApprovalLogger.error(
|
||||
"watch exec approval prompt failed id=\(prompt.id, privacy: .public) reason=\(reason, privacy: .public) error=\(error.localizedDescription, privacy: .public)")
|
||||
// swiftlint:disable:next line_length
|
||||
self.watchExecApprovalLogger.error("watch exec approval prompt failed id=\(prompt.id, privacy: .public) reason=\(reason, privacy: .public) error=\(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
await self.syncWatchExecApprovalSnapshot(reason: "\(reason)_snapshot")
|
||||
}
|
||||
@@ -2840,8 +2850,8 @@ extension NodeAppModel {
|
||||
do {
|
||||
_ = try await self.watchMessagingService.sendExecApprovalResolved(message)
|
||||
} catch {
|
||||
self.watchExecApprovalLogger.error(
|
||||
"watch exec approval resolved update failed id=\(normalizedApprovalID, privacy: .public) error=\(error.localizedDescription, privacy: .public)")
|
||||
// swiftlint:disable:next line_length
|
||||
self.watchExecApprovalLogger.error("watch exec approval resolved update failed id=\(normalizedApprovalID, privacy: .public) error=\(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
await self.syncWatchExecApprovalSnapshot(reason: "resolved_snapshot")
|
||||
}
|
||||
@@ -2860,8 +2870,8 @@ extension NodeAppModel {
|
||||
do {
|
||||
_ = try await self.watchMessagingService.sendExecApprovalExpired(message)
|
||||
} catch {
|
||||
self.watchExecApprovalLogger.error(
|
||||
"watch exec approval expiry update failed id=\(normalizedApprovalID, privacy: .public) error=\(error.localizedDescription, privacy: .public)")
|
||||
// swiftlint:disable:next line_length
|
||||
self.watchExecApprovalLogger.error("watch exec approval expiry update failed id=\(normalizedApprovalID, privacy: .public) error=\(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
await self.syncWatchExecApprovalSnapshot(reason: "expired_\(reason.rawValue)")
|
||||
}
|
||||
@@ -2869,7 +2879,9 @@ extension NodeAppModel {
|
||||
private func syncWatchExecApprovalSnapshot(reason: String) async {
|
||||
self.pruneExpiredWatchExecApprovalPrompts()
|
||||
GatewayDiagnostics.log(
|
||||
"watch exec approval: sync snapshot start reason=\(reason) cacheCount=\(self.watchExecApprovalPromptsByID.count) backgrounded=\(self.isBackgrounded)")
|
||||
"watch exec approval: sync snapshot start "
|
||||
+ "reason=\(reason) cacheCount=\(self.watchExecApprovalPromptsByID.count) "
|
||||
+ "backgrounded=\(self.isBackgrounded)")
|
||||
let approvals = self.watchExecApprovalPromptsByID.values
|
||||
.sorted { lhs, rhs in
|
||||
let lhsExpires = lhs.expiresAtMs ?? Int.max
|
||||
@@ -2888,13 +2900,13 @@ extension NodeAppModel {
|
||||
_ = try await self.watchMessagingService.syncExecApprovalSnapshot(message)
|
||||
GatewayDiagnostics.log(
|
||||
"watch exec approval: sync snapshot sent reason=\(reason) count=\(approvals.count)")
|
||||
self.watchExecApprovalLogger.debug(
|
||||
"watch exec approval snapshot sent reason=\(reason, privacy: .public) count=\(approvals.count, privacy: .public)")
|
||||
// swiftlint:disable:next line_length
|
||||
self.watchExecApprovalLogger.debug("watch exec approval snapshot sent reason=\(reason, privacy: .public) count=\(approvals.count, privacy: .public)")
|
||||
} catch {
|
||||
GatewayDiagnostics.log(
|
||||
"watch exec approval: sync snapshot failed reason=\(reason) error=\(error.localizedDescription)")
|
||||
self.watchExecApprovalLogger.error(
|
||||
"watch exec approval snapshot failed reason=\(reason, privacy: .public) error=\(error.localizedDescription, privacy: .public)")
|
||||
// swiftlint:disable:next line_length
|
||||
self.watchExecApprovalLogger.error("watch exec approval snapshot failed reason=\(reason, privacy: .public) error=\(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2933,7 +2945,10 @@ extension NodeAppModel {
|
||||
candidateIDs: approvalIDs,
|
||||
cachedApprovalIDs: Array(self.watchExecApprovalPromptsByID.keys))
|
||||
GatewayDiagnostics.log(
|
||||
"watch exec approval: hydrate candidates reason=\(reason) ids=\(approvalIDs.joined(separator: ",")) missing=\(missingApprovalIDs.joined(separator: ",")) cached=\(self.watchExecApprovalPromptsByID.count)")
|
||||
"watch exec approval: hydrate candidates "
|
||||
+ "reason=\(reason) ids=\(approvalIDs.joined(separator: ",")) "
|
||||
+ "missing=\(missingApprovalIDs.joined(separator: ",")) "
|
||||
+ "cached=\(self.watchExecApprovalPromptsByID.count)")
|
||||
guard !missingApprovalIDs.isEmpty else {
|
||||
self.watchExecApprovalLogger.debug(
|
||||
"watch exec approval hydrate skipped reason=\(reason, privacy: .public): no missing approval ids")
|
||||
@@ -2957,8 +2972,8 @@ extension NodeAppModel {
|
||||
forApprovalID: approvalId,
|
||||
notificationCenter: self.notificationCenter)
|
||||
case let .failed(message):
|
||||
self.watchExecApprovalLogger.error(
|
||||
"watch exec approval hydrate failed id=\(approvalId, privacy: .public) reason=\(reason, privacy: .public) error=\(message, privacy: .public)")
|
||||
// swiftlint:disable:next line_length
|
||||
self.watchExecApprovalLogger.error("watch exec approval hydrate failed id=\(approvalId, privacy: .public) reason=\(reason, privacy: .public) error=\(message, privacy: .public)")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3039,8 +3054,8 @@ extension NodeAppModel {
|
||||
reason: .notFound)
|
||||
return true
|
||||
case let .failed(message):
|
||||
self.watchExecApprovalLogger.error(
|
||||
"watch exec approval push fetch failed id=\(normalizedApprovalID, privacy: .public) error=\(message, privacy: .public)")
|
||||
// swiftlint:disable:next line_length
|
||||
self.watchExecApprovalLogger.error("watch exec approval push fetch failed id=\(normalizedApprovalID, privacy: .public) error=\(message, privacy: .public)")
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -3086,13 +3101,15 @@ extension NodeAppModel {
|
||||
return true
|
||||
}
|
||||
|
||||
if ExecApprovalNotificationBridge.payloadKind(userInfo: userInfo) == ExecApprovalNotificationBridge.requestedKind,
|
||||
let execApprovalPushKind = ExecApprovalNotificationBridge.payloadKind(userInfo: userInfo)
|
||||
let isExecApprovalRequestPush = execApprovalPushKind == ExecApprovalNotificationBridge.requestedKind
|
||||
if isExecApprovalRequestPush,
|
||||
let approvalId = ExecApprovalNotificationBridge.approvalID(from: userInfo)
|
||||
{
|
||||
let handled = await self.handleExecApprovalRequestedRemotePush(approvalId: approvalId)
|
||||
if handled {
|
||||
self.execApprovalNotificationLogger.info(
|
||||
"Handled exec approval request push wakeId=\(wakeId, privacy: .public) id=\(approvalId, privacy: .public)")
|
||||
// swiftlint:disable:next line_length
|
||||
self.execApprovalNotificationLogger.info("Handled exec approval request push wakeId=\(wakeId, privacy: .public) id=\(approvalId, privacy: .public)")
|
||||
}
|
||||
return handled
|
||||
}
|
||||
@@ -3313,8 +3330,8 @@ extension NodeAppModel {
|
||||
self.clearPendingExecApprovalPromptIfMatches(approvalId)
|
||||
await self.publishWatchExecApprovalExpired(approvalId: approvalId, reason: .notFound)
|
||||
case let .failed(message):
|
||||
self.execApprovalNotificationLogger.error(
|
||||
"Exec approval prompt fetch failed id=\(approvalId, privacy: .public) reason=\(message, privacy: .public)")
|
||||
// swiftlint:disable:next line_length
|
||||
self.execApprovalNotificationLogger.error("Exec approval prompt fetch failed id=\(approvalId, privacy: .public) reason=\(message, privacy: .public)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3417,7 +3434,9 @@ extension NodeAppModel {
|
||||
return .stale
|
||||
}
|
||||
GatewayDiagnostics.log(
|
||||
"watch exec approval: fetch prompt failed id=\(approvalId) reason=\(fetchReason) error=\(error.localizedDescription)")
|
||||
"watch exec approval: fetch prompt failed "
|
||||
+ "id=\(approvalId) reason=\(fetchReason) "
|
||||
+ "error=\(error.localizedDescription)")
|
||||
return .failed(message: error.localizedDescription)
|
||||
}
|
||||
}
|
||||
@@ -3647,28 +3666,33 @@ extension NodeAppModel {
|
||||
let reconnectReason = normalizedReason.isEmpty ? "watch_request" : normalizedReason
|
||||
if await self.isOperatorConnected() {
|
||||
GatewayDiagnostics.log(
|
||||
"watch exec approval: watch_request_reconnect_connected reason=\(reconnectReason) phase=already_connected")
|
||||
"watch exec approval: watch_request_reconnect_connected "
|
||||
+ "reason=\(reconnectReason) phase=already_connected")
|
||||
return true
|
||||
}
|
||||
|
||||
guard self.isBackgrounded else {
|
||||
GatewayDiagnostics.log(
|
||||
"watch exec approval: watch_request_reconnect_begin reason=\(reconnectReason) backgrounded=false strategy=default")
|
||||
"watch exec approval: watch_request_reconnect_begin "
|
||||
+ "reason=\(reconnectReason) backgrounded=false strategy=default")
|
||||
let connected = await self.ensureOperatorApprovalConnection(timeoutMs: timeoutMs)
|
||||
GatewayDiagnostics.log(
|
||||
"watch exec approval: watch_request_reconnect_\(connected ? "connected" : "timeout") reason=\(reconnectReason) phase=foreground_delegate")
|
||||
"watch exec approval: watch_request_reconnect_\(connected ? "connected" : "timeout") "
|
||||
+ "reason=\(reconnectReason) phase=foreground_delegate")
|
||||
return connected
|
||||
}
|
||||
|
||||
guard self.gatewayAutoReconnectEnabled else {
|
||||
GatewayDiagnostics.log(
|
||||
"watch exec approval: watch_request_reconnect_timeout reason=\(reconnectReason) phase=auto_reconnect_disabled")
|
||||
"watch exec approval: watch_request_reconnect_timeout "
|
||||
+ "reason=\(reconnectReason) phase=auto_reconnect_disabled")
|
||||
return false
|
||||
}
|
||||
|
||||
guard let cfg = self.activeGatewayConnectConfig else {
|
||||
GatewayDiagnostics.log(
|
||||
"watch exec approval: watch_request_reconnect_timeout reason=\(reconnectReason) phase=no_active_gateway_config")
|
||||
"watch exec approval: watch_request_reconnect_timeout "
|
||||
+ "reason=\(reconnectReason) phase=no_active_gateway_config")
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -3677,7 +3701,8 @@ extension NodeAppModel {
|
||||
let leaseSeconds = min(45.0, max(15.0, Double(max(timeoutMs, 1_000)) / 1000.0 + 8.0))
|
||||
self.grantBackgroundReconnectLease(seconds: leaseSeconds, reason: "watch_review_\(reconnectReason)")
|
||||
GatewayDiagnostics.log(
|
||||
"watch exec approval: watch_request_reconnect_lease_granted reason=\(reconnectReason) seconds=\(leaseSeconds)")
|
||||
"watch exec approval: watch_request_reconnect_lease_granted "
|
||||
+ "reason=\(reconnectReason) seconds=\(leaseSeconds)")
|
||||
|
||||
let hadReconnectLoop = self.operatorGatewayTask != nil
|
||||
let canStartReconnectLoop = hadReconnectLoop || self.shouldStartOperatorGatewayLoop(
|
||||
@@ -3687,20 +3712,24 @@ extension NodeAppModel {
|
||||
stableID: cfg.effectiveStableID)
|
||||
guard canStartReconnectLoop else {
|
||||
GatewayDiagnostics.log(
|
||||
"watch exec approval: watch_request_reconnect_timeout reason=\(reconnectReason) phase=no_operator_reconnect_auth")
|
||||
"watch exec approval: watch_request_reconnect_timeout "
|
||||
+ "reason=\(reconnectReason) phase=no_operator_reconnect_auth")
|
||||
return false
|
||||
}
|
||||
|
||||
self.ensureOperatorReconnectLoopIfNeeded()
|
||||
GatewayDiagnostics.log(
|
||||
"watch exec approval: watch_request_reconnect_loop_\(hadReconnectLoop ? "reused" : "started") reason=\(reconnectReason)")
|
||||
"watch exec approval: watch_request_reconnect_loop_\(hadReconnectLoop ? "reused" : "started") "
|
||||
+ "reason=\(reconnectReason)")
|
||||
|
||||
let initialWaitMs = min(2_500, max(750, timeoutMs / 4))
|
||||
GatewayDiagnostics.log(
|
||||
"watch exec approval: watch_request_reconnect_wait reason=\(reconnectReason) phase=initial timeoutMs=\(initialWaitMs)")
|
||||
"watch exec approval: watch_request_reconnect_wait "
|
||||
+ "reason=\(reconnectReason) phase=initial timeoutMs=\(initialWaitMs)")
|
||||
if await self.waitForOperatorConnection(timeoutMs: initialWaitMs, pollMs: 200) {
|
||||
GatewayDiagnostics.log(
|
||||
"watch exec approval: watch_request_reconnect_connected reason=\(reconnectReason) phase=initial")
|
||||
"watch exec approval: watch_request_reconnect_connected "
|
||||
+ "reason=\(reconnectReason) phase=initial")
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -3725,10 +3754,12 @@ extension NodeAppModel {
|
||||
|
||||
let remainingWaitMs = max(250, timeoutMs - initialWaitMs)
|
||||
GatewayDiagnostics.log(
|
||||
"watch exec approval: watch_request_reconnect_wait reason=\(reconnectReason) phase=restart timeoutMs=\(remainingWaitMs)")
|
||||
"watch exec approval: watch_request_reconnect_wait "
|
||||
+ "reason=\(reconnectReason) phase=restart timeoutMs=\(remainingWaitMs)")
|
||||
let connected = await self.waitForOperatorConnection(timeoutMs: remainingWaitMs, pollMs: 200)
|
||||
GatewayDiagnostics.log(
|
||||
"watch exec approval: watch_request_reconnect_\(connected ? "connected" : "timeout") reason=\(reconnectReason) phase=restart")
|
||||
"watch exec approval: watch_request_reconnect_\(connected ? "connected" : "timeout") "
|
||||
+ "reason=\(reconnectReason) phase=restart")
|
||||
return connected
|
||||
}
|
||||
|
||||
|
||||
@@ -338,7 +338,9 @@ struct OnboardingWizardView: View {
|
||||
Text("Security notice")
|
||||
.font(.headline)
|
||||
Text(
|
||||
"The connected OpenClaw agent can use device capabilities you enable, such as camera, microphone, photos, contacts, calendar, and location. Continue only if you trust the gateway and agent you connect to.")
|
||||
"The connected OpenClaw agent can use device capabilities you enable, "
|
||||
+ "such as camera, microphone, photos, contacts, calendar, and location. "
|
||||
+ "Continue only if you trust the gateway and agent you connect to.")
|
||||
.font(.footnote)
|
||||
.foregroundStyle(.secondary)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
|
||||
@@ -13,8 +13,7 @@ private func sendReachableWatchMessage(_ payload: [String: Any], with session: W
|
||||
// WatchConnectivity replies arrive on its own queue. Keep this continuation explicitly
|
||||
// nonisolated so Swift 6 does not inherit a caller actor (for example MainActor) into the
|
||||
// Objective-C callback boundary and trap on the reply callback executor check.
|
||||
try await withCheckedThrowingContinuation(isolation: nil) {
|
||||
(continuation: CheckedContinuation<Void, Error>) in
|
||||
try await withCheckedThrowingContinuation(isolation: nil) { (continuation: CheckedContinuation<Void, Error>) in
|
||||
session.sendMessage(
|
||||
payload,
|
||||
replyHandler: { _ in
|
||||
@@ -259,7 +258,9 @@ extension WatchConnectivityTransport: WCSessionDelegate {
|
||||
error: (any Error)?)
|
||||
{
|
||||
GatewayDiagnostics.log(
|
||||
"watch messaging: activation complete state=\(Self.activationStateLabel(activationState)) error=\(error?.localizedDescription ?? "none")")
|
||||
"watch messaging: activation complete "
|
||||
+ "state=\(Self.activationStateLabel(activationState)) "
|
||||
+ "error=\(error?.localizedDescription ?? "none")")
|
||||
if let error {
|
||||
Self.logger.error("watch activation failed: \(error.localizedDescription, privacy: .public)")
|
||||
} else {
|
||||
@@ -357,7 +358,9 @@ extension WatchConnectivityTransport: WCSessionDelegate {
|
||||
|
||||
func sessionReachabilityDidChange(_ session: WCSession) {
|
||||
GatewayDiagnostics.log(
|
||||
"watch messaging: reachability changed reachable=\(session.isReachable) paired=\(session.isPaired) installed=\(session.isWatchAppInstalled)")
|
||||
"watch messaging: reachability changed "
|
||||
+ "reachable=\(session.isReachable) paired=\(session.isPaired) "
|
||||
+ "installed=\(session.isWatchAppInstalled)")
|
||||
self.emitStatusUpdate(Self.status(for: session))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +74,10 @@ final class WatchMessagingService: @preconcurrency WatchMessagingServicing {
|
||||
let snapshot = self.transport.currentStatusSnapshot()
|
||||
self.lastEmittedStatus = snapshot
|
||||
GatewayDiagnostics.log(
|
||||
"watch messaging: set status handler supported=\(snapshot.supported) paired=\(snapshot.paired) appInstalled=\(snapshot.appInstalled) reachable=\(snapshot.reachable) activation=\(snapshot.activationState)")
|
||||
"watch messaging: set status handler "
|
||||
+ "supported=\(snapshot.supported) paired=\(snapshot.paired) "
|
||||
+ "appInstalled=\(snapshot.appInstalled) reachable=\(snapshot.reachable) "
|
||||
+ "activation=\(snapshot.activationState)")
|
||||
handler(snapshot)
|
||||
}
|
||||
|
||||
@@ -134,7 +137,10 @@ final class WatchMessagingService: @preconcurrency WatchMessagingServicing {
|
||||
}
|
||||
self.lastEmittedStatus = snapshot
|
||||
GatewayDiagnostics.log(
|
||||
"watch messaging: status supported=\(snapshot.supported) paired=\(snapshot.paired) appInstalled=\(snapshot.appInstalled) reachable=\(snapshot.reachable) activation=\(snapshot.activationState)")
|
||||
"watch messaging: status "
|
||||
+ "supported=\(snapshot.supported) paired=\(snapshot.paired) "
|
||||
+ "appInstalled=\(snapshot.appInstalled) reachable=\(snapshot.reachable) "
|
||||
+ "activation=\(snapshot.activationState)")
|
||||
self.statusHandler?(snapshot)
|
||||
}
|
||||
|
||||
@@ -148,7 +154,9 @@ final class WatchMessagingService: @preconcurrency WatchMessagingServicing {
|
||||
|
||||
private func emitExecApprovalSnapshotRequest(_ event: WatchExecApprovalSnapshotRequestEvent) {
|
||||
GatewayDiagnostics.log(
|
||||
"watch messaging: snapshot request id=\(event.requestId) transport=\(event.transport) sentAtMs=\(event.sentAtMs ?? -1)")
|
||||
"watch messaging: snapshot request "
|
||||
+ "id=\(event.requestId) transport=\(event.transport) "
|
||||
+ "sentAtMs=\(event.sentAtMs ?? -1)")
|
||||
self.execApprovalSnapshotRequestHandler?(event)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1008,7 +1008,9 @@ final class TalkModeManager: NSObject {
|
||||
self.logger.warning("unknown voice alias \(requestedVoice ?? "?", privacy: .public)")
|
||||
}
|
||||
|
||||
let configuredKey = self.apiKey?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false ? self.apiKey : nil
|
||||
let configuredKey = self.apiKey?
|
||||
.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
.isEmpty == false ? self.apiKey : nil
|
||||
#if DEBUG
|
||||
let resolvedKey = configuredKey ?? ProcessInfo.processInfo.environment["ELEVENLABS_API_KEY"]
|
||||
#else
|
||||
@@ -1514,7 +1516,9 @@ final class TalkModeManager: NSObject {
|
||||
"talk output_format unsupported for local playback: \(requestedOutputFormat, privacy: .public)")
|
||||
}
|
||||
|
||||
let configuredKey = self.apiKey?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false ? self.apiKey : nil
|
||||
let configuredKey = self.apiKey?
|
||||
.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
.isEmpty == false ? self.apiKey : nil
|
||||
#if DEBUG
|
||||
let resolvedKey = configuredKey ?? ProcessInfo.processInfo.environment["ELEVENLABS_API_KEY"]
|
||||
#else
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"version": "2026.4.16"
|
||||
"version": "2026.4.20"
|
||||
}
|
||||
|
||||
@@ -30,6 +30,26 @@ final class AppState {
|
||||
case direct
|
||||
}
|
||||
|
||||
struct RemoteGatewayConfigDraft {
|
||||
var transport: RemoteTransport
|
||||
var remoteUrl: String
|
||||
var remoteHost: String?
|
||||
var remoteTarget: String
|
||||
var remoteIdentity: String
|
||||
var remoteToken: String
|
||||
var remoteTokenDirty: Bool
|
||||
}
|
||||
|
||||
struct GatewayConfigSyncDraft {
|
||||
var connectionMode: ConnectionMode
|
||||
var remoteTransport: RemoteTransport
|
||||
var remoteTarget: String
|
||||
var remoteIdentity: String
|
||||
var remoteUrl: String
|
||||
var remoteToken: String
|
||||
var remoteTokenDirty: Bool
|
||||
}
|
||||
|
||||
var isPaused: Bool {
|
||||
didSet { self.ifNotPreview { UserDefaults.standard.set(self.isPaused, forKey: pauseDefaultsKey) } }
|
||||
}
|
||||
@@ -420,25 +440,19 @@ final class AppState {
|
||||
|
||||
private static func updatedRemoteGatewayConfig(
|
||||
current: [String: Any],
|
||||
transport: RemoteTransport,
|
||||
remoteUrl: String,
|
||||
remoteHost: String?,
|
||||
remoteTarget: String,
|
||||
remoteIdentity: String,
|
||||
remoteToken: String,
|
||||
remoteTokenDirty: Bool) -> (remote: [String: Any], changed: Bool)
|
||||
draft: RemoteGatewayConfigDraft) -> (remote: [String: Any], changed: Bool)
|
||||
{
|
||||
var remote = current
|
||||
var changed = false
|
||||
|
||||
switch transport {
|
||||
switch draft.transport {
|
||||
case .direct:
|
||||
changed = Self.updateGatewayString(
|
||||
&remote,
|
||||
key: "transport",
|
||||
value: RemoteTransport.direct.rawValue) || changed
|
||||
|
||||
let trimmedUrl = remoteUrl.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let trimmedUrl = draft.remoteUrl.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if trimmedUrl.isEmpty {
|
||||
changed = Self.updateGatewayString(&remote, key: "url", value: nil) || changed
|
||||
} else if let normalizedUrl = GatewayRemoteConfig.normalizeGatewayUrlString(trimmedUrl) {
|
||||
@@ -448,7 +462,7 @@ final class AppState {
|
||||
case .ssh:
|
||||
changed = Self.updateGatewayString(&remote, key: "transport", value: nil) || changed
|
||||
|
||||
if let host = remoteHost {
|
||||
if let host = draft.remoteHost {
|
||||
let existingUrl = (remote["url"] as? String)?
|
||||
.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
||||
let parsedExisting = existingUrl.isEmpty ? nil : URL(string: existingUrl)
|
||||
@@ -458,13 +472,13 @@ final class AppState {
|
||||
changed = Self.updateGatewayString(&remote, key: "url", value: desiredUrl) || changed
|
||||
}
|
||||
|
||||
let sanitizedTarget = Self.sanitizeSSHTarget(remoteTarget)
|
||||
let sanitizedTarget = Self.sanitizeSSHTarget(draft.remoteTarget)
|
||||
changed = Self.updateGatewayString(&remote, key: "sshTarget", value: sanitizedTarget) || changed
|
||||
changed = Self.updateGatewayString(&remote, key: "sshIdentity", value: remoteIdentity) || changed
|
||||
changed = Self.updateGatewayString(&remote, key: "sshIdentity", value: draft.remoteIdentity) || changed
|
||||
}
|
||||
|
||||
if remoteTokenDirty {
|
||||
changed = Self.updateGatewayString(&remote, key: "token", value: remoteToken) || changed
|
||||
if draft.remoteTokenDirty {
|
||||
changed = Self.updateGatewayString(&remote, key: "token", value: draft.remoteToken) || changed
|
||||
}
|
||||
|
||||
return (remote, changed)
|
||||
@@ -550,19 +564,13 @@ final class AppState {
|
||||
|
||||
private static func syncedGatewayRoot(
|
||||
currentRoot: [String: Any],
|
||||
connectionMode: ConnectionMode,
|
||||
remoteTransport: RemoteTransport,
|
||||
remoteTarget: String,
|
||||
remoteIdentity: String,
|
||||
remoteUrl: String,
|
||||
remoteToken: String,
|
||||
remoteTokenDirty: Bool) -> (root: [String: Any], changed: Bool)
|
||||
draft: GatewayConfigSyncDraft) -> (root: [String: Any], changed: Bool)
|
||||
{
|
||||
var root = currentRoot
|
||||
var gateway = root["gateway"] as? [String: Any] ?? [:]
|
||||
var changed = false
|
||||
|
||||
let desiredMode: String? = switch connectionMode {
|
||||
let desiredMode: String? = switch draft.connectionMode {
|
||||
case .local:
|
||||
"local"
|
||||
case .remote:
|
||||
@@ -582,18 +590,19 @@ final class AppState {
|
||||
changed = true
|
||||
}
|
||||
|
||||
if connectionMode == .remote {
|
||||
let remoteHost = CommandResolver.parseSSHTarget(remoteTarget)?.host
|
||||
if draft.connectionMode == .remote {
|
||||
let remoteHost = CommandResolver.parseSSHTarget(draft.remoteTarget)?.host
|
||||
let currentRemote = gateway["remote"] as? [String: Any] ?? [:]
|
||||
let updated = Self.updatedRemoteGatewayConfig(
|
||||
current: currentRemote,
|
||||
transport: remoteTransport,
|
||||
remoteUrl: remoteUrl,
|
||||
remoteHost: remoteHost,
|
||||
remoteTarget: remoteTarget,
|
||||
remoteIdentity: remoteIdentity,
|
||||
remoteToken: remoteToken,
|
||||
remoteTokenDirty: remoteTokenDirty)
|
||||
draft: .init(
|
||||
transport: draft.remoteTransport,
|
||||
remoteUrl: draft.remoteUrl,
|
||||
remoteHost: remoteHost,
|
||||
remoteTarget: draft.remoteTarget,
|
||||
remoteIdentity: draft.remoteIdentity,
|
||||
remoteToken: draft.remoteToken,
|
||||
remoteTokenDirty: draft.remoteTokenDirty))
|
||||
if updated.changed {
|
||||
gateway["remote"] = updated.remote
|
||||
changed = true
|
||||
@@ -625,13 +634,14 @@ final class AppState {
|
||||
// Keep app-only connection settings local to avoid overwriting remote gateway config.
|
||||
let synced = Self.syncedGatewayRoot(
|
||||
currentRoot: OpenClawConfigFile.loadDict(),
|
||||
connectionMode: self.connectionMode,
|
||||
remoteTransport: self.remoteTransport,
|
||||
remoteTarget: self.remoteTarget,
|
||||
remoteIdentity: self.remoteIdentity,
|
||||
remoteUrl: self.remoteUrl,
|
||||
remoteToken: self.remoteToken,
|
||||
remoteTokenDirty: self.remoteTokenDirty)
|
||||
draft: .init(
|
||||
connectionMode: self.connectionMode,
|
||||
remoteTransport: self.remoteTransport,
|
||||
remoteTarget: self.remoteTarget,
|
||||
remoteIdentity: self.remoteIdentity,
|
||||
remoteUrl: self.remoteUrl,
|
||||
remoteToken: self.remoteToken,
|
||||
remoteTokenDirty: self.remoteTokenDirty))
|
||||
guard synced.changed else { return }
|
||||
OpenClawConfigFile.saveDict(synced.root)
|
||||
}
|
||||
@@ -788,44 +798,20 @@ extension AppState {
|
||||
extension AppState {
|
||||
static func _testUpdatedRemoteGatewayConfig(
|
||||
current: [String: Any],
|
||||
transport: RemoteTransport,
|
||||
remoteUrl: String,
|
||||
remoteHost: String?,
|
||||
remoteTarget: String,
|
||||
remoteIdentity: String,
|
||||
remoteToken: String,
|
||||
remoteTokenDirty: Bool) -> [String: Any]
|
||||
draft: RemoteGatewayConfigDraft) -> [String: Any]
|
||||
{
|
||||
self.updatedRemoteGatewayConfig(
|
||||
current: current,
|
||||
transport: transport,
|
||||
remoteUrl: remoteUrl,
|
||||
remoteHost: remoteHost,
|
||||
remoteTarget: remoteTarget,
|
||||
remoteIdentity: remoteIdentity,
|
||||
remoteToken: remoteToken,
|
||||
remoteTokenDirty: remoteTokenDirty).remote
|
||||
draft: draft).remote
|
||||
}
|
||||
|
||||
static func _testSyncedGatewayRoot(
|
||||
currentRoot: [String: Any],
|
||||
connectionMode: ConnectionMode,
|
||||
remoteTransport: RemoteTransport,
|
||||
remoteTarget: String,
|
||||
remoteIdentity: String,
|
||||
remoteUrl: String,
|
||||
remoteToken: String,
|
||||
remoteTokenDirty: Bool) -> [String: Any]
|
||||
draft: GatewayConfigSyncDraft) -> [String: Any]
|
||||
{
|
||||
self.syncedGatewayRoot(
|
||||
currentRoot: currentRoot,
|
||||
connectionMode: connectionMode,
|
||||
remoteTransport: remoteTransport,
|
||||
remoteTarget: remoteTarget,
|
||||
remoteIdentity: remoteIdentity,
|
||||
remoteUrl: remoteUrl,
|
||||
remoteToken: remoteToken,
|
||||
remoteTokenDirty: remoteTokenDirty).root
|
||||
draft: draft).root
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -60,7 +60,7 @@ extension ChannelsStore {
|
||||
timeoutMs: 35000)
|
||||
self.whatsappLoginMessage = result.message
|
||||
self.whatsappLoginQrDataUrl = result.qrDataUrl
|
||||
self.whatsappLoginConnected = nil
|
||||
self.whatsappLoginConnected = result.connected
|
||||
shouldAutoWait = autoWait && result.qrDataUrl != nil
|
||||
} catch {
|
||||
self.whatsappLoginMessage = error.localizedDescription
|
||||
@@ -148,6 +148,7 @@ extension ChannelsStore {
|
||||
private struct WhatsAppLoginStartResult: Codable {
|
||||
let qrDataUrl: String?
|
||||
let message: String
|
||||
let connected: Bool?
|
||||
}
|
||||
|
||||
private struct WhatsAppLoginWaitResult: Codable {
|
||||
|
||||
@@ -476,10 +476,8 @@ private enum ExecHostExecutor {
|
||||
{
|
||||
guard decision == .allowAlways, context.security == .allowlist else { return }
|
||||
var seenPatterns = Set<String>()
|
||||
for pattern in context.allowAlwaysPatterns {
|
||||
if seenPatterns.insert(pattern).inserted {
|
||||
ExecApprovalsStore.addAllowlistEntry(agentId: context.agentId, pattern: pattern)
|
||||
}
|
||||
for pattern in context.allowAlwaysPatterns where seenPatterns.insert(pattern).inserted {
|
||||
ExecApprovalsStore.addAllowlistEntry(agentId: context.agentId, pattern: pattern)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -308,7 +308,9 @@ struct GeneralSettings: View {
|
||||
.padding(.leading, self.remoteLabelWidth + 10)
|
||||
if self.state.remoteTokenUnsupported {
|
||||
Text(
|
||||
"The current gateway.remote.token value is not plain text. OpenClaw for macOS cannot use it directly; enter a plaintext token here to replace it.")
|
||||
"The current gateway.remote.token value is not plain text. "
|
||||
+ "OpenClaw for macOS cannot use it directly; "
|
||||
+ "enter a plaintext token here to replace it.")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.orange)
|
||||
.padding(.leading, self.remoteLabelWidth + 10)
|
||||
|
||||
@@ -845,10 +845,8 @@ extension MacNodeRuntime {
|
||||
{
|
||||
guard persistAllowlist, security == .allowlist else { return }
|
||||
var seenPatterns = Set<String>()
|
||||
for pattern in allowAlwaysPatterns {
|
||||
if seenPatterns.insert(pattern).inserted {
|
||||
ExecApprovalsStore.addAllowlistEntry(agentId: agentId, pattern: pattern)
|
||||
}
|
||||
for pattern in allowAlwaysPatterns where seenPatterns.insert(pattern).inserted {
|
||||
ExecApprovalsStore.addAllowlistEntry(agentId: agentId, pattern: pattern)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -398,7 +398,9 @@ extension OnboardingView {
|
||||
.foregroundStyle(.secondary)
|
||||
if self.state.remoteTokenUnsupported {
|
||||
Text(
|
||||
"The current gateway.remote.token value is not plain text. OpenClaw for macOS cannot use it directly; enter a plaintext token here to replace it.")
|
||||
"The current gateway.remote.token value is not plain text. "
|
||||
+ "OpenClaw for macOS cannot use it directly; "
|
||||
+ "enter a plaintext token here to replace it.")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.orange)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
|
||||
@@ -61,28 +61,36 @@ enum RemoteGatewayAuthIssue: Equatable {
|
||||
var body: String {
|
||||
switch self {
|
||||
case .tokenRequired:
|
||||
"Paste the token configured on the gateway host. On the gateway host, run `openclaw config get gateway.auth.token`. If the gateway uses an environment variable instead, use `OPENCLAW_GATEWAY_TOKEN`."
|
||||
"Paste the token configured on the gateway host. "
|
||||
+ "On the gateway host, run `openclaw config get gateway.auth.token`. "
|
||||
+ "If the gateway uses an environment variable instead, use `OPENCLAW_GATEWAY_TOKEN`."
|
||||
case .tokenMismatch:
|
||||
"Check `gateway.auth.token` or `OPENCLAW_GATEWAY_TOKEN` on the gateway host and try again."
|
||||
case .gatewayTokenNotConfigured:
|
||||
"This gateway is set to token auth, but no `gateway.auth.token` is configured on the gateway host. If the gateway uses an environment variable instead, set `OPENCLAW_GATEWAY_TOKEN` before starting the gateway."
|
||||
"This gateway is set to token auth, but no `gateway.auth.token` is configured on the gateway host. "
|
||||
+ "If the gateway uses an environment variable instead, "
|
||||
+ "set `OPENCLAW_GATEWAY_TOKEN` before starting the gateway."
|
||||
case .setupCodeExpired:
|
||||
"Scan or paste a fresh setup code from an already-paired OpenClaw client, then try again."
|
||||
case .passwordRequired:
|
||||
"This onboarding flow does not support password auth yet. Reconfigure the gateway to use token auth, then retry."
|
||||
"This onboarding flow does not support password auth yet. "
|
||||
+ "Reconfigure the gateway to use token auth, then retry."
|
||||
case .pairingRequired:
|
||||
"Approve this device from an already-paired OpenClaw client. In your OpenClaw chat, run `/pair approve`, then click **Check connection** again."
|
||||
"Approve this device from an already-paired OpenClaw client. "
|
||||
+ "In your OpenClaw chat, run `/pair approve`, then click **Check connection** again."
|
||||
}
|
||||
}
|
||||
|
||||
var footnote: String? {
|
||||
switch self {
|
||||
case .tokenRequired, .gatewayTokenNotConfigured:
|
||||
"No token yet? Generate one on the gateway host with `openclaw doctor --generate-gateway-token`, then set it as `gateway.auth.token`."
|
||||
"No token yet? Generate one on the gateway host with "
|
||||
+ "`openclaw doctor --generate-gateway-token`, then set it as `gateway.auth.token`."
|
||||
case .setupCodeExpired:
|
||||
nil
|
||||
case .pairingRequired:
|
||||
"If you do not have another paired OpenClaw client yet, approve the pending request on the gateway host with `openclaw devices approve`."
|
||||
"If you do not have another paired OpenClaw client yet, "
|
||||
+ "approve the pending request on the gateway host with `openclaw devices approve`."
|
||||
case .tokenMismatch, .passwordRequired:
|
||||
nil
|
||||
}
|
||||
@@ -101,7 +109,8 @@ enum RemoteGatewayAuthIssue: Equatable {
|
||||
case .passwordRequired:
|
||||
"This gateway uses password auth. Remote onboarding on macOS cannot collect gateway passwords yet."
|
||||
case .pairingRequired:
|
||||
"Pairing required. In an already-paired OpenClaw client, run /pair approve, then check the connection again."
|
||||
"Pairing required. In an already-paired OpenClaw client, "
|
||||
+ "run /pair approve, then check the connection again."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -135,7 +144,8 @@ struct RemoteGatewayProbeSuccess: Equatable {
|
||||
case .some(.deviceToken):
|
||||
"This Mac used a stored device token. New or unpaired devices may still need the gateway token."
|
||||
case .some(.bootstrapToken):
|
||||
"This Mac is still using the temporary setup code. Approve pairing to finish provisioning device-scoped auth."
|
||||
"This Mac is still using the temporary setup code. "
|
||||
+ "Approve pairing to finish provisioning device-scoped auth."
|
||||
case .some(.sharedToken), .some(.password), .some(GatewayAuthSource.none), nil:
|
||||
nil
|
||||
}
|
||||
@@ -219,7 +229,8 @@ enum RemoteGatewayProbe {
|
||||
trimmed.localizedCaseInsensitiveContains("host key verification failed")
|
||||
{
|
||||
let host = CommandResolver.parseSSHTarget(target)?.host ?? target
|
||||
return "SSH check failed: Host key verification failed. Remove the old key with ssh-keygen -R \(host) and try again."
|
||||
return "SSH check failed: Host key verification failed. "
|
||||
+ "Remove the old key with ssh-keygen -R \(host) and try again."
|
||||
}
|
||||
if let trimmed, !trimmed.isEmpty {
|
||||
if let message = response.message, message.hasPrefix("exit ") {
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2026.4.16</string>
|
||||
<string>2026.4.20</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>2026041690</string>
|
||||
<string>2026042000</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>OpenClaw</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
|
||||
@@ -2481,6 +2481,24 @@ public struct ChannelsStatusResult: Codable, Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
public struct ChannelsStartParams: Codable, Sendable {
|
||||
public let channel: String
|
||||
public let accountid: String?
|
||||
|
||||
public init(
|
||||
channel: String,
|
||||
accountid: String?)
|
||||
{
|
||||
self.channel = channel
|
||||
self.accountid = accountid
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case channel
|
||||
case accountid = "accountId"
|
||||
}
|
||||
}
|
||||
|
||||
public struct ChannelsLogoutParams: Codable, Sendable {
|
||||
public let channel: String
|
||||
public let accountid: String?
|
||||
|
||||
@@ -8,13 +8,14 @@ struct AppStateRemoteConfigTests {
|
||||
func updatedRemoteGatewayConfigSetsTrimmedToken() {
|
||||
let remote = AppState._testUpdatedRemoteGatewayConfig(
|
||||
current: [:],
|
||||
transport: .ssh,
|
||||
remoteUrl: "",
|
||||
remoteHost: "gateway.example",
|
||||
remoteTarget: "alice@gateway.example",
|
||||
remoteIdentity: "/tmp/id_ed25519",
|
||||
remoteToken: " secret-token ",
|
||||
remoteTokenDirty: true)
|
||||
draft: .init(
|
||||
transport: .ssh,
|
||||
remoteUrl: "",
|
||||
remoteHost: "gateway.example",
|
||||
remoteTarget: "alice@gateway.example",
|
||||
remoteIdentity: "/tmp/id_ed25519",
|
||||
remoteToken: " secret-token ",
|
||||
remoteTokenDirty: true))
|
||||
|
||||
#expect(remote["token"] as? String == "secret-token")
|
||||
}
|
||||
@@ -23,13 +24,14 @@ struct AppStateRemoteConfigTests {
|
||||
func updatedRemoteGatewayConfigClearsTokenWhenBlank() {
|
||||
let remote = AppState._testUpdatedRemoteGatewayConfig(
|
||||
current: ["token": "old-token"],
|
||||
transport: .direct,
|
||||
remoteUrl: "wss://gateway.example",
|
||||
remoteHost: nil,
|
||||
remoteTarget: "",
|
||||
remoteIdentity: "",
|
||||
remoteToken: " ",
|
||||
remoteTokenDirty: true)
|
||||
draft: .init(
|
||||
transport: .direct,
|
||||
remoteUrl: "wss://gateway.example",
|
||||
remoteHost: nil,
|
||||
remoteTarget: "",
|
||||
remoteIdentity: "",
|
||||
remoteToken: " ",
|
||||
remoteTokenDirty: true))
|
||||
|
||||
#expect((remote["token"] as? String) == nil)
|
||||
}
|
||||
@@ -51,25 +53,27 @@ struct AppStateRemoteConfigTests {
|
||||
|
||||
let sshRoot = AppState._testSyncedGatewayRoot(
|
||||
currentRoot: initialRoot,
|
||||
connectionMode: .remote,
|
||||
remoteTransport: .ssh,
|
||||
remoteTarget: "alice@gateway.example",
|
||||
remoteIdentity: "",
|
||||
remoteUrl: "",
|
||||
remoteToken: "",
|
||||
remoteTokenDirty: false)
|
||||
draft: .init(
|
||||
connectionMode: .remote,
|
||||
remoteTransport: .ssh,
|
||||
remoteTarget: "alice@gateway.example",
|
||||
remoteIdentity: "",
|
||||
remoteUrl: "",
|
||||
remoteToken: "",
|
||||
remoteTokenDirty: false))
|
||||
let sshRemote = (sshRoot["gateway"] as? [String: Any])?["remote"] as? [String: Any]
|
||||
#expect((sshRemote?["token"] as? [String: String])?["$secretRef"] == "gateway-token") // pragma: allowlist secret
|
||||
|
||||
let localRoot = AppState._testSyncedGatewayRoot(
|
||||
currentRoot: sshRoot,
|
||||
connectionMode: .local,
|
||||
remoteTransport: .ssh,
|
||||
remoteTarget: "",
|
||||
remoteIdentity: "",
|
||||
remoteUrl: "",
|
||||
remoteToken: "",
|
||||
remoteTokenDirty: false)
|
||||
draft: .init(
|
||||
connectionMode: .local,
|
||||
remoteTransport: .ssh,
|
||||
remoteTarget: "",
|
||||
remoteIdentity: "",
|
||||
remoteUrl: "",
|
||||
remoteToken: "",
|
||||
remoteTokenDirty: false))
|
||||
let localGateway = localRoot["gateway"] as? [String: Any]
|
||||
let localRemote = localGateway?["remote"] as? [String: Any]
|
||||
#expect(localGateway?["mode"] as? String == "local")
|
||||
@@ -84,13 +88,14 @@ struct AppStateRemoteConfigTests {
|
||||
"$secretRef": "gateway-token", // pragma: allowlist secret
|
||||
],
|
||||
],
|
||||
transport: .direct,
|
||||
remoteUrl: "wss://gateway.example",
|
||||
remoteHost: nil,
|
||||
remoteTarget: "",
|
||||
remoteIdentity: "",
|
||||
remoteToken: " fresh-token ",
|
||||
remoteTokenDirty: true)
|
||||
draft: .init(
|
||||
transport: .direct,
|
||||
remoteUrl: "wss://gateway.example",
|
||||
remoteHost: nil,
|
||||
remoteTarget: "",
|
||||
remoteIdentity: "",
|
||||
remoteToken: " fresh-token ",
|
||||
remoteTokenDirty: true))
|
||||
|
||||
#expect(remote["token"] as? String == "fresh-token")
|
||||
}
|
||||
@@ -105,24 +110,26 @@ struct AppStateRemoteConfigTests {
|
||||
|
||||
let preserved = AppState._testUpdatedRemoteGatewayConfig(
|
||||
current: current,
|
||||
transport: .direct,
|
||||
remoteUrl: "wss://gateway.example",
|
||||
remoteHost: nil,
|
||||
remoteTarget: "",
|
||||
remoteIdentity: "",
|
||||
remoteToken: "",
|
||||
remoteTokenDirty: false)
|
||||
draft: .init(
|
||||
transport: .direct,
|
||||
remoteUrl: "wss://gateway.example",
|
||||
remoteHost: nil,
|
||||
remoteTarget: "",
|
||||
remoteIdentity: "",
|
||||
remoteToken: "",
|
||||
remoteTokenDirty: false))
|
||||
#expect((preserved["token"] as? [String: String])?["$secretRef"] == "gateway-token") // pragma: allowlist secret
|
||||
|
||||
let cleared = AppState._testUpdatedRemoteGatewayConfig(
|
||||
current: current,
|
||||
transport: .direct,
|
||||
remoteUrl: "wss://gateway.example",
|
||||
remoteHost: nil,
|
||||
remoteTarget: "",
|
||||
remoteIdentity: "",
|
||||
remoteToken: " ",
|
||||
remoteTokenDirty: true)
|
||||
draft: .init(
|
||||
transport: .direct,
|
||||
remoteUrl: "wss://gateway.example",
|
||||
remoteHost: nil,
|
||||
remoteTarget: "",
|
||||
remoteIdentity: "",
|
||||
remoteToken: " ",
|
||||
remoteTokenDirty: true))
|
||||
#expect((cleared["token"] as? String) == nil)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2481,6 +2481,24 @@ public struct ChannelsStatusResult: Codable, Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
public struct ChannelsStartParams: Codable, Sendable {
|
||||
public let channel: String
|
||||
public let accountid: String?
|
||||
|
||||
public init(
|
||||
channel: String,
|
||||
accountid: String?)
|
||||
{
|
||||
self.channel = channel
|
||||
self.accountid = accountid
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case channel
|
||||
case accountid = "accountId"
|
||||
}
|
||||
}
|
||||
|
||||
public struct ChannelsLogoutParams: Codable, Sendable {
|
||||
public let channel: String
|
||||
public let accountid: String?
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
17d43e3c5dd6ac09fa6c7954e6923d2e0fba767483bac0a2b257fb7ec736f8a4 config-baseline.json
|
||||
fdb7867bbc18792d3645ea36c31b64425d6f19c0b19a7460564f67eb97c0a71e config-baseline.core.json
|
||||
99bb34fcf83ba6bb50a3fc11f170bd379bee5728b0938707fc39ebd7638e12eb config-baseline.channel.json
|
||||
b695cb31b4c0cf1d31f842f2892e99cc3ff8d84263ae72b72977cae844b81d6e config-baseline.plugin.json
|
||||
cc473bcd00e63c3d3f351e4de1ceb390aae88dddce8616929e98a9d94412b1b9 config-baseline.json
|
||||
7956c319e82d288d496a51cb2ff4485ab72ef4900cb089f99e1df8b9ef3bfb73 config-baseline.core.json
|
||||
cd467228990cdbdebde2fa87d8b1384b94c149e791f2e67250bf17b13162d4a1 config-baseline.channel.json
|
||||
17a73724e5082b3aa846c220d38115916fb6003887439e6794510a99fc73f7de config-baseline.plugin.json
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
d08f0e793e66192fdbc377183ce0d94adcbec53cf334522bce8c0c457b90b0a8 plugin-sdk-api-baseline.json
|
||||
924f20b350a9f1997e95b3d7249cbb6720c9576c63e6c0c15cca0164734fd93d plugin-sdk-api-baseline.jsonl
|
||||
f135ddc1802b7f8b2d29bf495fd0ac1f497a89bab8164ca8c7c8f18efc010e6e plugin-sdk-api-baseline.json
|
||||
a47d06095ec5c3701a94888a11e89700d8a8511db46fa3122fb9407e160707b6 plugin-sdk-api-baseline.jsonl
|
||||
|
||||
@@ -59,6 +59,14 @@
|
||||
"source": "Feishu",
|
||||
"target": "Feishu"
|
||||
},
|
||||
{
|
||||
"source": "WeChat",
|
||||
"target": "微信"
|
||||
},
|
||||
{
|
||||
"source": "Weixin",
|
||||
"target": "微信"
|
||||
},
|
||||
{
|
||||
"source": "Mattermost",
|
||||
"target": "Mattermost"
|
||||
@@ -366,5 +374,17 @@
|
||||
{
|
||||
"source": "Testing",
|
||||
"target": "测试"
|
||||
},
|
||||
{
|
||||
"source": "/gateway/configuration#strict-validation",
|
||||
"target": "/gateway/configuration#strict-validation"
|
||||
},
|
||||
{
|
||||
"source": "/gateway/configuration#config-hot-reload",
|
||||
"target": "/gateway/configuration#config-hot-reload"
|
||||
},
|
||||
{
|
||||
"source": "/cli/config",
|
||||
"target": "/cli/config"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -25,6 +25,7 @@ openclaw cron add \
|
||||
|
||||
# Check your jobs
|
||||
openclaw cron list
|
||||
openclaw cron show <job-id>
|
||||
|
||||
# See run history
|
||||
openclaw cron runs --id <job-id>
|
||||
@@ -33,7 +34,9 @@ openclaw cron runs --id <job-id>
|
||||
## How cron works
|
||||
|
||||
- Cron runs **inside the Gateway** process (not inside the model).
|
||||
- Jobs persist at `~/.openclaw/cron/jobs.json` so restarts do not lose schedules.
|
||||
- Job definitions persist at `~/.openclaw/cron/jobs.json` so restarts do not lose schedules.
|
||||
- Runtime execution state persists next to it in `~/.openclaw/cron/jobs-state.json`. If you track cron definitions in git, track `jobs.json` and gitignore `jobs-state.json`.
|
||||
- After the split, older OpenClaw versions can read `jobs.json` but may treat jobs as fresh because runtime fields now live in `jobs-state.json`.
|
||||
- All cron executions create [background task](/automation/tasks) records.
|
||||
- One-shot jobs (`--at`) auto-delete after success by default.
|
||||
- Isolated cron runs best-effort close tracked browser tabs/processes for their `cron:<jobId>` session when the run completes, so detached browser automation does not leave orphaned processes behind.
|
||||
@@ -123,22 +126,19 @@ retries, cron aborts instead of looping forever.
|
||||
|
||||
## 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 |
|
||||
| Mode | What happens |
|
||||
| ---------- | ------------------------------------------------------------------- |
|
||||
| `announce` | Fallback-deliver final text to the target if the agent did not send |
|
||||
| `webhook` | POST finished event payload to a URL |
|
||||
| `none` | No runner fallback 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>`).
|
||||
|
||||
For cron-owned isolated jobs, the runner owns the final delivery path. The
|
||||
agent is prompted to return a plain-text summary, and that summary is then sent
|
||||
through `announce`, `webhook`, or kept internal for `none`. `--no-deliver`
|
||||
does not hand delivery back to the agent; it keeps the run internal.
|
||||
|
||||
If the original task explicitly says to message some external recipient, the
|
||||
agent should note who/where that message should go in its output instead of
|
||||
trying to send it directly.
|
||||
For isolated jobs, chat delivery is shared. If a chat route is available, the
|
||||
agent can use the `message` tool even when the job uses `--no-deliver`. If the
|
||||
agent sends to the configured/current target, OpenClaw skips the fallback
|
||||
announce. Otherwise `announce`, `webhook`, and `none` only control what the
|
||||
runner does with the final reply after the agent turn.
|
||||
|
||||
Failure notifications follow a separate destination path:
|
||||
|
||||
@@ -317,6 +317,9 @@ gog gmail watch start \
|
||||
# List all jobs
|
||||
openclaw cron list
|
||||
|
||||
# Show one job, including resolved delivery route
|
||||
openclaw cron show <jobId>
|
||||
|
||||
# Edit a job
|
||||
openclaw cron edit <jobId> --message "Updated prompt" --model "opus"
|
||||
|
||||
@@ -368,6 +371,10 @@ Model override note:
|
||||
}
|
||||
```
|
||||
|
||||
The runtime state sidecar is derived from `cron.store`: a `.json` store such as
|
||||
`~/clawd/cron/jobs.json` uses `~/clawd/cron/jobs-state.json`, while a store path
|
||||
without a `.json` suffix appends `-state.json`.
|
||||
|
||||
Disable cron: `cron.enabled: false` or `OPENCLAW_SKIP_CRON=1`.
|
||||
|
||||
**One-shot retry**: transient errors (rate limit, overload, network, server error) retry up to 3 times with exponential backoff. Permanent errors disable immediately.
|
||||
@@ -400,15 +407,15 @@ openclaw doctor
|
||||
|
||||
### Cron fired but no delivery
|
||||
|
||||
- Delivery mode is `none` means no external message is expected.
|
||||
- Delivery mode `none` means no runner fallback send is expected. The agent can
|
||||
still send directly with the `message` tool when a chat route is available.
|
||||
- Delivery target missing/invalid (`channel`/`to`) means outbound was skipped.
|
||||
- Channel auth errors (`unauthorized`, `Forbidden`) mean delivery was blocked by credentials.
|
||||
- If the isolated run returns only the silent token (`NO_REPLY` / `no_reply`),
|
||||
OpenClaw suppresses direct outbound delivery and also suppresses the fallback
|
||||
queued summary path, so nothing is posted back to chat.
|
||||
- For cron-owned isolated jobs, do not expect the agent to use the message tool
|
||||
as a fallback. The runner owns final delivery; `--no-deliver` keeps it
|
||||
internal instead of allowing a direct send.
|
||||
- If the agent should message the user itself, check that the job has a usable
|
||||
route (`channel: "last"` with a previous chat, or an explicit channel/target).
|
||||
|
||||
### Timezone gotchas
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ title: "Hooks"
|
||||
|
||||
# Hooks
|
||||
|
||||
Hooks are small scripts that run when something happens inside the Gateway. They are automatically discovered from directories and can be inspected with `openclaw hooks`.
|
||||
Hooks are small scripts that run when something happens inside the Gateway. They can be discovered from directories and inspected with `openclaw hooks`. The Gateway loads internal hooks only after you enable hooks or configure at least one hook entry, hook pack, legacy handler, or extra hook directory.
|
||||
|
||||
There are two kinds of hooks in OpenClaw:
|
||||
|
||||
@@ -139,6 +139,8 @@ Hooks are discovered from these directories, in order of increasing override pre
|
||||
|
||||
Workspace hooks can add new hook names but cannot override bundled, managed, or plugin-provided hooks with the same name.
|
||||
|
||||
The Gateway skips internal hook discovery on startup until internal hooks are configured. Enable a bundled or managed hook with `openclaw hooks enable <name>`, install a hook pack, or set `hooks.internal.enabled=true` to opt in. When you enable one named hook, the Gateway loads only that hook's handler; `hooks.internal.enabled=true`, extra hook directories, and legacy handlers opt into broad discovery.
|
||||
|
||||
### Hook packs
|
||||
|
||||
Hook packs are npm packages that export hooks via `openclaw.hooks` in `package.json`. Install with:
|
||||
|
||||
@@ -301,7 +301,7 @@ See [Task Flow](/automation/taskflow) for details.
|
||||
|
||||
### Tasks and cron
|
||||
|
||||
A cron job **definition** lives in `~/.openclaw/cron/jobs.json`. **Every** cron execution creates a task record — both main-session and isolated. Main-session cron tasks default to `silent` notify policy so they track without generating notifications.
|
||||
A cron job **definition** lives in `~/.openclaw/cron/jobs.json`; runtime execution state lives beside it in `~/.openclaw/cron/jobs-state.json`. **Every** cron execution creates a task record — both main-session and isolated. Main-session cron tasks default to `silent` notify policy so they track without generating notifications.
|
||||
|
||||
See [Cron Jobs](/automation/cron-jobs).
|
||||
|
||||
|
||||
@@ -217,6 +217,54 @@ Per-group configuration:
|
||||
- Uses `allowFrom` and `groupAllowFrom` to determine command authorization.
|
||||
- Authorized senders can run control commands even without mentioning in groups.
|
||||
|
||||
### Per-group system prompt
|
||||
|
||||
Each entry under `channels.bluebubbles.groups.*` accepts an optional `systemPrompt` string. The value is injected into the agent's system prompt on every turn that handles a message in that group, so you can set per-group persona or behavioral rules without editing agent prompts:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
bluebubbles: {
|
||||
groups: {
|
||||
"iMessage;-;chat123": {
|
||||
systemPrompt: "Keep responses under 3 sentences. Mirror the group's casual tone.",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
The key matches whatever BlueBubbles reports as `chatGuid` / `chatIdentifier` / numeric `chatId` for the group, and a `"*"` wildcard entry provides a default for every group without an exact match (same pattern used by `requireMention` and per-group tool policies). Exact matches always win over the wildcard. DMs ignore this field; use agent-level or account-level prompt customization instead.
|
||||
|
||||
#### Worked example: threaded replies and tapback reactions (Private API)
|
||||
|
||||
With the BlueBubbles Private API enabled, inbound messages arrive with short message IDs (for example `[[reply_to:5]]`) and the agent can call `action=reply` to thread into a specific message or `action=react` to drop a tapback. A per-group `systemPrompt` is a reliable way to keep the agent choosing the right tool:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
bluebubbles: {
|
||||
groups: {
|
||||
"iMessage;+;chat-family": {
|
||||
systemPrompt: [
|
||||
"When replying in this group, always call action=reply with the",
|
||||
"[[reply_to:N]] messageId from context so your response threads",
|
||||
"under the triggering message. Never send a new unlinked message.",
|
||||
"",
|
||||
"For short acknowledgements ('ok', 'got it', 'on it'), use",
|
||||
"action=react with an appropriate tapback emoji (❤️, 👍, 😂, ‼️, ❓)",
|
||||
"instead of sending a text reply.",
|
||||
].join(" "),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Tapback reactions and threaded replies both require the BlueBubbles Private API; see [Advanced actions](#advanced-actions) and [Message IDs](#message-ids-short-vs-full) for the underlying mechanics.
|
||||
|
||||
## ACP conversation bindings
|
||||
|
||||
BlueBubbles chats can be turned into durable ACP workspaces without changing the transport layer.
|
||||
@@ -345,6 +393,103 @@ Use full IDs for durable automations and storage:
|
||||
|
||||
See [Configuration](/gateway/configuration) for template variables.
|
||||
|
||||
## Coalescing split-send DMs (command + URL in one composition)
|
||||
|
||||
When a user types a command and a URL together in iMessage — e.g. `Dump https://example.com/article` — Apple splits the send into **two separate webhook deliveries**:
|
||||
|
||||
1. A text message (`"Dump"`).
|
||||
2. A URL-preview balloon (`"https://..."`) with OG-preview images as attachments.
|
||||
|
||||
The two webhooks arrive at OpenClaw ~0.8-2.0 s apart on most setups. Without coalescing, the agent receives the command alone on turn 1, replies (often "send me the URL"), and only sees the URL on turn 2 — at which point the command context is already lost.
|
||||
|
||||
`channels.bluebubbles.coalesceSameSenderDms` opts a DM into merging consecutive same-sender webhooks into a single agent turn. Group chats continue to key per-message so multi-user turn structure is preserved.
|
||||
|
||||
### When to enable
|
||||
|
||||
Enable when:
|
||||
|
||||
- You ship skills that expect `command + payload` in one message (dump, paste, save, queue, etc.).
|
||||
- Your users paste URLs, images, or long content alongside commands.
|
||||
- You can accept the added DM turn latency (see below).
|
||||
|
||||
Leave disabled when:
|
||||
|
||||
- You need minimum command latency for single-word DM triggers.
|
||||
- All your flows are one-shot commands without payload follow-ups.
|
||||
|
||||
### Enabling
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
bluebubbles: {
|
||||
coalesceSameSenderDms: true, // opt in (default: false)
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
With the flag on and no explicit `messages.inbound.byChannel.bluebubbles`, the debounce window widens to **2500 ms** (the default for non-coalescing is 500 ms). The wider window is required — Apple's split-send cadence of 0.8-2.0 s does not fit in the tighter default.
|
||||
|
||||
To tune the window yourself:
|
||||
|
||||
```json5
|
||||
{
|
||||
messages: {
|
||||
inbound: {
|
||||
byChannel: {
|
||||
// 2500 ms works for most setups; raise to 4000 ms if your Mac is slow
|
||||
// or under memory pressure (observed gap can stretch past 2 s then).
|
||||
bluebubbles: 2500,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Trade-offs
|
||||
|
||||
- **Added latency for DM control commands.** With the flag on, DM control-command messages (like `Dump`, `Save`, etc.) now wait up to the debounce window before dispatching, in case a payload webhook is coming. Group-chat commands keep instant dispatch.
|
||||
- **Merged output is bounded** — merged text caps at 4000 chars with an explicit `…[truncated]` marker; attachments cap at 20; source entries cap at 10 (first-plus-latest retained beyond that). Every source `messageId` still reaches inbound-dedupe so a later MessagePoller replay of any individual event is recognized as a duplicate.
|
||||
- **Opt-in, per-channel.** Other channels (Telegram, WhatsApp, Slack, …) are unaffected.
|
||||
|
||||
### Scenarios and what the agent sees
|
||||
|
||||
| User composes | Apple delivers | Flag off (default) | Flag on + 2500 ms window |
|
||||
| ------------------------------------------------------------------ | ------------------------- | --------------------------------------- | ----------------------------------------------------------------------- |
|
||||
| `Dump https://example.com` (one send) | 2 webhooks ~1 s apart | Two agent turns: "Dump" alone, then URL | One turn: merged text `Dump https://example.com` |
|
||||
| `Save this 📎image.jpg caption` (attachment + text) | 2 webhooks | Two turns | One turn: text + image |
|
||||
| `/status` (standalone command) | 1 webhook | Instant dispatch | **Wait up to window, then dispatch** |
|
||||
| URL pasted alone | 1 webhook | Instant dispatch | Instant dispatch (only one entry in bucket) |
|
||||
| Text + URL sent as two deliberate separate messages, minutes apart | 2 webhooks outside window | Two turns | Two turns (window expires between them) |
|
||||
| Rapid flood (>10 small DMs inside window) | N webhooks | N turns | One turn, bounded output (first + latest, text/attachment caps applied) |
|
||||
|
||||
### Split-send coalescing troubleshooting
|
||||
|
||||
If the flag is on and split-sends still arrive as two turns, check each layer:
|
||||
|
||||
1. **Config actually loaded.**
|
||||
|
||||
```
|
||||
grep coalesceSameSenderDms ~/.openclaw/openclaw.json
|
||||
```
|
||||
|
||||
Then `openclaw gateway restart` — the flag is read at debouncer-registry creation.
|
||||
|
||||
2. **Debounce window wide enough for your setup.** Look at the BlueBubbles server log under `~/Library/Logs/bluebubbles-server/main.log`:
|
||||
|
||||
```
|
||||
grep -E "Dispatching event to webhook" main.log | tail -20
|
||||
```
|
||||
|
||||
Measure the gap between the `"Dump"`-style text dispatch and the `"https://..."; Attachments:` dispatch that follows. Raise `messages.inbound.byChannel.bluebubbles` to comfortably cover that gap.
|
||||
|
||||
3. **Session JSONL timestamps ≠ webhook arrival.** Session event timestamps (`~/.openclaw/agents/<id>/sessions/*.jsonl`) reflect when the gateway hands a message to the agent, **not** when the webhook arrived. A queued-second message tagged `[Queued messages while agent was busy]` means the first turn was still running when the second webhook arrived — the coalesce bucket had already flushed. Tune the window against the BB server log, not the session log.
|
||||
|
||||
4. **Memory pressure slowing reply dispatch.** On smaller machines (8 GB), agent turns can take long enough that the coalesce bucket flushes before the reply completes, and the URL lands as a queued second turn. Check `memory_pressure` and `ps -o rss -p $(pgrep openclaw-gateway)`; if the gateway is over ~500 MB RSS and the compressor is active, close other heavy processes or bump to a larger host.
|
||||
|
||||
5. **Reply-quote sends are a different path.** If the user tapped `Dump` as a **reply** to an existing URL-balloon (iMessage shows a "1 Reply" badge on the Dump bubble), the URL lives in `replyToBody`, not in a second webhook. Coalescing does not apply — that's a skill/prompt concern, not a debouncer concern.
|
||||
|
||||
## Block streaming
|
||||
|
||||
Control whether responses are sent as a single message or streamed in blocks:
|
||||
@@ -384,9 +529,11 @@ Provider options:
|
||||
- `channels.bluebubbles.sendReadReceipts`: Send read receipts (default: `true`).
|
||||
- `channels.bluebubbles.blockStreaming`: Enable block streaming (default: `false`; required for streaming replies).
|
||||
- `channels.bluebubbles.textChunkLimit`: Outbound chunk size in chars (default: 4000).
|
||||
- `channels.bluebubbles.sendTimeoutMs`: Per-request timeout in ms for outbound text sends via `/api/v1/message/text` (default: 30000). Raise on macOS 26 setups where Private API iMessage sends can stall for 60+ seconds inside the iMessage framework; for example `45000` or `60000`. Probes, chat lookups, reactions, edits, and health checks currently keep the shorter 10s default; broadening coverage to reactions and edits is planned as a follow-up. Per-account override: `channels.bluebubbles.accounts.<accountId>.sendTimeoutMs`.
|
||||
- `channels.bluebubbles.chunkMode`: `length` (default) splits only when exceeding `textChunkLimit`; `newline` splits on blank lines (paragraph boundaries) before length chunking.
|
||||
- `channels.bluebubbles.mediaMaxMb`: Inbound/outbound media cap in MB (default: 8).
|
||||
- `channels.bluebubbles.mediaLocalRoots`: Explicit allowlist of absolute local directories permitted for outbound local media paths. Local path sends are denied by default unless this is configured. Per-account override: `channels.bluebubbles.accounts.<accountId>.mediaLocalRoots`.
|
||||
- `channels.bluebubbles.coalesceSameSenderDms`: Merge consecutive same-sender DM webhooks into one agent turn so Apple's text+URL split-send arrives as a single message (default: `false`). See [Coalescing split-send DMs](#coalescing-split-send-dms-command--url-in-one-composition) for scenarios, window tuning, and trade-offs. Widens the default inbound debounce window from 500 ms to 2500 ms when enabled without an explicit `messages.inbound.byChannel.bluebubbles`.
|
||||
- `channels.bluebubbles.historyLimit`: Max group messages for context (0 disables).
|
||||
- `channels.bluebubbles.dmHistoryLimit`: DM history limit.
|
||||
- `channels.bluebubbles.actions`: Enable/disable specific actions.
|
||||
@@ -422,6 +569,7 @@ Prefer `chat_guid` for stable routing:
|
||||
- Edit/unsend require macOS 13+ and a compatible BlueBubbles server version. On macOS 26 (Tahoe), edit is currently broken due to private API changes.
|
||||
- Group icon updates can be flaky on macOS 26 (Tahoe): the API may return success but the new icon does not sync.
|
||||
- OpenClaw auto-hides known-broken actions based on the BlueBubbles server's macOS version. If edit still appears on macOS 26 (Tahoe), disable it manually with `channels.bluebubbles.actions.edit=false`.
|
||||
- `coalesceSameSenderDms` enabled but split-sends (e.g. `Dump` + URL) still arrive as two turns: see the [split-send coalescing troubleshooting](#split-send-coalescing-troubleshooting) checklist — common causes are too-tight debounce window, session-log timestamps misread as webhook arrival, or a reply-quote send (which uses `replyToBody`, not a second webhook).
|
||||
- For status/health info: `openclaw status --all` or `openclaw status --deep`.
|
||||
|
||||
For general channel workflow reference, see [Channels](/channels) and the [Plugins](/tools/plugin) guide.
|
||||
|
||||
@@ -82,12 +82,12 @@ If you want...
|
||||
|
||||
Yes — this works well if your “personal” traffic is **DMs** and your “public” traffic is **groups**.
|
||||
|
||||
Why: in single-agent mode, DMs typically land in the **main** session key (`agent:main:main`), while groups always use **non-main** session keys (`agent:main:<channel>:group:<id>`). If you enable sandboxing with `mode: "non-main"`, those group sessions run in Docker while your main DM session stays on-host.
|
||||
Why: in single-agent mode, DMs typically land in the **main** session key (`agent:main:main`), while groups always use **non-main** session keys (`agent:main:<channel>:group:<id>`). If you enable sandboxing with `mode: "non-main"`, those group sessions run in the configured sandbox backend while your main DM session stays on-host. Docker is the default backend if you do not choose one.
|
||||
|
||||
This gives you one agent “brain” (shared workspace + memory), but two execution postures:
|
||||
|
||||
- **DMs**: full tools (host)
|
||||
- **Groups**: sandbox + restricted tools (Docker)
|
||||
- **Groups**: sandbox + restricted tools
|
||||
|
||||
> If you need truly separate workspaces/personas (“personal” and “public” must never mix), use a second agent + bindings. See [Multi-Agent Routing](/concepts/multi-agent).
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ Text is supported everywhere; media and reactions vary by channel.
|
||||
- [Twitch](/channels/twitch) — Twitch chat via IRC connection (bundled plugin).
|
||||
- [Voice Call](/plugins/voice-call) — Telephony via Plivo or Twilio (plugin, installed separately).
|
||||
- [WebChat](/web/webchat) — Gateway WebChat UI over WebSocket.
|
||||
- [WeChat](https://www.npmjs.com/package/@tencent-weixin/openclaw-weixin) — Tencent iLink Bot plugin via QR login; private chats only.
|
||||
- [WeChat](/channels/wechat) — Tencent iLink Bot plugin via QR login; private chats only (external plugin).
|
||||
- [WhatsApp](/channels/whatsapp) — Most popular; uses Baileys and requires QR pairing.
|
||||
- [Zalo](/channels/zalo) — Zalo Bot API; Vietnam's popular messenger (bundled plugin).
|
||||
- [Zalo Personal](/channels/zalouser) — Zalo personal account via QR login (bundled plugin).
|
||||
|
||||
@@ -1014,7 +1014,7 @@ Live directory lookup uses the logged-in Matrix account:
|
||||
- `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. Entries should be full Matrix user IDs; unresolved names are ignored at runtime.
|
||||
- `groupAllowFrom`: allowlist of user IDs for room traffic. Full Matrix user IDs are safest; exact directory matches are resolved at startup and when the allowlist changes while the monitor is running. Unresolved names are ignored.
|
||||
- `historyLimit`: max room messages to include as group history context. Falls back to `messages.groupChat.historyLimit`; if both are unset, the effective default is `0`. Set `0` to disable.
|
||||
- `replyToMode`: `off`, `first`, `all`, or `batched`.
|
||||
- `markdown`: optional Markdown rendering configuration for outbound Matrix text.
|
||||
@@ -1035,7 +1035,7 @@ Live directory lookup uses the logged-in Matrix account:
|
||||
- `autoJoinAllowlist`: rooms/aliases allowed when `autoJoin` is `allowlist`. Alias entries are resolved to room IDs during invite handling; OpenClaw does not trust alias state claimed by the invited room.
|
||||
- `dm`: DM policy block (`enabled`, `policy`, `allowFrom`, `sessionScope`, `threadReplies`).
|
||||
- `dm.policy`: controls DM access after OpenClaw has joined the room and classified it as a DM. It does not change whether an invite is auto-joined.
|
||||
- `dm.allowFrom`: entries should be full Matrix user IDs unless you already resolved them through live directory lookup.
|
||||
- `dm.allowFrom`: allowlist of user IDs for DM traffic. Full Matrix user IDs are safest; exact directory matches are resolved at startup and when the allowlist changes while the monitor is running. Unresolved names are ignored.
|
||||
- `dm.sessionScope`: `per-user` (default) or `per-room`. Use `per-room` when you want each Matrix DM room to keep separate context even if the peer is the same.
|
||||
- `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`).
|
||||
|
||||
@@ -100,6 +100,12 @@ If the same device retries with different auth details (for example different
|
||||
role/scopes/public key), the previous pending request is superseded and a new
|
||||
`requestId` is created.
|
||||
|
||||
Important: an already paired device does not get broader access silently. If it
|
||||
reconnects asking for more scopes or a broader role, OpenClaw keeps the
|
||||
existing approval as-is and creates a fresh pending upgrade request. Use
|
||||
`openclaw devices list` to compare the currently approved access with the newly
|
||||
requested access before you approve.
|
||||
|
||||
### Node pairing state storage
|
||||
|
||||
Stored under `~/.openclaw/devices/`:
|
||||
|
||||
@@ -327,7 +327,7 @@ Surface different features that extend the above defaults.
|
||||
{
|
||||
"command": "/think",
|
||||
"description": "Set the thinking level",
|
||||
"usage_hint": "<off|minimal|low|medium|high|xhigh>"
|
||||
"usage_hint": "<level>"
|
||||
},
|
||||
{
|
||||
"command": "/verbose",
|
||||
@@ -448,7 +448,7 @@ Surface different features that extend the above defaults.
|
||||
{
|
||||
"command": "/think",
|
||||
"description": "Set the thinking level",
|
||||
"usage_hint": "<off|minimal|low|medium|high|xhigh>",
|
||||
"usage_hint": "<level>",
|
||||
"url": "https://gateway-host.example.com/slack/events"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -115,7 +115,7 @@ Token resolution order is account-aware. In practice, config values win over env
|
||||
|
||||
`channels.telegram.allowFrom` accepts numeric Telegram user IDs. `telegram:` / `tg:` prefixes are accepted and normalized.
|
||||
`dmPolicy: "allowlist"` with empty `allowFrom` blocks all DMs and is rejected by config validation.
|
||||
Onboarding accepts `@username` input and resolves it to numeric IDs.
|
||||
Setup asks for numeric user IDs only.
|
||||
If you upgraded and your config contains `@username` allowlist entries, run `openclaw doctor --fix` to resolve them (best-effort; requires a Telegram bot token).
|
||||
If you previously relied on pairing-store allowlist files, `openclaw doctor --fix` can recover entries into `channels.telegram.allowFrom` in allowlist flows (for example when `dmPolicy: "allowlist"` has no explicit IDs yet).
|
||||
|
||||
@@ -259,6 +259,7 @@ curl "https://api.telegram.org/bot<bot_token>/getUpdates"
|
||||
- Group sessions are isolated by group ID. Forum topics append `:topic:<threadId>` to keep topics isolated.
|
||||
- DM messages can carry `message_thread_id`; OpenClaw routes them with thread-aware session keys and preserves thread ID for replies.
|
||||
- Long polling uses grammY runner with per-chat/per-thread sequencing. Overall runner sink concurrency uses `agents.defaults.maxConcurrent`.
|
||||
- Long-polling watchdog restarts trigger after 120 seconds without completed `getUpdates` liveness by default. Increase `channels.telegram.pollingStallThresholdMs` only if your deployment still sees false polling-stall restarts during long-running work. The value is in milliseconds and is allowed from `30000` to `600000`; per-account overrides are supported.
|
||||
- Telegram Bot API has no read-receipt support (`sendReadReceipts` does not apply).
|
||||
|
||||
## Feature reference
|
||||
@@ -766,6 +767,7 @@ curl "https://api.telegram.org/bot<bot_token>/getUpdates"
|
||||
- `channels.telegram.chunkMode="newline"` prefers paragraph boundaries (blank lines) before length splitting.
|
||||
- `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).
|
||||
- `channels.telegram.pollingStallThresholdMs` defaults to `120000`; tune between `30000` and `600000` only for false-positive polling-stall restarts.
|
||||
- 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.
|
||||
@@ -917,6 +919,8 @@ Per-account, per-group, and per-topic overrides are supported (same inheritance
|
||||
- Node 22+ + custom fetch/proxy can trigger immediate abort behavior if AbortSignal types mismatch.
|
||||
- Some hosts resolve `api.telegram.org` to IPv6 first; broken IPv6 egress can cause intermittent Telegram API failures.
|
||||
- If logs include `TypeError: fetch failed` or `Network request for 'getUpdates' failed!`, OpenClaw now retries these as recoverable network errors.
|
||||
- If logs include `Polling stall detected`, OpenClaw restarts polling and rebuilds the Telegram transport after 120 seconds without completed long-poll liveness by default.
|
||||
- Increase `channels.telegram.pollingStallThresholdMs` only when long-running `getUpdates` calls are healthy but your host still reports false polling-stall restarts. Persistent stalls usually point to proxy, DNS, IPv6, or TLS egress issues between the host and `api.telegram.org`.
|
||||
- On VPS hosts with unstable direct egress/TLS, route Telegram API calls through `channels.telegram.proxy`:
|
||||
|
||||
```yaml
|
||||
@@ -1055,7 +1059,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`, `network.dangerouslyAllowPrivateNetwork`, `proxy`
|
||||
- media/network: `mediaMaxMb`, `timeoutSeconds`, `pollingStallThresholdMs`, `retry`, `network.autoSelectFamily`, `network.dangerouslyAllowPrivateNetwork`, `proxy`
|
||||
- webhook: `webhookUrl`, `webhookSecret`, `webhookPath`, `webhookHost`
|
||||
- actions/capabilities: `capabilities.inlineButtons`, `actions.sendMessage|editMessage|deleteMessage|reactions|sticker`
|
||||
- reactions: `reactionNotifications`, `reactionLevel`
|
||||
|
||||
@@ -25,7 +25,8 @@ openclaw channels status --probe
|
||||
Healthy baseline:
|
||||
|
||||
- `Runtime: running`
|
||||
- `RPC probe: ok`
|
||||
- `Connectivity probe: ok`
|
||||
- `Capability: read-only`, `write-capable`, or `admin-capable`
|
||||
- Channel probe shows transport connected and, where supported, `works` or `audit ok`
|
||||
|
||||
## WhatsApp
|
||||
@@ -44,13 +45,14 @@ Full troubleshooting: [/channels/whatsapp#troubleshooting](/channels/whatsapp#tr
|
||||
|
||||
### Telegram failure signatures
|
||||
|
||||
| Symptom | Fastest check | Fix |
|
||||
| ----------------------------------- | ----------------------------------------------- | --------------------------------------------------------------------------- |
|
||||
| `/start` but no usable reply flow | `openclaw pairing list telegram` | Approve pairing or change DM policy. |
|
||||
| Bot online but group stays silent | Verify mention requirement and bot privacy mode | Disable privacy mode for group visibility or mention bot. |
|
||||
| Send failures with network errors | Inspect logs for Telegram API call failures | Fix DNS/IPv6/proxy routing to `api.telegram.org`. |
|
||||
| `setMyCommands` rejected at startup | Inspect logs for `BOT_COMMANDS_TOO_MUCH` | Reduce plugin/skill/custom Telegram commands or disable native menus. |
|
||||
| Upgraded and allowlist blocks you | `openclaw security audit` and config allowlists | Run `openclaw doctor --fix` or replace `@username` with numeric sender IDs. |
|
||||
| Symptom | Fastest check | Fix |
|
||||
| ----------------------------------- | ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `/start` but no usable reply flow | `openclaw pairing list telegram` | Approve pairing or change DM policy. |
|
||||
| Bot online but group stays silent | Verify mention requirement and bot privacy mode | Disable privacy mode for group visibility or mention bot. |
|
||||
| Send failures with network errors | Inspect logs for Telegram API call failures | Fix DNS/IPv6/proxy routing to `api.telegram.org`. |
|
||||
| Polling stalls or reconnects slowly | `openclaw logs --follow` for polling diagnostics | Upgrade; if restarts are false positives, tune `pollingStallThresholdMs`. Persistent stalls still point to proxy/DNS/IPv6. |
|
||||
| `setMyCommands` rejected at startup | Inspect logs for `BOT_COMMANDS_TOO_MUCH` | Reduce plugin/skill/custom Telegram commands or disable native menus. |
|
||||
| Upgraded and allowlist blocks you | `openclaw security audit` and config allowlists | Run `openclaw doctor --fix` or replace `@username` with numeric sender IDs. |
|
||||
|
||||
Full troubleshooting: [/channels/telegram#troubleshooting](/channels/telegram#troubleshooting)
|
||||
|
||||
|
||||
168
docs/channels/wechat.md
Normal file
168
docs/channels/wechat.md
Normal file
@@ -0,0 +1,168 @@
|
||||
---
|
||||
summary: "WeChat channel setup through the external openclaw-weixin plugin"
|
||||
read_when:
|
||||
- You want to connect OpenClaw to WeChat or Weixin
|
||||
- You are installing or troubleshooting the openclaw-weixin channel plugin
|
||||
- You need to understand how external channel plugins run beside the Gateway
|
||||
title: "WeChat"
|
||||
---
|
||||
|
||||
# WeChat
|
||||
|
||||
OpenClaw connects to WeChat through Tencent's external
|
||||
`@tencent-weixin/openclaw-weixin` channel plugin.
|
||||
|
||||
Status: external plugin. Direct chats and media are supported. Group chats are not
|
||||
advertised by the current plugin capability metadata.
|
||||
|
||||
## Naming
|
||||
|
||||
- **WeChat** is the user-facing name in these docs.
|
||||
- **Weixin** is the name used by Tencent's package and by the plugin id.
|
||||
- `openclaw-weixin` is the OpenClaw channel id.
|
||||
- `@tencent-weixin/openclaw-weixin` is the npm package.
|
||||
|
||||
Use `openclaw-weixin` in CLI commands and config paths.
|
||||
|
||||
## How it works
|
||||
|
||||
The WeChat code does not live in the OpenClaw core repo. OpenClaw provides the
|
||||
generic channel plugin contract, and the external plugin provides the
|
||||
WeChat-specific runtime:
|
||||
|
||||
1. `openclaw plugins install` installs `@tencent-weixin/openclaw-weixin`.
|
||||
2. The Gateway discovers the plugin manifest and loads the plugin entrypoint.
|
||||
3. The plugin registers channel id `openclaw-weixin`.
|
||||
4. `openclaw channels login --channel openclaw-weixin` starts QR login.
|
||||
5. The plugin stores account credentials under the OpenClaw state directory.
|
||||
6. When the Gateway starts, the plugin starts its Weixin monitor for each
|
||||
configured account.
|
||||
7. Inbound WeChat messages are normalized through the channel contract, routed to
|
||||
the selected OpenClaw agent, and sent back through the plugin outbound path.
|
||||
|
||||
That separation matters: OpenClaw core should stay channel-agnostic. WeChat login,
|
||||
Tencent iLink API calls, media upload/download, context tokens, and account
|
||||
monitoring are owned by the external plugin.
|
||||
|
||||
## Install
|
||||
|
||||
Quick install:
|
||||
|
||||
```bash
|
||||
npx -y @tencent-weixin/openclaw-weixin-cli install
|
||||
```
|
||||
|
||||
Manual install:
|
||||
|
||||
```bash
|
||||
openclaw plugins install "@tencent-weixin/openclaw-weixin"
|
||||
openclaw config set plugins.entries.openclaw-weixin.enabled true
|
||||
```
|
||||
|
||||
Restart the Gateway after install:
|
||||
|
||||
```bash
|
||||
openclaw gateway restart
|
||||
```
|
||||
|
||||
## Login
|
||||
|
||||
Run QR login on the same machine that runs the Gateway:
|
||||
|
||||
```bash
|
||||
openclaw channels login --channel openclaw-weixin
|
||||
```
|
||||
|
||||
Scan the QR code with WeChat on your phone and confirm the login. The plugin saves
|
||||
the account token locally after a successful scan.
|
||||
|
||||
To add another WeChat account, run the same login command again. For multiple
|
||||
accounts, isolate direct-message sessions by account, channel, and sender:
|
||||
|
||||
```bash
|
||||
openclaw config set session.dmScope per-account-channel-peer
|
||||
```
|
||||
|
||||
## Access control
|
||||
|
||||
Direct messages use the normal OpenClaw pairing and allowlist model for channel
|
||||
plugins.
|
||||
|
||||
Approve new senders:
|
||||
|
||||
```bash
|
||||
openclaw pairing list openclaw-weixin
|
||||
openclaw pairing approve openclaw-weixin <CODE>
|
||||
```
|
||||
|
||||
For the full access-control model, see [Pairing](/channels/pairing).
|
||||
|
||||
## Compatibility
|
||||
|
||||
The plugin checks the host OpenClaw version at startup.
|
||||
|
||||
| Plugin line | OpenClaw version | npm tag |
|
||||
| ----------- | ----------------------- | -------- |
|
||||
| `2.x` | `>=2026.3.22` | `latest` |
|
||||
| `1.x` | `>=2026.1.0 <2026.3.22` | `legacy` |
|
||||
|
||||
If the plugin reports that your OpenClaw version is too old, either update
|
||||
OpenClaw or install the legacy plugin line:
|
||||
|
||||
```bash
|
||||
openclaw plugins install @tencent-weixin/openclaw-weixin@legacy
|
||||
```
|
||||
|
||||
## Sidecar process
|
||||
|
||||
The WeChat plugin can run helper work beside the Gateway while it monitors the
|
||||
Tencent iLink API. In issue #68451, that helper path exposed a bug in OpenClaw's
|
||||
generic stale-Gateway cleanup: a child process could try to clean up the parent
|
||||
Gateway process, causing restart loops under process managers such as systemd.
|
||||
|
||||
Current OpenClaw startup cleanup excludes the current process and its ancestors,
|
||||
so a channel helper must not kill the Gateway that launched it. This fix is
|
||||
generic; it is not a WeChat-specific path in core.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
Check install and status:
|
||||
|
||||
```bash
|
||||
openclaw plugins list
|
||||
openclaw channels status --probe
|
||||
openclaw --version
|
||||
```
|
||||
|
||||
If the channel shows as installed but does not connect, confirm that the plugin is
|
||||
enabled and restart:
|
||||
|
||||
```bash
|
||||
openclaw config set plugins.entries.openclaw-weixin.enabled true
|
||||
openclaw gateway restart
|
||||
```
|
||||
|
||||
If the Gateway restarts repeatedly after enabling WeChat, update both OpenClaw and
|
||||
the plugin:
|
||||
|
||||
```bash
|
||||
npm view @tencent-weixin/openclaw-weixin version
|
||||
openclaw plugins install "@tencent-weixin/openclaw-weixin" --force
|
||||
openclaw gateway restart
|
||||
```
|
||||
|
||||
Temporary disable:
|
||||
|
||||
```bash
|
||||
openclaw config set plugins.entries.openclaw-weixin.enabled false
|
||||
openclaw gateway restart
|
||||
```
|
||||
|
||||
## Related docs
|
||||
|
||||
- Channel overview: [Chat Channels](/channels)
|
||||
- Pairing: [Pairing](/channels/pairing)
|
||||
- Channel routing: [Channel Routing](/channels/channel-routing)
|
||||
- Plugin architecture: [Plugin Architecture](/plugins/architecture)
|
||||
- Channel plugin SDK: [Channel Plugin SDK](/plugins/sdk-channel-plugins)
|
||||
- External package: [@tencent-weixin/openclaw-weixin](https://www.npmjs.com/package/@tencent-weixin/openclaw-weixin)
|
||||
70
docs/ci.md
70
docs/ci.md
@@ -12,57 +12,71 @@ The CI runs on every push to `main` and every pull request. It uses smart scopin
|
||||
|
||||
## Job Overview
|
||||
|
||||
| Job | Purpose | When it runs |
|
||||
| ------------------------ | --------------------------------------------------------------------------------------- | ----------------------------------- |
|
||||
| `preflight` | Detect docs-only changes, changed scopes, changed extensions, and build the CI manifest | Always on non-draft pushes and PRs |
|
||||
| `security-fast` | Private key detection, workflow audit via `zizmor`, production dependency audit | Always on non-draft pushes and PRs |
|
||||
| `build-artifacts` | Build `dist/` and the Control UI once, upload reusable artifacts for downstream jobs | Node-relevant changes |
|
||||
| `checks-fast-core` | Fast Linux correctness lanes such as bundled/plugin-contract/protocol checks | Node-relevant changes |
|
||||
| `checks-node-extensions` | Full bundled-plugin test shards across the extension suite | Node-relevant changes |
|
||||
| `checks-node-core-test` | Core Node test shards, excluding channel, bundled, contract, and extension lanes | Node-relevant changes |
|
||||
| `extension-fast` | Focused tests for only the changed bundled plugins | When extension changes are detected |
|
||||
| `check` | Main local gate in CI: `pnpm check` plus `pnpm build:strict-smoke` | Node-relevant changes |
|
||||
| `check-additional` | Architecture, boundary, import-cycle guards plus the gateway watch regression harness | Node-relevant changes |
|
||||
| `build-smoke` | Built-CLI smoke tests and startup-memory smoke | Node-relevant changes |
|
||||
| `checks` | Remaining Linux Node lanes: channel tests and push-only Node 22 compatibility | Node-relevant changes |
|
||||
| `check-docs` | Docs formatting, lint, and broken-link checks | Docs changed |
|
||||
| `skills-python` | Ruff + pytest for Python-backed skills | Python-skill-relevant changes |
|
||||
| `checks-windows` | Windows-specific test lanes | Windows-relevant changes |
|
||||
| `macos-node` | macOS TypeScript test lane using the shared built artifacts | macOS-relevant changes |
|
||||
| `macos-swift` | Swift lint, build, and tests for the macOS app | macOS-relevant changes |
|
||||
| `android` | Android build and test matrix | Android-relevant changes |
|
||||
| Job | Purpose | When it runs |
|
||||
| -------------------------------- | -------------------------------------------------------------------------------------------- | ----------------------------------- |
|
||||
| `preflight` | Detect docs-only changes, changed scopes, changed extensions, and build the CI manifest | Always on non-draft pushes and PRs |
|
||||
| `security-scm-fast` | Private key detection and workflow audit via `zizmor` | Always on non-draft pushes and PRs |
|
||||
| `security-dependency-audit` | Dependency-free production lockfile audit against npm advisories | Always on non-draft pushes and PRs |
|
||||
| `security-fast` | Required aggregate for the fast security jobs | Always on non-draft pushes and PRs |
|
||||
| `build-artifacts` | Build `dist/` and the Control UI once, upload reusable artifacts for downstream jobs | Node-relevant changes |
|
||||
| `checks-fast-core` | Fast Linux correctness lanes such as bundled/plugin-contract/protocol checks | Node-relevant changes |
|
||||
| `checks-fast-contracts-channels` | Sharded channel contract checks with a stable aggregate check result | Node-relevant changes |
|
||||
| `checks-node-extensions` | Full bundled-plugin test shards across the extension suite | Node-relevant changes |
|
||||
| `checks-node-core-test` | Core Node test shards, excluding channel, bundled, contract, and extension lanes | Node-relevant changes |
|
||||
| `extension-fast` | Focused tests for only the changed bundled plugins | When extension changes are detected |
|
||||
| `check` | Sharded main local gate equivalent: prod types, lint, guards, test types, and strict smoke | Node-relevant changes |
|
||||
| `check-additional` | Architecture, boundary, extension-surface guards, package-boundary, and gateway-watch shards | Node-relevant changes |
|
||||
| `build-smoke` | Built-CLI smoke tests and startup-memory smoke | Node-relevant changes |
|
||||
| `checks` | Remaining Linux Node lanes: channel tests and push-only Node 22 compatibility | Node-relevant changes |
|
||||
| `check-docs` | Docs formatting, lint, and broken-link checks | Docs changed |
|
||||
| `skills-python` | Ruff + pytest for Python-backed skills | Python-skill-relevant changes |
|
||||
| `checks-windows` | Windows-specific test lanes | Windows-relevant changes |
|
||||
| `macos-node` | macOS TypeScript test lane using the shared built artifacts | macOS-relevant changes |
|
||||
| `macos-swift` | Swift lint, build, and tests for the macOS app | macOS-relevant changes |
|
||||
| `android` | Android build and test matrix | Android-relevant changes |
|
||||
|
||||
## Fail-Fast Order
|
||||
|
||||
Jobs are ordered so cheap checks fail before expensive ones run:
|
||||
|
||||
1. `preflight` decides which lanes exist at all. The `docs-scope` and `changed-scope` logic are steps inside this job, not standalone jobs.
|
||||
2. `security-fast`, `check`, `check-additional`, `check-docs`, and `skills-python` fail quickly without waiting on the heavier artifact and platform matrix jobs.
|
||||
2. `security-scm-fast`, `security-dependency-audit`, `security-fast`, `check`, `check-additional`, `check-docs`, and `skills-python` fail quickly without waiting on the heavier artifact and platform matrix jobs.
|
||||
3. `build-artifacts` overlaps with the fast Linux lanes so downstream consumers can start as soon as the shared build is ready.
|
||||
4. Heavier platform and runtime lanes fan out after that: `checks-fast-core`, `checks-node-extensions`, `checks-node-core-test`, `extension-fast`, `checks`, `checks-windows`, `macos-node`, `macos-swift`, and `android`.
|
||||
4. Heavier platform and runtime lanes fan out after that: `checks-fast-core`, `checks-fast-contracts-channels`, `checks-node-extensions`, `checks-node-core-test`, `extension-fast`, `checks`, `checks-windows`, `macos-node`, `macos-swift`, and `android`.
|
||||
|
||||
Scope logic lives in `scripts/ci-changed-scope.mjs` and is covered by unit tests in `src/scripts/ci-changed-scope.test.ts`.
|
||||
The separate `install-smoke` workflow reuses the same scope script through its own `preflight` job. It computes `run_install_smoke` from the narrower changed-smoke signal, so Docker/install smoke only runs for install, packaging, and container-relevant changes.
|
||||
|
||||
Local changed-lane logic lives in `scripts/changed-lanes.mjs` and is executed by `scripts/check-changed.mjs`. That local gate is stricter about architecture boundaries than the broad CI platform scope: core production changes run core prod typecheck plus core tests, core test-only changes run only core test typecheck/tests, extension production changes run extension prod typecheck plus extension tests, and extension test-only changes run only extension test typecheck/tests. Public Plugin SDK or plugin-contract changes expand to extension validation because extensions depend on those core contracts. Unknown root/config changes fail safe to all lanes.
|
||||
|
||||
On pushes, the `checks` matrix adds the push-only `compat-node22` lane. On pull requests, that lane is skipped and the matrix stays focused on the normal test/channel lanes.
|
||||
|
||||
The slowest Node test families are split into include-file shards so each job stays small: channel contracts split registry and core coverage into eight weighted shards each, auto-reply reply command tests split into four include-pattern shards, and the other large auto-reply reply prefix groups split into two shards each. `check-additional` also separates package-boundary compile/canary work from runtime topology gateway/architecture work.
|
||||
|
||||
GitHub may mark superseded jobs as `cancelled` when a newer push lands on the same PR or `main` ref. Treat that as CI noise unless the newest run for the same ref is also failing. The aggregate shard checks call out this cancellation case explicitly so it is easier to distinguish from a test failure.
|
||||
|
||||
## Runners
|
||||
|
||||
| Runner | Jobs |
|
||||
| -------------------------------- | ---------------------------------------------------------------------------------------------------- |
|
||||
| `blacksmith-16vcpu-ubuntu-2404` | `preflight`, `security-fast`, `build-artifacts`, Linux checks, docs checks, Python skills, `android` |
|
||||
| `blacksmith-32vcpu-windows-2025` | `checks-windows` |
|
||||
| `macos-latest` | `macos-node`, `macos-swift` |
|
||||
| Runner | Jobs |
|
||||
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `blacksmith-16vcpu-ubuntu-2404` | `preflight`, `security-scm-fast`, `security-dependency-audit`, `security-fast`, `build-artifacts`, Linux checks, docs checks, Python skills, `android` |
|
||||
| `blacksmith-32vcpu-windows-2025` | `checks-windows` |
|
||||
| `macos-latest` | `macos-node`, `macos-swift` |
|
||||
|
||||
## Local Equivalents
|
||||
|
||||
```bash
|
||||
pnpm check # types + lint + format
|
||||
pnpm changed:lanes # inspect the local changed-lane classifier for origin/main...HEAD
|
||||
pnpm check:changed # smart local gate: changed typecheck/lint/tests by boundary lane
|
||||
pnpm check # fast local gate: production tsgo + sharded lint + parallel fast guards
|
||||
pnpm check:test-types
|
||||
pnpm check:timed # same gate with per-stage timings
|
||||
pnpm build:strict-smoke
|
||||
pnpm check:import-cycles
|
||||
pnpm check:architecture
|
||||
pnpm test:gateway:watch-regression
|
||||
pnpm test # vitest tests
|
||||
pnpm test:channels
|
||||
pnpm test:contracts:channels
|
||||
pnpm check:docs # docs format + lint + broken links
|
||||
pnpm build # build dist when CI artifact/build-smoke lanes matter
|
||||
```
|
||||
|
||||
@@ -26,7 +26,7 @@ Related:
|
||||
- `-t, --to <dest>`: recipient used to derive the session key
|
||||
- `--session-id <id>`: explicit session id
|
||||
- `--agent <id>`: agent id; overrides routing bindings
|
||||
- `--thinking <off|minimal|low|medium|high|xhigh>`: agent thinking level
|
||||
- `--thinking <level>`: agent thinking level (`off`, `minimal`, `low`, `medium`, `high`, plus provider-supported custom levels such as `xhigh`, `adaptive`, or `max`)
|
||||
- `--verbose <on|off>`: persist verbose level for the session
|
||||
- `--channel <channel>`: delivery channel; omit to use the main session channel
|
||||
- `--reply-to <target>`: delivery target override
|
||||
|
||||
@@ -336,6 +336,34 @@ If dry-run fails:
|
||||
- `Dry run note: skipped <n> exec SecretRef resolvability check(s)`: dry-run skipped exec refs; rerun with `--allow-exec` if you need exec resolvability validation.
|
||||
- For batch mode, fix failing entries and rerun `--dry-run` before writing.
|
||||
|
||||
## Write safety
|
||||
|
||||
`openclaw config set` and other OpenClaw-owned config writers validate the full
|
||||
post-change config before committing it to disk. If the new payload fails schema
|
||||
validation or looks like a destructive clobber, the active config is left alone
|
||||
and the rejected payload is saved beside it as `openclaw.json.rejected.*`.
|
||||
|
||||
Prefer CLI writes for small edits:
|
||||
|
||||
```bash
|
||||
openclaw config set gateway.reload.mode hybrid --dry-run
|
||||
openclaw config set gateway.reload.mode hybrid
|
||||
openclaw config validate
|
||||
```
|
||||
|
||||
If a write is rejected, inspect the saved payload and fix the full config shape:
|
||||
|
||||
```bash
|
||||
CONFIG="$(openclaw config file)"
|
||||
ls -lt "$CONFIG".rejected.* 2>/dev/null | head
|
||||
openclaw config validate
|
||||
```
|
||||
|
||||
Direct editor writes are still allowed, but the running Gateway treats them as
|
||||
untrusted until they validate. Invalid direct edits can be restored from the
|
||||
last-known-good backup during startup or hot reload. See
|
||||
[Gateway troubleshooting](/gateway/troubleshooting#gateway-restored-last-known-good-config).
|
||||
|
||||
## Subcommands
|
||||
|
||||
- `config file`: Print the active config file path (resolved from `OPENCLAW_CONFIG_PATH` or default location).
|
||||
|
||||
@@ -16,12 +16,16 @@ Related:
|
||||
|
||||
Tip: run `openclaw cron --help` for the full command surface.
|
||||
|
||||
Note: `openclaw cron list` and `openclaw cron show <job-id>` preview the
|
||||
resolved delivery route. For `channel: "last"`, the preview shows whether the
|
||||
route resolved from the main/current session or will fail closed.
|
||||
|
||||
Note: isolated `cron add` jobs default to `--announce` delivery. Use `--no-deliver` to keep
|
||||
output internal. `--deliver` remains as a deprecated alias for `--announce`.
|
||||
|
||||
Note: cron-owned isolated runs expect a plain-text summary and the runner owns
|
||||
the final send path. `--no-deliver` keeps the run internal; it does not hand
|
||||
delivery back to the agent's message tool.
|
||||
Note: isolated cron chat delivery is shared. `--announce` is runner fallback
|
||||
delivery for the final reply; `--no-deliver` disables that fallback but does
|
||||
not remove the agent's `message` tool when a chat route is available.
|
||||
|
||||
Note: one-shot (`--at`) jobs delete after success by default. Use `--keep-after-run` to keep them.
|
||||
|
||||
@@ -124,22 +128,27 @@ openclaw cron add \
|
||||
|
||||
Delivery ownership note:
|
||||
|
||||
- Cron-owned isolated jobs always route final user-visible delivery through the
|
||||
cron runner (`announce`, `webhook`, or internal-only `none`).
|
||||
- If the task mentions messaging some external recipient, the agent should
|
||||
describe the intended destination in its result instead of trying to send it
|
||||
directly.
|
||||
- Isolated cron chat delivery is shared. The agent can send directly with the
|
||||
`message` tool when a chat route is available.
|
||||
- `announce` fallback-delivers the final reply only when the agent did not send
|
||||
directly to the resolved target. `webhook` posts the finished payload to a URL.
|
||||
`none` disables runner fallback delivery.
|
||||
|
||||
## Common admin commands
|
||||
|
||||
Manual run:
|
||||
|
||||
```bash
|
||||
openclaw cron list
|
||||
openclaw cron show <job-id>
|
||||
openclaw cron run <job-id>
|
||||
openclaw cron run <job-id> --due
|
||||
openclaw cron runs --id <job-id> --limit 50
|
||||
```
|
||||
|
||||
`cron runs` entries include delivery diagnostics with the intended cron target,
|
||||
the resolved target, message-tool sends, fallback use, and delivered state.
|
||||
|
||||
Agent/session retargeting:
|
||||
|
||||
```bash
|
||||
|
||||
@@ -21,8 +21,9 @@ openclaw devices list
|
||||
openclaw devices list --json
|
||||
```
|
||||
|
||||
Pending request output includes the requested role and scopes so approvals can
|
||||
be reviewed before you approve.
|
||||
Pending request output shows the requested access next to the device's current
|
||||
approved access when the device is already paired. This makes scope/role
|
||||
upgrades explicit instead of looking like the pairing was lost.
|
||||
|
||||
### `openclaw devices remove <deviceId>`
|
||||
|
||||
@@ -59,6 +60,12 @@ key), OpenClaw supersedes the previous pending entry and issues a new
|
||||
`requestId`. Run `openclaw devices list` right before approval to use the
|
||||
current ID.
|
||||
|
||||
If the device is already paired and asks for broader scopes or a broader role,
|
||||
OpenClaw keeps the existing approval in place and creates a new pending upgrade
|
||||
request. Review the `Requested` vs `Approved` columns in `openclaw devices list`
|
||||
or use `openclaw devices approve --latest` to preview the exact upgrade before
|
||||
approving it.
|
||||
|
||||
```
|
||||
openclaw devices approve
|
||||
openclaw devices approve <requestId>
|
||||
|
||||
@@ -63,6 +63,11 @@ Notes:
|
||||
- `--raw-stream`: log raw model stream events to jsonl.
|
||||
- `--raw-stream-path <path>`: raw stream jsonl path.
|
||||
|
||||
Startup profiling:
|
||||
|
||||
- Set `OPENCLAW_GATEWAY_STARTUP_TRACE=1` to log phase timings during Gateway startup.
|
||||
- Run `pnpm test:startup:gateway -- --runs 5 --warmup 1` to benchmark Gateway startup. The benchmark records first process output, `/healthz`, `/readyz`, and startup trace timings.
|
||||
|
||||
## Query a running Gateway
|
||||
|
||||
All query commands use WebSocket RPC.
|
||||
@@ -90,6 +95,8 @@ Pass `--token` or `--password` explicitly. Missing explicit credentials is an er
|
||||
openclaw gateway health --url ws://127.0.0.1:18789
|
||||
```
|
||||
|
||||
The HTTP `/healthz` endpoint is a liveness probe: it returns once the server can answer HTTP. The HTTP `/readyz` endpoint is stricter and stays red while startup sidecars, channels, or configured hooks are still settling.
|
||||
|
||||
### `gateway usage-cost`
|
||||
|
||||
Fetch usage-cost summaries from session logs.
|
||||
@@ -106,7 +113,7 @@ Options:
|
||||
|
||||
### `gateway status`
|
||||
|
||||
`gateway status` shows the Gateway service (launchd/systemd/schtasks) plus an optional RPC probe.
|
||||
`gateway status` shows the Gateway service (launchd/systemd/schtasks) plus an optional probe of connectivity/auth capability.
|
||||
|
||||
```bash
|
||||
openclaw gateway status
|
||||
@@ -120,17 +127,18 @@ Options:
|
||||
- `--token <token>`: token auth for the probe.
|
||||
- `--password <password>`: password auth for the probe.
|
||||
- `--timeout <ms>`: probe timeout (default `10000`).
|
||||
- `--no-probe`: skip the RPC probe (service-only view).
|
||||
- `--no-probe`: skip the connectivity probe (service-only view).
|
||||
- `--deep`: scan system-level services too.
|
||||
- `--require-rpc`: exit non-zero when the RPC probe fails. Cannot be combined with `--no-probe`.
|
||||
- `--require-rpc`: upgrade the default connectivity probe to a read probe and exit non-zero when that read probe fails. Cannot be combined with `--no-probe`.
|
||||
|
||||
Notes:
|
||||
|
||||
- `gateway status` stays available for diagnostics even when the local CLI config is missing or invalid.
|
||||
- Default `gateway status` proves service state, WebSocket connect, and the auth capability visible at handshake time. It does not prove read/write/admin operations.
|
||||
- `gateway status` resolves configured auth SecretRefs for probe auth when possible.
|
||||
- If a required auth SecretRef is unresolved in this command path, `gateway status --json` reports `rpc.authWarning` when probe connectivity/auth fails; pass `--token`/`--password` explicitly or resolve the secret source first.
|
||||
- If the probe succeeds, unresolved auth-ref warnings are suppressed to avoid false positives.
|
||||
- Use `--require-rpc` in scripts and automation when a listening service is not enough and you need the Gateway RPC itself to be healthy.
|
||||
- Use `--require-rpc` in scripts and automation when a listening service is not enough and you need read-scope RPC calls to be healthy too.
|
||||
- `--deep` adds a best-effort scan for extra launchd/systemd/schtasks installs. When multiple gateway-like services are detected, human output prints cleanup hints and warns that most setups should run one gateway per machine.
|
||||
- Human output includes the resolved file log path plus the CLI-vs-service config paths/validity snapshot to help diagnose profile or state-dir drift.
|
||||
- On Linux systemd installs, service auth drift checks read both `Environment=` and `EnvironmentFile=` values from the unit (including `%h`, quoted paths, multiple files, and optional `-` files).
|
||||
@@ -161,8 +169,9 @@ openclaw gateway probe --json
|
||||
Interpretation:
|
||||
|
||||
- `Reachable: yes` means at least one target accepted a WebSocket connect.
|
||||
- `RPC: ok` means detail RPC calls (`health`/`status`/`system-presence`/`config.get`) also succeeded.
|
||||
- `RPC: limited - missing scope: operator.read` means connect succeeded but detail RPC is scope-limited. This is reported as **degraded** reachability, not full failure.
|
||||
- `Capability: read-only|write-capable|admin-capable|pairing-pending|connect-only` reports what the probe could prove about auth. It is separate from reachability.
|
||||
- `Read probe: ok` means read-scope detail RPC calls (`health`/`status`/`system-presence`/`config.get`) also succeeded.
|
||||
- `Read probe: limited - missing scope: operator.read` means connect succeeded but read-scope RPC is limited. This is reported as **degraded** reachability, not full failure.
|
||||
- Exit code is non-zero only when no probed target is reachable.
|
||||
|
||||
JSON notes (`--json`):
|
||||
@@ -170,6 +179,7 @@ JSON notes (`--json`):
|
||||
- Top level:
|
||||
- `ok`: at least one target is reachable.
|
||||
- `degraded`: at least one target had scope-limited detail RPC.
|
||||
- `capability`: best capability seen across reachable targets (`read_only`, `write_capable`, `admin_capable`, `pairing_pending`, `connected_no_operator_scope`, or `unknown`).
|
||||
- `primaryTargetId`: best target to treat as the active winner in this order: explicit URL, SSH tunnel, configured remote, then local loopback.
|
||||
- `warnings[]`: best-effort warning records with `code`, `message`, and optional `targetIds`.
|
||||
- `network`: local loopback/tailnet URL hints derived from current config and host networking.
|
||||
@@ -178,13 +188,17 @@ JSON notes (`--json`):
|
||||
- `ok`: reachability after connect + degraded classification.
|
||||
- `rpcOk`: full detail RPC success.
|
||||
- `scopeLimited`: detail RPC failed due to missing operator scope.
|
||||
- Per target (`targets[].auth`):
|
||||
- `role`: auth role reported in `hello-ok` when available.
|
||||
- `scopes`: granted scopes reported in `hello-ok` when available.
|
||||
- `capability`: the surfaced auth capability classification for that target.
|
||||
|
||||
Common warning codes:
|
||||
|
||||
- `ssh_tunnel_failed`: SSH tunnel setup failed; the command fell back to direct probes.
|
||||
- `multiple_gateways`: more than one target was reachable; this is unusual unless you intentionally run isolated profiles, such as a rescue bot.
|
||||
- `auth_secretref_unresolved`: a configured auth SecretRef could not be resolved for a failed target.
|
||||
- `probe_scope_limited`: WebSocket connect succeeded, but detail RPC was limited by missing `operator.read`.
|
||||
- `probe_scope_limited`: WebSocket connect succeeded, but the read probe was limited by missing `operator.read`.
|
||||
|
||||
#### Remote over SSH (Mac app parity)
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ openclaw hooks list
|
||||
```
|
||||
|
||||
List all discovered hooks from workspace, managed, extra, and bundled directories.
|
||||
Gateway startup does not load internal hook handlers until at least one internal hook is configured.
|
||||
|
||||
**Options:**
|
||||
|
||||
|
||||
@@ -994,7 +994,7 @@ Options:
|
||||
- `-t, --to <dest>` (for session key and optional delivery)
|
||||
- `--session-id <id>`
|
||||
- `--agent <id>` (agent id; overrides routing bindings)
|
||||
- `--thinking <off|minimal|low|medium|high|xhigh>` (provider support varies; not model-gated at CLI level)
|
||||
- `--thinking <level>` (validated against the selected model's provider profile)
|
||||
- `--verbose <on|off>`
|
||||
- `--channel <channel>` (delivery channel; omit to use the main session channel)
|
||||
- `--reply-to <target>` (delivery target override, separate from session routing)
|
||||
|
||||
@@ -625,10 +625,10 @@ The most important fields are:
|
||||
| `config.model` | `string` | Optional blocking memory sub-agent model ref; when unset, active memory uses the current session model |
|
||||
| `config.queryMode` | `"message" \| "recent" \| "full"` | Controls how much conversation the blocking memory sub-agent sees |
|
||||
| `config.promptStyle` | `"balanced" \| "strict" \| "contextual" \| "recall-heavy" \| "precision-heavy" \| "preference-only"` | Controls how eager or strict the blocking memory sub-agent is when deciding whether to return memory |
|
||||
| `config.thinking` | `"off" \| "minimal" \| "low" \| "medium" \| "high" \| "xhigh" \| "adaptive"` | Advanced thinking override for the blocking memory sub-agent; default `off` for speed |
|
||||
| `config.thinking` | `"off" \| "minimal" \| "low" \| "medium" \| "high" \| "xhigh" \| "adaptive" \| "max"` | Advanced thinking override for the blocking memory sub-agent; default `off` for speed |
|
||||
| `config.promptOverride` | `string` | Advanced full prompt replacement; not recommended for normal use |
|
||||
| `config.promptAppend` | `string` | Advanced extra instructions appended to the default or overridden prompt |
|
||||
| `config.timeoutMs` | `number` | Hard timeout for the blocking memory sub-agent |
|
||||
| `config.timeoutMs` | `number` | Hard timeout for the blocking memory sub-agent, capped at 120000 ms |
|
||||
| `config.maxSummaryChars` | `number` | Maximum total characters allowed in the active-memory summary |
|
||||
| `config.logging` | `boolean` | Emits active memory logs while tuning |
|
||||
| `config.persistTranscripts` | `boolean` | Keeps blocking memory sub-agent transcripts on disk instead of deleting temp files |
|
||||
|
||||
@@ -132,10 +132,10 @@ capable model for better summaries:
|
||||
}
|
||||
```
|
||||
|
||||
## Compaction start notice
|
||||
## Compaction notices
|
||||
|
||||
By default, compaction runs silently. To show a brief notice when compaction
|
||||
starts, enable `notifyUser`:
|
||||
By default, compaction runs silently. To show brief notices when compaction
|
||||
starts and when it completes, enable `notifyUser`:
|
||||
|
||||
```json5
|
||||
{
|
||||
@@ -149,8 +149,8 @@ starts, enable `notifyUser`:
|
||||
}
|
||||
```
|
||||
|
||||
When enabled, the user sees a short message (for example, "Compacting
|
||||
context...") at the start of each compaction run.
|
||||
When enabled, the user sees short status messages around each compaction run
|
||||
(for example, "Compacting context..." and "Compaction complete").
|
||||
|
||||
## Compaction vs pruning
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ Config (global default + per-channel overrides):
|
||||
Notes:
|
||||
|
||||
- Debounce applies to **text-only** messages; media/attachments flush immediately.
|
||||
- Control commands bypass debouncing so they remain standalone.
|
||||
- Control commands bypass debouncing so they remain standalone — **except** when a channel explicitly opts in to same-sender DM coalescing (e.g. [BlueBubbles `coalesceSameSenderDms`](/channels/bluebubbles#coalescing-split-send-dms-command--url-in-one-composition)), where DM commands wait inside the debounce window so a split-send payload can join the same agent turn.
|
||||
|
||||
## Sessions and devices
|
||||
|
||||
@@ -153,6 +153,20 @@ Outbound message formatting is centralized in `messages`:
|
||||
|
||||
Details: [Configuration](/gateway/configuration-reference#messages) and channel docs.
|
||||
|
||||
## Silent replies
|
||||
|
||||
The exact silent token `NO_REPLY` / `no_reply` means “do not deliver a user-visible reply”.
|
||||
OpenClaw resolves that behavior by conversation type:
|
||||
|
||||
- Direct conversations disallow silence by default and rewrite a bare silent
|
||||
reply to a short visible fallback.
|
||||
- Groups/channels allow silence by default.
|
||||
- Internal orchestration allows silence by default.
|
||||
|
||||
Defaults live under `agents.defaults.silentReply` and
|
||||
`agents.defaults.silentReplyRewrite`; `surfaces.<id>.silentReply` and
|
||||
`surfaces.<id>.silentReplyRewrite` can override them per surface.
|
||||
|
||||
## Related
|
||||
|
||||
- [Streaming](/concepts/streaming) — real-time message delivery
|
||||
|
||||
@@ -42,8 +42,9 @@ For model selection rules, see [/concepts/models](/concepts/models).
|
||||
`buildAuthDoctorHint`,
|
||||
`matchesContextOverflowError`, `classifyFailoverReason`,
|
||||
`isCacheTtlEligible`, `buildMissingAuthMessage`, `suppressBuiltInModel`,
|
||||
`augmentModelCatalog`, `isBinaryThinking`, `supportsXHighThinking`,
|
||||
`resolveDefaultThinkingLevel`, `applyConfigDefaults`, `isModernModelRef`,
|
||||
`augmentModelCatalog`, `resolveThinkingProfile`, `isBinaryThinking`,
|
||||
`supportsXHighThinking`, `resolveDefaultThinkingLevel`,
|
||||
`applyConfigDefaults`, `isModernModelRef`,
|
||||
`prepareRuntimeAuth`, `resolveUsageAuth`, `fetchUsageSnapshot`, and
|
||||
`onModelSelected`.
|
||||
- Note: provider runtime `capabilities` is shared runner metadata (provider
|
||||
@@ -131,10 +132,11 @@ Typical split:
|
||||
vendor-owned error for direct resolution failures
|
||||
- `augmentModelCatalog`: provider appends synthetic/final catalog rows after
|
||||
discovery and config merging
|
||||
- `isBinaryThinking`: provider owns binary on/off thinking UX
|
||||
- `supportsXHighThinking`: provider opts selected models into `xhigh`
|
||||
- `resolveDefaultThinkingLevel`: provider owns default `/think` policy for a
|
||||
model family
|
||||
- `resolveThinkingProfile`: provider owns the exact `/think` level set,
|
||||
optional display labels, and default level for a selected model
|
||||
- `isBinaryThinking`: compatibility hook for binary on/off thinking UX
|
||||
- `supportsXHighThinking`: compatibility hook for selected `xhigh` models
|
||||
- `resolveDefaultThinkingLevel`: compatibility hook for default `/think` policy
|
||||
- `applyConfigDefaults`: provider applies provider-specific global defaults
|
||||
during config materialization based on auth mode, env, or model family
|
||||
- `isModernModelRef`: provider owns live/smoke preferred-model matching
|
||||
@@ -431,7 +433,7 @@ See [/providers/kilocode](/providers/kilocode) for setup details.
|
||||
`input: ["text", "image"]`; the bundled provider catalog keeps the chat refs
|
||||
text-only until that provider config is materialized
|
||||
- Moonshot: `moonshot` (`MOONSHOT_API_KEY`)
|
||||
- Example model: `moonshot/kimi-k2.5`
|
||||
- Example model: `moonshot/kimi-k2.6`
|
||||
- Kimi Coding: `kimi` (`KIMI_API_KEY` or `KIMICODE_API_KEY`)
|
||||
- Example model: `kimi/kimi-code`
|
||||
- Qianfan: `qianfan` (`QIANFAN_API_KEY`)
|
||||
@@ -488,13 +490,14 @@ need to override the base URL or model metadata:
|
||||
|
||||
- Provider: `moonshot`
|
||||
- Auth: `MOONSHOT_API_KEY`
|
||||
- Example model: `moonshot/kimi-k2.5`
|
||||
- Example model: `moonshot/kimi-k2.6`
|
||||
- CLI: `openclaw onboard --auth-choice moonshot-api-key` or `openclaw onboard --auth-choice moonshot-api-key-cn`
|
||||
|
||||
Kimi K2 model IDs:
|
||||
|
||||
[//]: # "moonshot-kimi-k2-model-refs:start"
|
||||
|
||||
- `moonshot/kimi-k2.6`
|
||||
- `moonshot/kimi-k2.5`
|
||||
- `moonshot/kimi-k2-thinking`
|
||||
- `moonshot/kimi-k2-thinking-turbo`
|
||||
@@ -505,7 +508,7 @@ Kimi K2 model IDs:
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
defaults: { model: { primary: "moonshot/kimi-k2.5" } },
|
||||
defaults: { model: { primary: "moonshot/kimi-k2.6" } },
|
||||
},
|
||||
models: {
|
||||
mode: "merge",
|
||||
@@ -514,7 +517,7 @@ Kimi K2 model IDs:
|
||||
baseUrl: "https://api.moonshot.ai/v1",
|
||||
apiKey: "${MOONSHOT_API_KEY}",
|
||||
api: "openai-completions",
|
||||
models: [{ id: "kimi-k2.5", name: "Kimi K2.5" }],
|
||||
models: [{ id: "kimi-k2.6", name: "Kimi K2.6" }],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -80,6 +80,8 @@ disposable server. It requires `OPENCLAW_QA_TELEGRAM_GROUP_ID`,
|
||||
private group. The SUT bot must have a Telegram username, and bot-to-bot
|
||||
observation works best when both bots have Bot-to-Bot Communication Mode
|
||||
enabled in `@BotFather`.
|
||||
The command exits non-zero when any scenario fails. Use `--allow-failures` when
|
||||
you want artifacts without a failing exit code.
|
||||
|
||||
Live transport lanes now share one smaller contract instead of each inventing
|
||||
their own scenario list shape:
|
||||
@@ -107,9 +109,11 @@ inside the guest, runs `qa suite`, then copies the normal QA report and
|
||||
summary back into `.artifacts/qa-e2e/...` on the host.
|
||||
It reuses the same scenario-selection behavior as `qa suite` on the host.
|
||||
Host and Multipass suite runs execute multiple selected scenarios in parallel
|
||||
with isolated gateway workers by default, up to 64 workers or the selected
|
||||
scenario count. Use `--concurrency <count>` to tune the worker count, or
|
||||
`--concurrency 1` for serial execution.
|
||||
with isolated gateway workers by default. `qa-channel` defaults to concurrency
|
||||
4, capped by the selected scenario count. Use `--concurrency <count>` to tune
|
||||
the worker count, or `--concurrency 1` for serial execution.
|
||||
The command exits non-zero when any scenario fails. Use `--allow-failures` when
|
||||
you want artifacts without a failing exit code.
|
||||
Live runs forward the supported QA auth inputs that are practical for the
|
||||
guest: env-based provider keys, the QA live provider config path, and
|
||||
`CODEX_HOME` when present. Keep `--output-dir` under the repo root so the guest
|
||||
|
||||
@@ -24,11 +24,19 @@ Use provider-owned contributions for model-family-specific tuning. Keep legacy
|
||||
`before_prompt_build` prompt mutation for compatibility or truly global prompt
|
||||
changes, not normal provider behavior.
|
||||
|
||||
The OpenAI GPT-5 family overlay keeps the core execution rule small and adds
|
||||
model-specific guidance for persona latching, concise output, tool discipline,
|
||||
parallel lookup, deliverable coverage, verification, missing context, and
|
||||
terminal-tool hygiene.
|
||||
|
||||
## Structure
|
||||
|
||||
The prompt is intentionally compact and uses fixed sections:
|
||||
|
||||
- **Tooling**: structured-tool source-of-truth reminder plus runtime tool-use guidance.
|
||||
- **Execution Bias**: compact follow-through guidance: act in-turn on
|
||||
actionable requests, continue until done or blocked, recover from weak tool
|
||||
results, check mutable state live, and verify before finalizing.
|
||||
- **Safety**: short guardrail reminder to avoid power-seeking behavior or bypassing oversight.
|
||||
- **Skills** (when available): tells the model how to load skill instructions on demand.
|
||||
- **OpenClaw Self-Update**: how to inspect config safely with
|
||||
|
||||
@@ -61,14 +61,14 @@ node --import tsx scripts/repro/tsx-name-repro.ts
|
||||
## Workarounds
|
||||
|
||||
- Use Bun for dev scripts (current temporary revert).
|
||||
- Use Node + tsc watch, then run compiled output:
|
||||
- Use `tsgo` for repo type checking, then run the built output:
|
||||
|
||||
```bash
|
||||
pnpm exec tsc --watch --preserveWatchOutput
|
||||
node --watch openclaw.mjs status
|
||||
pnpm tsgo
|
||||
node openclaw.mjs status
|
||||
```
|
||||
|
||||
- Confirmed locally: `pnpm exec tsc -p tsconfig.json` + `node openclaw.mjs status` works on Node 25.
|
||||
- Historical note: `tsc` was used here while debugging this Node/tsx issue, but repo type-check lanes now use `tsgo`.
|
||||
- Disable esbuild keepNames in the TS loader if possible (prevents `__name` helper insertion); tsx does not currently expose this.
|
||||
- Test Node LTS (22/24) with `tsx` to see if the issue is Node 25–specific.
|
||||
|
||||
|
||||
@@ -456,6 +456,14 @@
|
||||
"source": "/channels/grammy",
|
||||
"destination": "/channels/telegram"
|
||||
},
|
||||
{
|
||||
"source": "/channels/openclaw-weixin",
|
||||
"destination": "/channels/wechat"
|
||||
},
|
||||
{
|
||||
"source": "/channels/weixin",
|
||||
"destination": "/channels/wechat"
|
||||
},
|
||||
{
|
||||
"source": "/group-messages",
|
||||
"destination": "/channels/group-messages"
|
||||
@@ -1028,6 +1036,7 @@
|
||||
"channels/telegram",
|
||||
"channels/tlon",
|
||||
"channels/twitch",
|
||||
"channels/wechat",
|
||||
"channels/whatsapp",
|
||||
"channels/zalo",
|
||||
"channels/zalouser"
|
||||
|
||||
@@ -1392,7 +1392,7 @@ Periodic heartbeat runs.
|
||||
identifierInstructions: "Preserve deployment IDs, ticket IDs, and host:port pairs exactly.", // used when identifierPolicy=custom
|
||||
postCompactionSections: ["Session Startup", "Red Lines"], // [] disables reinjection
|
||||
model: "openrouter/anthropic/claude-sonnet-4-6", // optional compaction-only model override
|
||||
notifyUser: true, // send a brief notice when compaction starts (default: false)
|
||||
notifyUser: true, // send brief notices when compaction starts and completes (default: false)
|
||||
memoryFlush: {
|
||||
enabled: true,
|
||||
softThresholdTokens: 6000,
|
||||
@@ -1412,7 +1412,7 @@ Periodic heartbeat runs.
|
||||
- `identifierInstructions`: optional custom identifier-preservation text used when `identifierPolicy=custom`.
|
||||
- `postCompactionSections`: optional AGENTS.md H2/H3 section names to re-inject after compaction. Defaults to `["Session Startup", "Red Lines"]`; set `[]` to disable reinjection. When unset or explicitly set to that default pair, older `Every Session`/`Safety` headings are also accepted as a legacy fallback.
|
||||
- `model`: optional `provider/model-id` override for compaction summarization only. Use this when the main session should keep one model but compaction summaries should run on another; when unset, compaction uses the session's primary model.
|
||||
- `notifyUser`: when `true`, sends a brief notice to the user when compaction starts (for example, "Compacting context..."). Disabled by default to keep compaction silent.
|
||||
- `notifyUser`: when `true`, sends brief notices to the user when compaction starts and when it completes (for example, "Compacting context..." and "Compaction complete"). Disabled by default to keep compaction silent.
|
||||
- `memoryFlush`: silent agentic turn before auto-compaction to store durable memories. Skipped when workspace is read-only.
|
||||
|
||||
### `agents.defaults.contextPruning`
|
||||
@@ -1791,7 +1791,7 @@ scripts/sandbox-browser-setup.sh # optional browser image
|
||||
- `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.
|
||||
- `thinkingDefault`: optional per-agent default thinking level (`off | minimal | low | medium | high | xhigh | adaptive | max`). Overrides `agents.defaults.thinkingDefault` for this agent when no per-message or session override is set.
|
||||
- `reasoningDefault`: optional per-agent default reasoning visibility (`on | off | stream`). Applies when no per-message or session reasoning override is set.
|
||||
- `fastModeDefault`: optional per-agent default for fast mode (`true | false`). Applies when no per-message or session fast-mode override is set.
|
||||
- `embeddedHarness`: optional per-agent low-level harness policy override. Use `{ runtime: "codex", fallback: "none" }` to make one agent Codex-only while other agents keep the default PI fallback.
|
||||
@@ -2673,8 +2673,8 @@ Set `ZAI_API_KEY`. `z.ai/*` and `z-ai/*` are accepted aliases. Shortcut: `opencl
|
||||
env: { MOONSHOT_API_KEY: "sk-..." },
|
||||
agents: {
|
||||
defaults: {
|
||||
model: { primary: "moonshot/kimi-k2.5" },
|
||||
models: { "moonshot/kimi-k2.5": { alias: "Kimi K2.5" } },
|
||||
model: { primary: "moonshot/kimi-k2.6" },
|
||||
models: { "moonshot/kimi-k2.6": { alias: "Kimi K2.6" } },
|
||||
},
|
||||
},
|
||||
models: {
|
||||
@@ -2686,11 +2686,11 @@ Set `ZAI_API_KEY`. `z.ai/*` and `z-ai/*` are accepted aliases. Shortcut: `opencl
|
||||
api: "openai-completions",
|
||||
models: [
|
||||
{
|
||||
id: "kimi-k2.5",
|
||||
name: "Kimi K2.5",
|
||||
id: "kimi-k2.6",
|
||||
name: "Kimi K2.6",
|
||||
reasoning: false,
|
||||
input: ["text", "image"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
cost: { input: 0.95, output: 4, cacheRead: 0.16, cacheWrite: 0 },
|
||||
contextWindow: 262144,
|
||||
maxTokens: 262144,
|
||||
},
|
||||
@@ -2967,7 +2967,8 @@ See [Plugins](/tools/plugin).
|
||||
- `profiles.*.cdpUrl` accepts `http://`, `https://`, `ws://`, and `wss://`.
|
||||
Use HTTP(S) when you want OpenClaw to discover `/json/version`; use WS(S)
|
||||
when your provider gives you a direct DevTools WebSocket URL.
|
||||
- `existing-session` profiles are host-only and use Chrome MCP instead of CDP.
|
||||
- `existing-session` profiles use Chrome MCP instead of CDP and can attach on
|
||||
the selected host or through a connected browser node.
|
||||
- `existing-session` profiles can set `userDataDir` to target a specific
|
||||
Chromium-based browser profile such as Brave or Edge.
|
||||
- `existing-session` profiles keep the current Chrome MCP route limits:
|
||||
@@ -3192,8 +3193,8 @@ See [Multiple Gateways](/gateway/multiple-gateways).
|
||||
path: "/hooks",
|
||||
maxBodyBytes: 262144,
|
||||
defaultSessionKey: "hook:ingress",
|
||||
allowRequestSessionKey: false,
|
||||
allowedSessionKeyPrefixes: ["hook:"],
|
||||
allowRequestSessionKey: true,
|
||||
allowedSessionKeyPrefixes: ["hook:", "hook:gmail:"],
|
||||
allowedAgentIds: ["hooks", "main"],
|
||||
presets: ["gmail"],
|
||||
transformsDir: "~/.openclaw/hooks/transforms",
|
||||
@@ -3224,6 +3225,7 @@ Validation and safety notes:
|
||||
- `hooks.token` must be **distinct** from `gateway.auth.token`; reusing the Gateway token is rejected.
|
||||
- `hooks.path` cannot be `/`; use a dedicated subpath such as `/hooks`.
|
||||
- If `hooks.allowRequestSessionKey=true`, constrain `hooks.allowedSessionKeyPrefixes` (for example `["hook:"]`).
|
||||
- If a mapping or preset uses a templated `sessionKey`, set `hooks.allowedSessionKeyPrefixes` and `hooks.allowRequestSessionKey=true`. Static mapping keys do not require that opt-in.
|
||||
|
||||
**Endpoints:**
|
||||
|
||||
@@ -3231,6 +3233,7 @@ Validation and safety notes:
|
||||
- `POST /hooks/agent` → `{ message, name?, agentId?, sessionKey?, wakeMode?, deliver?, channel?, to?, model?, thinking?, timeoutSeconds? }`
|
||||
- `sessionKey` from request payload is accepted only when `hooks.allowRequestSessionKey=true` (default: `false`).
|
||||
- `POST /hooks/<name>` → resolved via `hooks.mappings`
|
||||
- Template-rendered mapping `sessionKey` values are treated as externally supplied and also require `hooks.allowRequestSessionKey=true`.
|
||||
|
||||
<Accordion title="Mapping details">
|
||||
|
||||
@@ -3242,8 +3245,8 @@ Validation and safety notes:
|
||||
- `agentId` routes to a specific agent; unknown IDs fall back to default.
|
||||
- `allowedAgentIds`: restricts explicit routing (`*` or omitted = allow all, `[]` = deny all).
|
||||
- `defaultSessionKey`: optional fixed session key for hook agent runs without explicit `sessionKey`.
|
||||
- `allowRequestSessionKey`: allow `/hooks/agent` callers to set `sessionKey` (default: `false`).
|
||||
- `allowedSessionKeyPrefixes`: optional prefix allowlist for explicit `sessionKey` values (request + mapping), e.g. `["hook:"]`.
|
||||
- `allowRequestSessionKey`: allow `/hooks/agent` callers and template-driven mapping session keys to set `sessionKey` (default: `false`).
|
||||
- `allowedSessionKeyPrefixes`: optional prefix allowlist for explicit `sessionKey` values (request + mapping), e.g. `["hook:"]`. It becomes required when any mapping or preset uses a templated `sessionKey`.
|
||||
- `deliver: true` sends final reply to a channel; `channel` defaults to `last`.
|
||||
- `model` overrides LLM for this hook run (must be allowed if model catalog is set).
|
||||
|
||||
@@ -3251,6 +3254,10 @@ Validation and safety notes:
|
||||
|
||||
### Gmail integration
|
||||
|
||||
- The built-in Gmail preset uses `sessionKey: "hook:gmail:{{messages[0].id}}"`.
|
||||
- If you keep that per-message routing, set `hooks.allowRequestSessionKey: true` and constrain `hooks.allowedSessionKeyPrefixes` to match the Gmail namespace, for example `["hook:", "hook:gmail:"]`.
|
||||
- If you need `hooks.allowRequestSessionKey: false`, override the preset with a static `sessionKey` instead of the templated default.
|
||||
|
||||
```json5
|
||||
{
|
||||
hooks: {
|
||||
|
||||
@@ -96,6 +96,17 @@ When validation fails:
|
||||
- Run `openclaw doctor` to see exact issues
|
||||
- Run `openclaw doctor --fix` (or `--yes`) to apply repairs
|
||||
|
||||
The Gateway also keeps a trusted last-known-good copy after a successful startup. If
|
||||
`openclaw.json` is later changed outside OpenClaw and no longer validates, startup
|
||||
and hot reload preserve the broken file as a timestamped `.clobbered.*` snapshot,
|
||||
restore the last-known-good copy, and log a loud warning with the recovery reason.
|
||||
The next main-agent turn also receives a system-event warning telling it that the
|
||||
config was restored and must not be blindly rewritten. Last-known-good promotion
|
||||
is updated after validated startup and after accepted hot reloads, including
|
||||
OpenClaw-owned config writes whose persisted file hash still matches the accepted
|
||||
write. Promotion is skipped when the candidate contains redacted secret
|
||||
placeholders such as `***` or shortened token values.
|
||||
|
||||
## Common tasks
|
||||
|
||||
<AccordionGroup>
|
||||
@@ -287,7 +298,7 @@ When validation fails:
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Enable sandboxing">
|
||||
Run agent sessions in isolated Docker containers:
|
||||
Run agent sessions in isolated sandbox runtimes:
|
||||
|
||||
```json5
|
||||
{
|
||||
@@ -494,6 +505,19 @@ When validation fails:
|
||||
|
||||
The Gateway watches `~/.openclaw/openclaw.json` and applies changes automatically — no manual restart needed for most settings.
|
||||
|
||||
Direct file edits are treated as untrusted until they validate. The watcher waits
|
||||
for editor temp-write/rename churn to settle, reads the final file, and rejects
|
||||
invalid external edits by restoring the last-known-good config. OpenClaw-owned
|
||||
config writes use the same schema gate before writing; destructive clobbers such
|
||||
as dropping `gateway.mode` or shrinking the file by more than half are rejected
|
||||
and saved as `.rejected.*` for inspection.
|
||||
|
||||
If you see `Config auto-restored from last-known-good` or
|
||||
`config reload restored last-known-good config` in logs, inspect the matching
|
||||
`.clobbered.*` file next to `openclaw.json`, fix the rejected payload, then run
|
||||
`openclaw config validate`. See [Gateway troubleshooting](/gateway/troubleshooting#gateway-restored-last-known-good-config)
|
||||
for the recovery checklist.
|
||||
|
||||
### Reload modes
|
||||
|
||||
| Mode | Behavior |
|
||||
|
||||
@@ -86,6 +86,7 @@ cat ~/.openclaw/openclaw.json
|
||||
- Gateway port collision diagnostics (default `18789`).
|
||||
- Security warnings for open DM policies.
|
||||
- Gateway auth checks for local token mode (offers token generation when no token source exists; does not overwrite token SecretRef configs).
|
||||
- Device pairing trouble detection (pending first-time pair requests, pending role/scope upgrades, stale local device-token cache drift, and paired-record auth drift).
|
||||
- systemd linger check on Linux.
|
||||
- Workspace bootstrap file size check (truncation/near-limit warnings for context files).
|
||||
- Shell completion status check and auto-install/upgrade.
|
||||
@@ -379,10 +380,14 @@ switch to legacy names if the current image is missing.
|
||||
|
||||
### 7b) Bundled plugin runtime deps
|
||||
|
||||
Doctor verifies that bundled plugin runtime dependencies (for example the
|
||||
Discord plugin runtime packages) are present in the OpenClaw install root.
|
||||
If any are missing, doctor reports the packages and installs them in
|
||||
`openclaw doctor --fix` / `openclaw doctor --repair` mode.
|
||||
Doctor verifies runtime dependencies only for bundled plugins that are active in
|
||||
the current config or enabled by their bundled manifest default, for example
|
||||
`plugins.entries.discord.enabled: true`, legacy
|
||||
`channels.discord.enabled: true`, or a default-enabled bundled provider. If any
|
||||
are missing, doctor reports the packages and installs them in
|
||||
`openclaw doctor --fix` / `openclaw doctor --repair` mode. External plugins still
|
||||
use `openclaw plugins install` / `openclaw plugins update`; doctor does not
|
||||
install dependencies for arbitrary plugin paths.
|
||||
|
||||
### 8) Gateway service migrations and cleanup hints
|
||||
|
||||
@@ -401,6 +406,34 @@ encrypted-state preparation. Both steps are non-fatal; errors are logged and
|
||||
startup continues. In read-only mode (`openclaw doctor` without `--fix`) this check
|
||||
is skipped entirely.
|
||||
|
||||
### 8c) Device pairing and auth drift
|
||||
|
||||
Doctor now inspects device-pairing state as part of the normal health pass.
|
||||
|
||||
What it reports:
|
||||
|
||||
- pending first-time pairing requests
|
||||
- pending role upgrades for already paired devices
|
||||
- pending scope upgrades for already paired devices
|
||||
- public-key mismatch repairs where the device id still matches but the device
|
||||
identity no longer matches the approved record
|
||||
- paired records missing an active token for an approved role
|
||||
- paired tokens whose scopes drift outside the approved pairing baseline
|
||||
- local cached device-token entries for the current machine that predate a
|
||||
gateway-side token rotation or carry stale scope metadata
|
||||
|
||||
Doctor does not auto-approve pair requests or auto-rotate device tokens. It
|
||||
prints the exact next steps instead:
|
||||
|
||||
- inspect pending requests with `openclaw devices list`
|
||||
- approve the exact request with `openclaw devices approve <requestId>`
|
||||
- rotate a fresh token with `openclaw devices rotate --device <deviceId> --role <role>`
|
||||
- remove and re-approve a stale record with `openclaw devices remove <deviceId>`
|
||||
|
||||
This closes the common "already paired but still getting pairing required"
|
||||
hole: doctor now distinguishes first-time pairing from pending role/scope
|
||||
upgrades and from stale token/device-identity drift.
|
||||
|
||||
### 9) Security warnings
|
||||
|
||||
Doctor emits warnings when a provider is open to DMs without an allowlist, or
|
||||
|
||||
@@ -47,7 +47,7 @@ openclaw status
|
||||
openclaw logs --follow
|
||||
```
|
||||
|
||||
Healthy baseline: `Runtime: running` and `RPC probe: ok`.
|
||||
Healthy baseline: `Runtime: running`, `Connectivity probe: ok`, and `Capability: ...` that matches what you expect. Use `openclaw gateway status --require-rpc` when you need read-scope RPC proof, not just reachability.
|
||||
|
||||
</Step>
|
||||
|
||||
|
||||
@@ -10,70 +10,85 @@ title: "Multiple Gateways"
|
||||
|
||||
Most setups should use one Gateway because a single Gateway can handle multiple messaging connections and agents. If you need stronger isolation or redundancy (e.g., a rescue bot), run separate Gateways with isolated profiles/ports.
|
||||
|
||||
## Isolation checklist (required)
|
||||
## Best Recommended Setup
|
||||
|
||||
For most users, the simplest rescue-bot setup is:
|
||||
|
||||
- keep the main bot on the default profile
|
||||
- run the rescue bot on `--profile rescue`
|
||||
- use a completely separate Telegram bot for the rescue account
|
||||
- keep the rescue bot on a different base port such as `19001`
|
||||
|
||||
This keeps the rescue bot isolated from the main bot so it can debug or apply
|
||||
config changes if the primary bot is down. Leave at least 20 ports between
|
||||
base ports so the derived browser/canvas/CDP ports never collide.
|
||||
|
||||
## Rescue-Bot Quickstart
|
||||
|
||||
Use this as the default path unless you have a strong reason to do something
|
||||
else:
|
||||
|
||||
```bash
|
||||
# Rescue bot (separate Telegram bot, separate profile, port 19001)
|
||||
openclaw --profile rescue onboard
|
||||
openclaw --profile rescue gateway install
|
||||
```
|
||||
|
||||
If your main bot is already running, that is usually all you need.
|
||||
|
||||
During `openclaw --profile rescue onboard`:
|
||||
|
||||
- use the separate Telegram bot token
|
||||
- keep the `rescue` profile
|
||||
- use a base port at least 20 higher than the main bot
|
||||
- accept the default rescue workspace unless you already manage one yourself
|
||||
|
||||
If onboarding already installed the rescue service for you, the final
|
||||
`gateway install` is not needed.
|
||||
|
||||
## Why This Works
|
||||
|
||||
The rescue bot stays independent because it has its own:
|
||||
|
||||
- profile/config
|
||||
- state directory
|
||||
- workspace
|
||||
- base port (plus derived ports)
|
||||
- Telegram bot token
|
||||
|
||||
For most setups, use a completely separate Telegram bot for the rescue profile:
|
||||
|
||||
- easy to keep operator-only
|
||||
- separate bot token and identity
|
||||
- independent from the main bot's channel/app install
|
||||
- simple DM-based recovery path when the main bot is broken
|
||||
|
||||
## What `--profile rescue onboard` Changes
|
||||
|
||||
`openclaw --profile rescue onboard` uses the normal onboarding flow, but it
|
||||
writes everything into a separate profile.
|
||||
|
||||
In practice, that means the rescue bot gets its own:
|
||||
|
||||
- config file
|
||||
- state directory
|
||||
- workspace (by default `~/.openclaw/workspace-rescue`)
|
||||
- managed service name
|
||||
|
||||
The prompts are otherwise the same as normal onboarding.
|
||||
|
||||
## Isolation Checklist
|
||||
|
||||
Keep these unique per Gateway instance:
|
||||
|
||||
- `OPENCLAW_CONFIG_PATH` — per-instance config file
|
||||
- `OPENCLAW_STATE_DIR` — per-instance sessions, creds, caches
|
||||
- `agents.defaults.workspace` — per-instance workspace root
|
||||
- `gateway.port` (or `--port`) — unique per instance
|
||||
- Derived ports (browser/canvas) must not overlap
|
||||
- derived browser/canvas/CDP ports
|
||||
|
||||
If these are shared, you will hit config races and port conflicts.
|
||||
|
||||
## Recommended: profiles (`--profile`)
|
||||
|
||||
Profiles auto-scope `OPENCLAW_STATE_DIR` + `OPENCLAW_CONFIG_PATH` and suffix service names.
|
||||
|
||||
```bash
|
||||
# main
|
||||
openclaw --profile main setup
|
||||
openclaw --profile main gateway --port 18789
|
||||
|
||||
# rescue
|
||||
openclaw --profile rescue setup
|
||||
openclaw --profile rescue gateway --port 19001
|
||||
```
|
||||
|
||||
Per-profile services:
|
||||
|
||||
```bash
|
||||
openclaw --profile main gateway install
|
||||
openclaw --profile rescue gateway install
|
||||
```
|
||||
|
||||
## Rescue-bot guide
|
||||
|
||||
Run a second Gateway on the same host with its own:
|
||||
|
||||
- profile/config
|
||||
- state dir
|
||||
- workspace
|
||||
- base port (plus derived ports)
|
||||
|
||||
This keeps the rescue bot isolated from the main bot so it can debug or apply config changes if the primary bot is down.
|
||||
|
||||
Port spacing: leave at least 20 ports between base ports so the derived browser/canvas/CDP ports never collide.
|
||||
|
||||
### How to install (rescue bot)
|
||||
|
||||
```bash
|
||||
# Main bot (existing or fresh, without --profile param)
|
||||
# Runs on port 18789 + Chrome CDC/Canvas/... Ports
|
||||
openclaw onboard
|
||||
openclaw gateway install
|
||||
|
||||
# Rescue bot (isolated profile + ports)
|
||||
openclaw --profile rescue onboard
|
||||
# Notes:
|
||||
# - workspace name will be postfixed with -rescue per default
|
||||
# - Port should be at least 18789 + 20 Ports,
|
||||
# better choose completely different base port, like 19789,
|
||||
# - rest of the onboarding is the same as normal
|
||||
|
||||
# To install the service (if not happened automatically during setup)
|
||||
openclaw --profile rescue gateway install
|
||||
```
|
||||
|
||||
## Port mapping (derived)
|
||||
|
||||
Base port = `gateway.port` (or `OPENCLAW_GATEWAY_PORT` / `--port`).
|
||||
@@ -95,7 +110,7 @@ If you override any of these in config or env, you must keep them unique per ins
|
||||
|
||||
```bash
|
||||
OPENCLAW_CONFIG_PATH=~/.openclaw/main.json \
|
||||
OPENCLAW_STATE_DIR=~/.openclaw-main \
|
||||
OPENCLAW_STATE_DIR=~/.openclaw \
|
||||
openclaw gateway --port 18789
|
||||
|
||||
OPENCLAW_CONFIG_PATH=~/.openclaw/rescue.json \
|
||||
@@ -106,10 +121,10 @@ openclaw gateway --port 19001
|
||||
## Quick checks
|
||||
|
||||
```bash
|
||||
openclaw --profile main gateway status --deep
|
||||
openclaw gateway status --deep
|
||||
openclaw --profile rescue gateway status --deep
|
||||
openclaw --profile rescue gateway probe
|
||||
openclaw --profile main status
|
||||
openclaw status
|
||||
openclaw --profile rescue status
|
||||
openclaw --profile rescue browser status
|
||||
```
|
||||
|
||||
@@ -9,7 +9,7 @@ status: active
|
||||
|
||||
OpenClaw has three related (but different) controls:
|
||||
|
||||
1. **Sandbox** (`agents.defaults.sandbox.*` / `agents.list[].sandbox.*`) decides **where tools run** (Docker vs host).
|
||||
1. **Sandbox** (`agents.defaults.sandbox.*` / `agents.list[].sandbox.*`) decides **where tools run** (sandbox backend vs host).
|
||||
2. **Tool policy** (`tools.*`, `tools.sandbox.tools.*`, `agents.list[].tools.*`) decides **which tools are available/allowed**.
|
||||
3. **Elevated** (`tools.elevated.*`, `agents.list[].tools.elevated.*`) is an **exec-only escape hatch** to run outside the sandbox when you’re sandboxed (`gateway` by default, or `node` when the exec target is configured to `node`).
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ Not sandboxed:
|
||||
|
||||
`agents.defaults.sandbox.backend` controls **which runtime** provides the sandbox:
|
||||
|
||||
- `"docker"` (default): local Docker-backed sandbox runtime.
|
||||
- `"docker"` (default when sandboxing is enabled): local Docker-backed sandbox runtime.
|
||||
- `"ssh"`: generic SSH-backed remote sandbox runtime.
|
||||
- `"openshell"`: OpenShell-backed sandbox runtime.
|
||||
|
||||
@@ -79,7 +79,10 @@ OpenShell-specific config lives under `plugins.entries.openshell.config`.
|
||||
|
||||
### Docker backend
|
||||
|
||||
The Docker backend is the default runtime, executing tools and sandbox browsers locally via the Docker daemon socket (`/var/run/docker.sock`). Sandbox container isolation is determined by Docker namespaces.
|
||||
Sandboxing is off by default. If you enable sandboxing and do not choose a
|
||||
backend, OpenClaw uses the Docker backend. It executes tools and sandbox browsers
|
||||
locally via the Docker daemon socket (`/var/run/docker.sock`). Sandbox container
|
||||
isolation is determined by Docker namespaces.
|
||||
|
||||
**Docker-out-of-Docker (DooD) Constraints**:
|
||||
If you deploy the OpenClaw Gateway itself as a Docker container, it orchestrates sibling sandbox containers using the host's Docker socket (DooD). This introduces a specific path mapping constraint:
|
||||
|
||||
@@ -1109,7 +1109,7 @@ Dedicated doc: [Sandboxing](/gateway/sandboxing)
|
||||
Two complementary approaches:
|
||||
|
||||
- **Run the full Gateway in Docker** (container boundary): [Docker](/install/docker)
|
||||
- **Tool sandbox** (`agents.defaults.sandbox`, host gateway + Docker-isolated tools): [Sandboxing](/gateway/sandboxing)
|
||||
- **Tool sandbox** (`agents.defaults.sandbox`, host gateway + sandbox-isolated tools; Docker is the default backend): [Sandboxing](/gateway/sandboxing)
|
||||
|
||||
Note: to prevent cross-agent access, keep `agents.defaults.sandbox.scope` at `"agent"` (default)
|
||||
or `"session"` for stricter per-session isolation. `scope: "shared"` uses a
|
||||
|
||||
@@ -25,7 +25,7 @@ openclaw channels status --probe
|
||||
|
||||
Expected healthy signals:
|
||||
|
||||
- `openclaw gateway status` shows `Runtime: running` and `RPC probe: ok`.
|
||||
- `openclaw gateway status` shows `Runtime: running`, `Connectivity probe: ok`, and a `Capability: ...` line.
|
||||
- `openclaw doctor` reports no blocking config/service issues.
|
||||
- `openclaw channels status --probe` shows live per-account transport status and,
|
||||
where supported, probe/audit results such as `works` or `audit ok`.
|
||||
@@ -193,12 +193,12 @@ Common signatures:
|
||||
|
||||
Use `error.details.code` from the failed `connect` response to pick the next action:
|
||||
|
||||
| Detail code | Meaning | Recommended action |
|
||||
| ---------------------------- | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `AUTH_TOKEN_MISSING` | Client did not send a required shared token. | Paste/set token in the client and retry. For dashboard paths: `openclaw config get gateway.auth.token` then paste into Control UI settings. |
|
||||
| `AUTH_TOKEN_MISMATCH` | Shared token did not match gateway auth token. | If `canRetryWithDeviceToken=true`, allow one trusted retry. Cached-token retries reuse stored approved scopes; explicit `deviceToken` / `scopes` callers keep requested scopes. If still failing, run the [token drift recovery checklist](/cli/devices#token-drift-recovery-checklist). |
|
||||
| `AUTH_DEVICE_TOKEN_MISMATCH` | Cached per-device token is stale or revoked. | Rotate/re-approve device token using [devices CLI](/cli/devices), then reconnect. |
|
||||
| `PAIRING_REQUIRED` | Device identity is known but not approved for this role. | Approve pending request: `openclaw devices list` then `openclaw devices approve <requestId>`. |
|
||||
| Detail code | Meaning | Recommended action |
|
||||
| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `AUTH_TOKEN_MISSING` | Client did not send a required shared token. | Paste/set token in the client and retry. For dashboard paths: `openclaw config get gateway.auth.token` then paste into Control UI settings. |
|
||||
| `AUTH_TOKEN_MISMATCH` | Shared token did not match gateway auth token. | If `canRetryWithDeviceToken=true`, allow one trusted retry. Cached-token retries reuse stored approved scopes; explicit `deviceToken` / `scopes` callers keep requested scopes. If still failing, run the [token drift recovery checklist](/cli/devices#token-drift-recovery-checklist). |
|
||||
| `AUTH_DEVICE_TOKEN_MISMATCH` | Cached per-device token is stale or revoked. | Rotate/re-approve device token using [devices CLI](/cli/devices), then reconnect. |
|
||||
| `PAIRING_REQUIRED` | Device identity needs approval. Check `error.details.reason` for `not-paired`, `scope-upgrade`, `role-upgrade`, or `metadata-upgrade`, and use `requestId` / `remediationHint` when present. | Approve pending request: `openclaw devices list` then `openclaw devices approve <requestId>`. Scope/role upgrades use the same flow after you review the requested access. |
|
||||
|
||||
Device auth v2 migration check:
|
||||
|
||||
@@ -262,6 +262,63 @@ Related:
|
||||
- [/gateway/configuration](/gateway/configuration)
|
||||
- [/gateway/doctor](/gateway/doctor)
|
||||
|
||||
## Gateway restored last-known-good config
|
||||
|
||||
Use this when the Gateway starts, but logs say it restored `openclaw.json`.
|
||||
|
||||
```bash
|
||||
openclaw logs --follow
|
||||
openclaw config file
|
||||
openclaw config validate
|
||||
openclaw doctor
|
||||
```
|
||||
|
||||
Look for:
|
||||
|
||||
- `Config auto-restored from last-known-good`
|
||||
- `gateway: invalid config was restored from last-known-good backup`
|
||||
- `config reload restored last-known-good config after invalid-config`
|
||||
- A timestamped `openclaw.json.clobbered.*` file beside the active config
|
||||
- A main-agent system event that starts with `Config recovery warning`
|
||||
|
||||
What happened:
|
||||
|
||||
- The rejected config did not validate during startup or hot reload.
|
||||
- OpenClaw preserved the rejected payload as `.clobbered.*`.
|
||||
- The active config was restored from the last validated last-known-good copy.
|
||||
- The next main-agent turn is warned not to blindly rewrite the rejected config.
|
||||
|
||||
Inspect and repair:
|
||||
|
||||
```bash
|
||||
CONFIG="$(openclaw config file)"
|
||||
ls -lt "$CONFIG".clobbered.* "$CONFIG".rejected.* 2>/dev/null | head
|
||||
diff -u "$CONFIG" "$(ls -t "$CONFIG".clobbered.* 2>/dev/null | head -n 1)"
|
||||
openclaw config validate
|
||||
openclaw doctor
|
||||
```
|
||||
|
||||
Common signatures:
|
||||
|
||||
- `.clobbered.*` exists → an external direct edit or startup read was restored.
|
||||
- `.rejected.*` exists → an OpenClaw-owned config write failed schema or clobber checks before commit.
|
||||
- `Config write rejected:` → the write tried to drop required shape, shrink the file sharply, or persist invalid config.
|
||||
- `Config last-known-good promotion skipped` → the candidate contained redacted secret placeholders such as `***`.
|
||||
|
||||
Fix options:
|
||||
|
||||
1. Keep the restored active config if it is correct.
|
||||
2. Copy only the intended keys from `.clobbered.*` or `.rejected.*`, then apply them with `openclaw config set` or `config.patch`.
|
||||
3. Run `openclaw config validate` before restarting.
|
||||
4. If you edit by hand, keep the full JSON5 config, not just the partial object you wanted to change.
|
||||
|
||||
Related:
|
||||
|
||||
- [/gateway/configuration#strict-validation](/gateway/configuration#strict-validation)
|
||||
- [/gateway/configuration#config-hot-reload](/gateway/configuration#config-hot-reload)
|
||||
- [/cli/config](/cli/config)
|
||||
- [/gateway/doctor](/gateway/doctor)
|
||||
|
||||
## Gateway probe warnings
|
||||
|
||||
Use this when `openclaw gateway probe` reaches something, but still prints a warning block.
|
||||
@@ -281,7 +338,8 @@ Common signatures:
|
||||
|
||||
- `SSH tunnel failed to start; falling back to direct probes.` → SSH setup failed, but the command still tried direct configured/loopback targets.
|
||||
- `multiple reachable gateways detected` → more than one target answered. Usually this means an intentional multi-gateway setup or stale/duplicate listeners.
|
||||
- `Probe diagnostics are limited by gateway scopes (missing operator.read)` → connect worked, but detail RPC is scope-limited; pair device identity or use credentials with `operator.read`.
|
||||
- `Read-probe diagnostics are limited by gateway scopes (missing operator.read)` → connect worked, but detail RPC is scope-limited; pair device identity or use credentials with `operator.read`.
|
||||
- `Capability: pairing-pending` or `gateway closed (1008): pairing required` → the gateway answered, but this client still needs pairing/approval before normal operator access.
|
||||
- unresolved `gateway.auth.*` / `gateway.remote.*` SecretRef warning text → auth material was unavailable in this command path for the failed target.
|
||||
|
||||
Related:
|
||||
@@ -413,6 +471,7 @@ Common signatures:
|
||||
- `browser.executablePath not found` → configured path is invalid.
|
||||
- `browser.cdpUrl must be http(s) or ws(s)` → the configured CDP URL uses an unsupported scheme such as `file:` or `ftp:`.
|
||||
- `browser.cdpUrl has invalid port` → the configured CDP URL has a bad or out-of-range port.
|
||||
- `Could not find DevToolsActivePort for chrome` → Chrome MCP existing-session could not attach to the selected browser data dir yet. Open the browser inspect page, enable remote debugging, keep the browser open, approve the first attach prompt, then retry. If signed-in state is not required, prefer the managed `openclaw` profile.
|
||||
- `No Chrome tabs found for profile="user"` → the Chrome MCP attach profile has no open local Chrome tabs.
|
||||
- `Remote CDP for profile "<name>" is not reachable` → the configured remote CDP endpoint is not reachable from the gateway host.
|
||||
- `Browser attachOnly is enabled ... not reachable` or `Browser attachOnly is enabled and CDP websocket ... is not reachable` → attach-only profile has no reachable target, or the HTTP endpoint answered but the CDP WebSocket still could not be opened.
|
||||
@@ -471,7 +530,7 @@ What to check:
|
||||
Common signatures:
|
||||
|
||||
- `refusing to bind gateway ... without auth` → non-loopback bind without a valid gateway auth path.
|
||||
- `RPC probe: failed` while runtime is running → gateway alive but inaccessible with current auth/url.
|
||||
- `Connectivity probe: failed` while runtime is running → gateway alive but inaccessible with current auth/url.
|
||||
|
||||
### 3) Pairing and device identity state changed
|
||||
|
||||
|
||||
@@ -160,7 +160,7 @@ Quick answers plus deeper troubleshooting for real-world setups (local dev, VPS,
|
||||
cd openclaw
|
||||
pnpm install
|
||||
pnpm build
|
||||
pnpm ui:build # auto-installs UI deps on first run
|
||||
pnpm ui:build
|
||||
openclaw onboard
|
||||
```
|
||||
|
||||
@@ -767,7 +767,7 @@ for usage/billing and raise limits as needed.
|
||||
<Accordion title="Telegram: what goes in allowFrom?">
|
||||
`channels.telegram.allowFrom` is **the human sender's Telegram user ID** (numeric). It is not the bot username.
|
||||
|
||||
Onboarding accepts `@username` input and resolves it to a numeric ID, but OpenClaw authorization uses numeric IDs only.
|
||||
Setup asks for numeric user IDs only. If you already have legacy `@username` entries in config, `openclaw doctor --fix` can try to resolve them.
|
||||
|
||||
Safer (no third-party bot):
|
||||
|
||||
@@ -1094,15 +1094,14 @@ for usage/billing and raise limits as needed.
|
||||
<Accordion title="Cron fired, but nothing was sent to the channel. Why?">
|
||||
Check the delivery mode first:
|
||||
|
||||
- `--no-deliver` / `delivery.mode: "none"` means no external message is expected.
|
||||
- `--no-deliver` / `delivery.mode: "none"` means no runner fallback send is expected.
|
||||
- Missing or invalid announce target (`channel` / `to`) means the runner skipped outbound delivery.
|
||||
- Channel auth failures (`unauthorized`, `Forbidden`) mean the runner tried to deliver but credentials blocked it.
|
||||
- A silent isolated result (`NO_REPLY` / `no_reply` only) is treated as intentionally non-deliverable, so the runner also suppresses queued fallback delivery.
|
||||
|
||||
For isolated cron jobs, the runner owns final delivery. The agent is expected
|
||||
to return a plain-text summary for the runner to send. `--no-deliver` keeps
|
||||
that result internal; it does not let the agent send directly with the
|
||||
message tool instead.
|
||||
For isolated cron jobs, the agent can still send directly with the `message`
|
||||
tool when a chat route is available. `--announce` only controls the runner
|
||||
fallback path for final text that the agent did not already send.
|
||||
|
||||
Debug:
|
||||
|
||||
@@ -1258,7 +1257,7 @@ for usage/billing and raise limits as needed.
|
||||
openclaw browser --browser-profile chrome-live tabs
|
||||
```
|
||||
|
||||
This path is host-local. If the Gateway runs elsewhere, either run a node host on the browser machine or use remote CDP instead.
|
||||
This path can use the local host browser or a connected browser node. If the Gateway runs elsewhere, either run a node host on the browser machine or use remote CDP instead.
|
||||
|
||||
Current limits on `existing-session` / `user`:
|
||||
|
||||
@@ -1293,7 +1292,7 @@ for usage/billing and raise limits as needed.
|
||||
<Accordion title="Can I keep DMs personal but make groups public/sandboxed with one agent?">
|
||||
Yes - if your private traffic is **DMs** and your public traffic is **groups**.
|
||||
|
||||
Use `agents.defaults.sandbox.mode: "non-main"` so group/channel sessions (non-main keys) run in Docker, while the main DM session stays on-host. Then restrict what tools are available in sandboxed sessions via `tools.sandbox.tools`.
|
||||
Use `agents.defaults.sandbox.mode: "non-main"` so group/channel sessions (non-main keys) run in the configured sandbox backend, while the main DM session stays on-host. Docker is the default backend if you do not choose one. Then restrict what tools are available in sandboxed sessions via `tools.sandbox.tools`.
|
||||
|
||||
Setup walkthrough + example config: [Groups: personal DMs + public groups](/channels/groups#pattern-personal-dms-public-groups-single-agent)
|
||||
|
||||
@@ -1629,10 +1628,20 @@ for usage/billing and raise limits as needed.
|
||||
`config.apply` replaces the **entire config**. If you send a partial object, everything
|
||||
else is removed.
|
||||
|
||||
Current OpenClaw protects many accidental clobbers:
|
||||
|
||||
- OpenClaw-owned config writes validate the full post-change config before writing.
|
||||
- Invalid or destructive OpenClaw-owned writes are rejected and saved as `openclaw.json.rejected.*`.
|
||||
- If a direct edit breaks startup or hot reload, the Gateway restores the last-known-good config and saves the rejected file as `openclaw.json.clobbered.*`.
|
||||
- The main agent receives a boot warning after recovery so it does not blindly write the bad config again.
|
||||
|
||||
Recover:
|
||||
|
||||
- Restore from backup (git or a copied `~/.openclaw/openclaw.json`).
|
||||
- If you have no backup, re-run `openclaw doctor` and reconfigure channels/models.
|
||||
- Check `openclaw logs --follow` for `Config auto-restored from last-known-good`, `Config write rejected:`, or `config reload restored last-known-good config`.
|
||||
- Inspect the newest `openclaw.json.clobbered.*` or `openclaw.json.rejected.*` beside the active config.
|
||||
- Keep the active restored config if it works, then copy only the intended keys back with `openclaw config set` or `config.patch`.
|
||||
- Run `openclaw config validate` and `openclaw doctor`.
|
||||
- If you have no last-known-good or rejected payload, restore from backup, or re-run `openclaw doctor` and reconfigure channels/models.
|
||||
- If this was unexpected, file a bug and include your last known config or any backup.
|
||||
- A local coding agent can often reconstruct a working config from logs or history.
|
||||
|
||||
@@ -1644,7 +1653,7 @@ for usage/billing and raise limits as needed.
|
||||
- Use `config.patch` for partial RPC edits; keep `config.apply` for full-config replacement only.
|
||||
- If you are using the owner-only `gateway` tool from an agent run, it will still reject writes to `tools.exec.ask` / `tools.exec.security` (including legacy `tools.bash.*` aliases that normalize to the same protected exec paths).
|
||||
|
||||
Docs: [Config](/cli/config), [Configure](/cli/configure), [Doctor](/gateway/doctor).
|
||||
Docs: [Config](/cli/config), [Configure](/cli/configure), [Gateway troubleshooting](/gateway/troubleshooting#gateway-restored-last-known-good-config), [Doctor](/gateway/doctor).
|
||||
|
||||
</Accordion>
|
||||
|
||||
@@ -2728,8 +2737,8 @@ Related: [/concepts/oauth](/concepts/oauth) (OAuth flows, token storage, multi-a
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title='Why does openclaw gateway status say "Runtime: running" but "RPC probe: failed"?'>
|
||||
Because "running" is the **supervisor's** view (launchd/systemd/schtasks). The RPC probe is the CLI actually connecting to the gateway WebSocket and calling `status`.
|
||||
<Accordion title='Why does openclaw gateway status say "Runtime: running" but "Connectivity probe: failed"?'>
|
||||
Because "running" is the **supervisor's** view (launchd/systemd/schtasks). The connectivity probe is the CLI actually connecting to the gateway WebSocket.
|
||||
|
||||
Use `openclaw gateway status` and trust these lines:
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ This doc is a “how we test” guide:
|
||||
|
||||
Most days:
|
||||
|
||||
- Full gate (expected before push): `pnpm build && pnpm check && pnpm test`
|
||||
- Full gate (expected before push): `pnpm build && pnpm check && pnpm check:test-types && pnpm test`
|
||||
- Faster local full-suite run on a roomy machine: `pnpm test:max`
|
||||
- Direct Vitest watch loop: `pnpm test:watch`
|
||||
- Direct file targeting now routes extension/channel paths too: `pnpm test extensions/discord/src/monitor/message-handler.preflight.test.ts`
|
||||
@@ -39,6 +39,11 @@ When debugging real providers/models (requires real creds):
|
||||
|
||||
- Live suite (models + gateway tool/image probes): `pnpm test:live`
|
||||
- Target one live file quietly: `pnpm test:live -- src/agents/models.profiles.live.test.ts`
|
||||
- Moonshot/Kimi cost smoke: with `MOONSHOT_API_KEY` set, run
|
||||
`openclaw models list --provider moonshot --json`, then run an isolated
|
||||
`openclaw agent --local --session-id live-kimi-cost --message 'Reply exactly: KIMI_LIVE_OK' --thinking off --json`
|
||||
against `moonshot/kimi-k2.6`. Verify the JSON reports Moonshot/K2.6 and the
|
||||
assistant transcript stores normalized `usage.cost`.
|
||||
|
||||
Tip: when you only need one failing case, prefer narrowing live tests via the allowlist env vars described below.
|
||||
|
||||
@@ -49,9 +54,11 @@ These commands sit beside the main test suites when you need QA-lab realism:
|
||||
- `pnpm openclaw qa suite`
|
||||
- Runs repo-backed QA scenarios directly on the host.
|
||||
- Runs multiple selected scenarios in parallel by default with isolated
|
||||
gateway workers, up to 64 workers or the selected scenario count. Use
|
||||
`--concurrency <count>` to tune the worker count, or `--concurrency 1` for
|
||||
the older serial lane.
|
||||
gateway workers. `qa-channel` defaults to concurrency 4 (bounded by the
|
||||
selected scenario count). Use `--concurrency <count>` to tune the worker
|
||||
count, or `--concurrency 1` for the older serial lane.
|
||||
- Exits non-zero when any scenario fails. Use `--allow-failures` when you
|
||||
want artifacts without a failing exit code.
|
||||
- Supports provider modes `live-frontier`, `mock-openai`, and `aimock`.
|
||||
`aimock` starts a local AIMock-backed provider server for experimental
|
||||
fixture and protocol-mock coverage without replacing the scenario-aware
|
||||
@@ -69,6 +76,12 @@ These commands sit beside the main test suites when you need QA-lab realism:
|
||||
`.artifacts/qa-e2e/...`.
|
||||
- `pnpm qa:lab:up`
|
||||
- Starts the Docker-backed QA site for operator-style QA work.
|
||||
- `pnpm test:docker:bundled-channel-deps`
|
||||
- Packs and installs the current OpenClaw build in Docker, starts the Gateway
|
||||
with OpenAI configured, then enables Telegram and Discord via config edits.
|
||||
- Verifies the first Gateway restart installs each bundled channel plugin's
|
||||
runtime dependencies on demand, and a second restart does not reinstall
|
||||
dependencies that were already activated.
|
||||
- `pnpm openclaw qa aimock`
|
||||
- Starts only the local AIMock provider server for direct protocol smoke
|
||||
testing.
|
||||
@@ -86,6 +99,8 @@ These commands sit beside the main test suites when you need QA-lab realism:
|
||||
- Runs the Telegram live QA lane against a real private group using the driver and SUT bot tokens from env.
|
||||
- Requires `OPENCLAW_QA_TELEGRAM_GROUP_ID`, `OPENCLAW_QA_TELEGRAM_DRIVER_BOT_TOKEN`, and `OPENCLAW_QA_TELEGRAM_SUT_BOT_TOKEN`. The group id must be the numeric Telegram chat id.
|
||||
- Supports `--credential-source convex` for shared pooled credentials. Use env mode by default, or set `OPENCLAW_QA_CREDENTIAL_SOURCE=convex` to opt into pooled leases.
|
||||
- Exits non-zero when any scenario fails. Use `--allow-failures` when you
|
||||
want artifacts without a failing exit code.
|
||||
- Requires two distinct bots in the same private group, with the SUT bot exposing a Telegram username.
|
||||
- For stable bot-to-bot observation, enable Bot-to-Bot Communication Mode in `@BotFather` for both bots and ensure the driver bot can observe group bot traffic.
|
||||
- Writes a Telegram QA report, summary, and observed-messages artifact under `.artifacts/qa-e2e/...`.
|
||||
@@ -118,7 +133,7 @@ Required env vars:
|
||||
- `OPENCLAW_QA_CONVEX_SECRET_CI` for `ci`
|
||||
- Credential role selection:
|
||||
- CLI: `--credential-role maintainer|ci`
|
||||
- Env default: `OPENCLAW_QA_CREDENTIAL_ROLE` (defaults to `maintainer`)
|
||||
- Env default: `OPENCLAW_QA_CREDENTIAL_ROLE` (defaults to `ci` in CI, `maintainer` otherwise)
|
||||
|
||||
Optional env vars:
|
||||
|
||||
@@ -273,6 +288,7 @@ Think of the suites as “increasing realism” (and increasing flakiness/cost):
|
||||
- `pnpm test --watch` still uses the native root `vitest.config.ts` project graph, because a multi-shard watch loop is not practical.
|
||||
- `pnpm test`, `pnpm test:watch`, and `pnpm test:perf:imports` route explicit file/directory targets through scoped lanes first, so `pnpm test extensions/discord/src/monitor/message-handler.preflight.test.ts` avoids paying the full root project startup tax.
|
||||
- `pnpm test:changed` expands changed git paths into the same scoped lanes when the diff only touches routable source/test files; config/setup edits still fall back to the broad root-project rerun.
|
||||
- `pnpm check:changed` is the normal smart local gate for narrow work. It classifies the diff into core, core tests, extensions, extension tests, apps, docs, and tooling, then runs the matching typecheck/lint/test lanes. Public Plugin SDK and plugin-contract changes include extension validation because extensions depend on those core contracts.
|
||||
- Import-light unit tests from agents, commands, plugins, auto-reply helpers, `plugin-sdk`, and similar pure utility areas route through the `unit-fast` lane, which skips `test/setup-openclaw-runtime.ts`; stateful/runtime-heavy files stay on the existing lanes.
|
||||
- Selected `plugin-sdk` and `commands` helper source files also map changed-mode runs to explicit sibling tests in those light lanes, so helper edits avoid rerunning the full heavy suite for that directory.
|
||||
- `auto-reply` now has three dedicated buckets: top-level core helpers, top-level `reply.*` integration tests, and the `src/auto-reply/reply/**` subtree. This keeps the heaviest reply harness work off the cheap status/chunk/token tests.
|
||||
@@ -294,6 +310,8 @@ Think of the suites as “increasing realism” (and increasing flakiness/cost):
|
||||
- Each `pnpm test` shard inherits the same `threads` + `isolate: false` defaults from the shared Vitest config.
|
||||
- The shared `scripts/run-vitest.mjs` launcher now also adds `--no-maglev` for Vitest child Node processes by default to reduce V8 compile churn during big local runs. Set `OPENCLAW_VITEST_ENABLE_MAGLEV=1` if you need to compare against stock V8 behavior.
|
||||
- Fast-local iteration note:
|
||||
- `pnpm changed:lanes` shows which architectural lanes a diff triggers.
|
||||
- The pre-commit hook runs `pnpm check:changed --staged` after staged formatting/linting, so core-only commits do not pay extension test cost unless they touch public extension-facing contracts.
|
||||
- `pnpm test:changed` routes through scoped lanes when the changed paths map cleanly to a smaller suite.
|
||||
- `pnpm test:max` and `pnpm test:changed:max` keep the same routing behavior, just with a higher worker cap.
|
||||
- Local worker auto-scaling is intentionally conservative now and also backs off when the host load average is already high, so multiple concurrent Vitest runs do less damage by default.
|
||||
|
||||
@@ -28,8 +28,8 @@ Good output in one line:
|
||||
|
||||
- `openclaw status` → shows configured channels and no obvious auth errors.
|
||||
- `openclaw status --all` → full report is present and shareable.
|
||||
- `openclaw gateway probe` → expected gateway target is reachable (`Reachable: yes`). `RPC: limited - missing scope: operator.read` is degraded diagnostics, not a connect failure.
|
||||
- `openclaw gateway status` → `Runtime: running` and `RPC probe: ok`.
|
||||
- `openclaw gateway probe` → expected gateway target is reachable (`Reachable: yes`). `Capability: ...` tells you what auth level the probe could prove, and `Read probe: limited - missing scope: operator.read` is degraded diagnostics, not a connect failure.
|
||||
- `openclaw gateway status` → `Runtime: running`, `Connectivity probe: ok`, and a plausible `Capability: ...` line. Use `--require-rpc` if you need read-scope RPC proof too.
|
||||
- `openclaw doctor` → no blocking config/service errors.
|
||||
- `openclaw channels status --probe` → reachable gateway returns live per-account
|
||||
transport state plus probe/audit results such as `works` or `audit ok`; if the
|
||||
@@ -117,7 +117,8 @@ flowchart TD
|
||||
Good output looks like:
|
||||
|
||||
- `Runtime: running`
|
||||
- `RPC probe: ok`
|
||||
- `Connectivity probe: ok`
|
||||
- `Capability: read-only`, `write-capable`, or `admin-capable`
|
||||
- Your channel shows transport connected and, where supported, `works` or `audit ok` in `channels status --probe`
|
||||
- Sender appears approved (or DM policy is open/allowlist)
|
||||
|
||||
@@ -147,7 +148,8 @@ flowchart TD
|
||||
Good output looks like:
|
||||
|
||||
- `Dashboard: http://...` is shown in `openclaw gateway status`
|
||||
- `RPC probe: ok`
|
||||
- `Connectivity probe: ok`
|
||||
- `Capability: read-only`, `write-capable`, or `admin-capable`
|
||||
- No auth loop in logs
|
||||
|
||||
Common log signatures:
|
||||
@@ -189,7 +191,8 @@ flowchart TD
|
||||
|
||||
- `Service: ... (loaded)`
|
||||
- `Runtime: running`
|
||||
- `RPC probe: ok`
|
||||
- `Connectivity probe: ok`
|
||||
- `Capability: read-only`, `write-capable`, or `admin-capable`
|
||||
|
||||
Common log signatures:
|
||||
|
||||
|
||||
@@ -47,13 +47,15 @@ The Ansible playbook installs and configures:
|
||||
|
||||
1. **Tailscale** -- mesh VPN for secure remote access
|
||||
2. **UFW firewall** -- SSH + Tailscale ports only
|
||||
3. **Docker CE + Compose V2** -- for agent sandboxes
|
||||
3. **Docker CE + Compose V2** -- for the default agent sandbox backend
|
||||
4. **Node.js 24 + pnpm** -- runtime dependencies (Node 22 LTS, currently `22.14+`, remains supported)
|
||||
5. **OpenClaw** -- host-based, not containerized
|
||||
6. **Systemd service** -- auto-start with security hardening
|
||||
|
||||
<Note>
|
||||
The gateway runs directly on the host (not in Docker), but agent sandboxes use Docker for isolation. See [Sandboxing](/gateway/sandboxing) for details.
|
||||
The gateway runs directly on the host (not in Docker). Agent sandboxing is
|
||||
optional; this playbook installs Docker because it is the default sandbox
|
||||
backend. See [Sandboxing](/gateway/sandboxing) for details and other backends.
|
||||
</Note>
|
||||
|
||||
## Post-Install Setup
|
||||
|
||||
@@ -14,7 +14,7 @@ Docker is **optional**. Use it only if you want a containerized gateway or to va
|
||||
|
||||
- **Yes**: you want an isolated, throwaway gateway environment or to run OpenClaw on a host without local installs.
|
||||
- **No**: you are running on your own machine and just want the fastest dev loop. Use the normal install flow instead.
|
||||
- **Sandboxing note**: agent sandboxing uses Docker too, but it does **not** require the full gateway to run in Docker. See [Sandboxing](/gateway/sandboxing).
|
||||
- **Sandboxing note**: the default sandbox backend uses Docker when sandboxing is enabled, but sandboxing is off by default and does **not** require the full gateway to run in Docker. SSH and OpenShell sandbox backends are also available. See [Sandboxing](/gateway/sandboxing).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
@@ -311,10 +311,11 @@ including binary baking, persistence, and updates.
|
||||
|
||||
## Agent Sandbox
|
||||
|
||||
When `agents.defaults.sandbox` is enabled, the gateway runs agent tool execution
|
||||
(shell, file read/write, etc.) inside isolated Docker containers while the
|
||||
gateway itself stays on the host. This gives you a hard wall around untrusted or
|
||||
multi-tenant agent sessions without containerizing the entire gateway.
|
||||
When `agents.defaults.sandbox` is enabled with the Docker backend, the gateway
|
||||
runs agent tool execution (shell, file read/write, etc.) inside isolated Docker
|
||||
containers while the gateway itself stays on the host. This gives you a hard wall
|
||||
around untrusted or multi-tenant agent sessions without containerizing the entire
|
||||
gateway.
|
||||
|
||||
Sandbox scope can be per-agent (default), per-session, or shared. Each scope
|
||||
gets its own workspace mounted at `/workspace`. You can also configure
|
||||
|
||||
@@ -213,18 +213,21 @@ For the generic Docker flow, see [Docker](/install/docker).
|
||||
|
||||
```bash
|
||||
OPENCLAW_IMAGE=openclaw:latest
|
||||
OPENCLAW_GATEWAY_TOKEN=change-me-now
|
||||
OPENCLAW_GATEWAY_TOKEN=
|
||||
OPENCLAW_GATEWAY_BIND=lan
|
||||
OPENCLAW_GATEWAY_PORT=18789
|
||||
|
||||
OPENCLAW_CONFIG_DIR=/home/$USER/.openclaw
|
||||
OPENCLAW_WORKSPACE_DIR=/home/$USER/.openclaw/workspace
|
||||
|
||||
GOG_KEYRING_PASSWORD=change-me-now
|
||||
GOG_KEYRING_PASSWORD=
|
||||
XDG_CONFIG_HOME=/home/node/.openclaw
|
||||
```
|
||||
|
||||
Generate strong secrets:
|
||||
Leave `OPENCLAW_GATEWAY_TOKEN` blank unless you explicitly want to
|
||||
manage it through `.env`; OpenClaw writes a random gateway token to
|
||||
config on first start. Generate a keyring password and paste it into
|
||||
`GOG_KEYRING_PASSWORD`:
|
||||
|
||||
```bash
|
||||
openssl rand -hex 32
|
||||
|
||||
@@ -134,18 +134,21 @@ For the generic Docker flow, see [Docker](/install/docker).
|
||||
|
||||
```bash
|
||||
OPENCLAW_IMAGE=openclaw:latest
|
||||
OPENCLAW_GATEWAY_TOKEN=change-me-now
|
||||
OPENCLAW_GATEWAY_TOKEN=
|
||||
OPENCLAW_GATEWAY_BIND=lan
|
||||
OPENCLAW_GATEWAY_PORT=18789
|
||||
|
||||
OPENCLAW_CONFIG_DIR=/root/.openclaw
|
||||
OPENCLAW_WORKSPACE_DIR=/root/.openclaw/workspace
|
||||
|
||||
GOG_KEYRING_PASSWORD=change-me-now
|
||||
GOG_KEYRING_PASSWORD=
|
||||
XDG_CONFIG_HOME=/home/node/.openclaw
|
||||
```
|
||||
|
||||
Generate strong secrets:
|
||||
Leave `OPENCLAW_GATEWAY_TOKEN` blank unless you explicitly want to
|
||||
manage it through `.env`; OpenClaw writes a random gateway token to
|
||||
config on first start. Generate a keyring password and paste it into
|
||||
`GOG_KEYRING_PASSWORD`:
|
||||
|
||||
```bash
|
||||
openssl rand -hex 32
|
||||
|
||||
@@ -115,7 +115,7 @@ For contributors or anyone who wants to run from a local checkout:
|
||||
```bash
|
||||
git clone https://github.com/openclaw/openclaw.git
|
||||
cd openclaw
|
||||
pnpm install && pnpm ui:build && pnpm build
|
||||
pnpm install && pnpm build && pnpm ui:build
|
||||
pnpm link --global
|
||||
openclaw onboard --install-daemon
|
||||
```
|
||||
|
||||
@@ -225,7 +225,7 @@ Events handled include:
|
||||
- `tool_execution_start` / `tool_execution_update` / `tool_execution_end`
|
||||
- `turn_start` / `turn_end`
|
||||
- `agent_start` / `agent_end`
|
||||
- `auto_compaction_start` / `auto_compaction_end`
|
||||
- `compaction_start` / `compaction_end`
|
||||
|
||||
### 4. Prompting
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user