mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 22:11:38 +08:00
Compare commits
484 Commits
fix/codeql
...
codex/code
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8364730c41 | ||
|
|
60c6915b6c | ||
|
|
a301df0668 | ||
|
|
4ad29d2d8e | ||
|
|
d647ba1c6f | ||
|
|
d5736710a9 | ||
|
|
84a3b50c11 | ||
|
|
3f002b10d2 | ||
|
|
579acc3a91 | ||
|
|
575854c096 | ||
|
|
ec59af3386 | ||
|
|
ea4d0a3ce7 | ||
|
|
e7f47f61ab | ||
|
|
51affb81b9 | ||
|
|
e2a465df4b | ||
|
|
8a77f299ee | ||
|
|
57fcd7b56d | ||
|
|
d29c470d7c | ||
|
|
dc1d6856bc | ||
|
|
476ac66d80 | ||
|
|
01595d60c1 | ||
|
|
6eae36282b | ||
|
|
91fbbccc10 | ||
|
|
8751464cb9 | ||
|
|
c1f31f3870 | ||
|
|
d9401c7deb | ||
|
|
fcc0f4996c | ||
|
|
ea1a6d250a | ||
|
|
aa5a0a36f8 | ||
|
|
3fd4d1d29d | ||
|
|
f858b5de22 | ||
|
|
bbc3384fda | ||
|
|
b388209eaf | ||
|
|
d0e83b0aea | ||
|
|
b7fd104a8b | ||
|
|
bce729f6ab | ||
|
|
7e06455e64 | ||
|
|
9c0975c1c2 | ||
|
|
a0035764b6 | ||
|
|
992dc8de88 | ||
|
|
2e50f167ce | ||
|
|
7df025f457 | ||
|
|
8bd9e227a0 | ||
|
|
456e1c0a6a | ||
|
|
4977c2d844 | ||
|
|
76c8f9ac3f | ||
|
|
8b62e0fa96 | ||
|
|
03bde3d65c | ||
|
|
757af70bf7 | ||
|
|
0d7d1aa09c | ||
|
|
8f16079623 | ||
|
|
0442417e1f | ||
|
|
42584964ac | ||
|
|
0ce0509856 | ||
|
|
3196abb064 | ||
|
|
71dd936312 | ||
|
|
b9fe26af7f | ||
|
|
36eec68fb9 | ||
|
|
efe6b37407 | ||
|
|
4f7286ce86 | ||
|
|
05f607c149 | ||
|
|
c2ffe77926 | ||
|
|
f0a2b09df6 | ||
|
|
b15faae92f | ||
|
|
df4136018e | ||
|
|
524d28bed0 | ||
|
|
f6d3363f31 | ||
|
|
85cfc91a70 | ||
|
|
6c4ecd8d25 | ||
|
|
c38d94677c | ||
|
|
9f55378745 | ||
|
|
05d8c27d85 | ||
|
|
ebbd80a6a2 | ||
|
|
5c95fc06fa | ||
|
|
ae0e57eefc | ||
|
|
4e9207c212 | ||
|
|
f99f6f164a | ||
|
|
5e33bfee10 | ||
|
|
62b1e0d8b8 | ||
|
|
15649228d4 | ||
|
|
77c0ecdf34 | ||
|
|
36e687edf0 | ||
|
|
24fc40b133 | ||
|
|
0e8cb3d94b | ||
|
|
0fe007f71b | ||
|
|
f221bc85a0 | ||
|
|
f9b47ad2a1 | ||
|
|
5fdde9353e | ||
|
|
bae211f72a | ||
|
|
d7ea6d9f8c | ||
|
|
a4f590a096 | ||
|
|
963c56e01c | ||
|
|
1e66728a55 | ||
|
|
cef2542cec | ||
|
|
472de0e1d5 | ||
|
|
84e9463eec | ||
|
|
6c07de05f4 | ||
|
|
a4fd45ca31 | ||
|
|
9b231e39ad | ||
|
|
49b1770b8e | ||
|
|
25446d3c0e | ||
|
|
6c86972fbe | ||
|
|
fb97e1cc88 | ||
|
|
70095f08f4 | ||
|
|
f0c7c430f5 | ||
|
|
73891eaca6 | ||
|
|
86251f4391 | ||
|
|
cba0a348dc | ||
|
|
3b75898bee | ||
|
|
3a24a25f4b | ||
|
|
56b10ddf17 | ||
|
|
0cf129f5d3 | ||
|
|
f86953f354 | ||
|
|
94b4b3c644 | ||
|
|
442f59508e | ||
|
|
7e8d95b413 | ||
|
|
2c152ffa7f | ||
|
|
27b35c5b24 | ||
|
|
023955b004 | ||
|
|
12882a88b1 | ||
|
|
394bc9c465 | ||
|
|
e6c1a6637a | ||
|
|
a6e79d42cf | ||
|
|
4de2e7487a | ||
|
|
1c0b4369ab | ||
|
|
755fa16a80 | ||
|
|
f85bd0f5a9 | ||
|
|
6c55106c80 | ||
|
|
bf8bdcb064 | ||
|
|
ad1e14af53 | ||
|
|
d0ec3d1f09 | ||
|
|
c07f29bcf7 | ||
|
|
cad2cef0fb | ||
|
|
debb8ac76c | ||
|
|
f6a1d70080 | ||
|
|
1076d6c124 | ||
|
|
4f02a57f65 | ||
|
|
5230b09ca9 | ||
|
|
6776129315 | ||
|
|
778b49b8fd | ||
|
|
6dac51569e | ||
|
|
c7a91f9632 | ||
|
|
6fb9e9e558 | ||
|
|
8be40059fe | ||
|
|
6f819280a3 | ||
|
|
32359e667b | ||
|
|
2fbe808a32 | ||
|
|
c3bac63c1b | ||
|
|
2ea47988dd | ||
|
|
578178faa4 | ||
|
|
f4fb9eb3ce | ||
|
|
a0f1293505 | ||
|
|
132b3e3940 | ||
|
|
e11787a564 | ||
|
|
f8f719ee23 | ||
|
|
200443e1b3 | ||
|
|
4ce031fd1a | ||
|
|
1b1b1b41a3 | ||
|
|
bcaf980015 | ||
|
|
ac0e3013ab | ||
|
|
942d46a4d5 | ||
|
|
c1fec482e8 | ||
|
|
54e2f4dc28 | ||
|
|
93222c5f12 | ||
|
|
22fa77de31 | ||
|
|
aaa2f32175 | ||
|
|
74bd209f48 | ||
|
|
25f832531c | ||
|
|
c6a12a6fd2 | ||
|
|
8e5c2efb8d | ||
|
|
1d47974f89 | ||
|
|
2ea00e1c35 | ||
|
|
0b4bc78496 | ||
|
|
e1a7c5b860 | ||
|
|
72f6016ce5 | ||
|
|
e073485c23 | ||
|
|
040f533f60 | ||
|
|
666ab0a00b | ||
|
|
29d9a30497 | ||
|
|
7b3dfbf214 | ||
|
|
42aaf0c98a | ||
|
|
ec69c07b27 | ||
|
|
050f0f50c9 | ||
|
|
4a4353e33f | ||
|
|
7719dd8804 | ||
|
|
12fbdd4ede | ||
|
|
524528944f | ||
|
|
5fbf406beb | ||
|
|
8fd9264ae7 | ||
|
|
e5d2273e05 | ||
|
|
caa7f7c4cc | ||
|
|
aa74888cf7 | ||
|
|
7301e57632 | ||
|
|
0e1af0d770 | ||
|
|
b48dcab1b5 | ||
|
|
ef832f83f6 | ||
|
|
b68f3de91b | ||
|
|
702e23835d | ||
|
|
dddf871ad9 | ||
|
|
f5fde074bd | ||
|
|
195a58224c | ||
|
|
1c9b4d871c | ||
|
|
1e3d240220 | ||
|
|
1b341f963b | ||
|
|
bbc47cb9e1 | ||
|
|
0a74037f6f | ||
|
|
5d519f1dc5 | ||
|
|
737fd808dd | ||
|
|
678ef019f3 | ||
|
|
8d288e2dfd | ||
|
|
2c488daaf4 | ||
|
|
b547286937 | ||
|
|
0d631fa701 | ||
|
|
b6daa922d6 | ||
|
|
39ab11425f | ||
|
|
fe022e409d | ||
|
|
404446f758 | ||
|
|
5f42438cf7 | ||
|
|
931e60723d | ||
|
|
f3d5c54884 | ||
|
|
5403df0bc2 | ||
|
|
cc2564615b | ||
|
|
213bfcf79b | ||
|
|
d4645373e7 | ||
|
|
19cb778451 | ||
|
|
bfa48c4025 | ||
|
|
3585d3e226 | ||
|
|
15adc741ff | ||
|
|
7f58e89731 | ||
|
|
d3bb5ce9e9 | ||
|
|
018f77cdc2 | ||
|
|
af34a5db6e | ||
|
|
1e6bdf3a55 | ||
|
|
d61c919106 | ||
|
|
bf7ac8d8c4 | ||
|
|
61db2e06d5 | ||
|
|
f9bb6e3515 | ||
|
|
9a051d2f9b | ||
|
|
7ddf28c0d4 | ||
|
|
6e3fd67084 | ||
|
|
90554ea048 | ||
|
|
ca620eaf35 | ||
|
|
48b39bffbe | ||
|
|
4b09c27398 | ||
|
|
4ed6a7c6b8 | ||
|
|
14ba8dc3f7 | ||
|
|
e8afaf512e | ||
|
|
996e0ae2f2 | ||
|
|
e26357fee8 | ||
|
|
4eec2843cd | ||
|
|
6387f83512 | ||
|
|
5c0388c253 | ||
|
|
8abf2977f4 | ||
|
|
bfd3c2a450 | ||
|
|
684001ae7b | ||
|
|
94543092be | ||
|
|
e5208bd331 | ||
|
|
c2cb648dc3 | ||
|
|
4cbd1b53cf | ||
|
|
f98ba66af6 | ||
|
|
88da533714 | ||
|
|
9e01d19db3 | ||
|
|
c9828635a8 | ||
|
|
ef186a06d9 | ||
|
|
38e03d3af3 | ||
|
|
298c2fbad4 | ||
|
|
22a74de693 | ||
|
|
86a563e899 | ||
|
|
9aad403b7f | ||
|
|
494eb01ac8 | ||
|
|
111432a7a6 | ||
|
|
067375cee3 | ||
|
|
61985cb1d2 | ||
|
|
9df0ae6767 | ||
|
|
29ed5266bf | ||
|
|
e131eaecb5 | ||
|
|
6efb44944c | ||
|
|
465d1b0d4b | ||
|
|
637525136e | ||
|
|
c48c3ecbc7 | ||
|
|
75b7ad2784 | ||
|
|
dffc295a74 | ||
|
|
2500b5d4ec | ||
|
|
ef0eb12615 | ||
|
|
37f8c3806a | ||
|
|
694598822f | ||
|
|
2e0acd9775 | ||
|
|
553e842fa6 | ||
|
|
ecf6cbf75d | ||
|
|
aec83af23d | ||
|
|
4ee6068ced | ||
|
|
8a399ec5b4 | ||
|
|
7d7b610a24 | ||
|
|
0ac1a07f7c | ||
|
|
0c3d1892cd | ||
|
|
250376f885 | ||
|
|
8ce44b057f | ||
|
|
9d21df251e | ||
|
|
a379ac0562 | ||
|
|
d0dac324c6 | ||
|
|
f42645037f | ||
|
|
e816235c2d | ||
|
|
6776345d0a | ||
|
|
c39b323ab3 | ||
|
|
bd32238a23 | ||
|
|
ad3e4dbcce | ||
|
|
c677861032 | ||
|
|
fc1c597dbf | ||
|
|
b0b627e5a9 | ||
|
|
be918636ab | ||
|
|
be14820b5d | ||
|
|
ec1b96cdfa | ||
|
|
d23c8a8eba | ||
|
|
7340c0322f | ||
|
|
0d2a201b27 | ||
|
|
ae07d57f9d | ||
|
|
42d73fd955 | ||
|
|
ffcc0d1fe1 | ||
|
|
e8810c04a4 | ||
|
|
ef270b7a28 | ||
|
|
9e94a9e418 | ||
|
|
97d42a9614 | ||
|
|
e5fd9c0582 | ||
|
|
9931603adb | ||
|
|
50d8ef2229 | ||
|
|
84920fad4e | ||
|
|
8b51d1fdc2 | ||
|
|
955a0e9c0f | ||
|
|
173f959613 | ||
|
|
1b6f2969aa | ||
|
|
2fd7c054ae | ||
|
|
3af8e17cc5 | ||
|
|
b2aac178d6 | ||
|
|
464e573602 | ||
|
|
e8f9c3e6de | ||
|
|
4ea0556f64 | ||
|
|
214b3d3336 | ||
|
|
1c300cec5d | ||
|
|
76930da7eb | ||
|
|
eabab1f64f | ||
|
|
54f44ec321 | ||
|
|
5d1ba08e3c | ||
|
|
07bc320a8a | ||
|
|
778902103d | ||
|
|
e8258fd4a6 | ||
|
|
f2d97df262 | ||
|
|
8093ae6029 | ||
|
|
3e67ee63b4 | ||
|
|
0260903f7f | ||
|
|
d0a7986638 | ||
|
|
60bdb96f2c | ||
|
|
e0fe02fb09 | ||
|
|
22d6e9564a | ||
|
|
45b8645079 | ||
|
|
40b0b1bfe0 | ||
|
|
3144e7a729 | ||
|
|
354084b1b3 | ||
|
|
5a69832833 | ||
|
|
8989ceee50 | ||
|
|
ce833acbdb | ||
|
|
6603a174bc | ||
|
|
619064b6d7 | ||
|
|
df0ee092f0 | ||
|
|
98d87b06e0 | ||
|
|
6cc7432cd1 | ||
|
|
4987482e4c | ||
|
|
fd0ca5987b | ||
|
|
206b5f78a2 | ||
|
|
6bc3458222 | ||
|
|
a853c5e8c2 | ||
|
|
e7dafaf2af | ||
|
|
f110c153c2 | ||
|
|
c709b17fef | ||
|
|
38da2ac6f8 | ||
|
|
4b98f09529 | ||
|
|
bd20f8e07e | ||
|
|
3c4851037b | ||
|
|
b277ae3f4c | ||
|
|
a102f4dede | ||
|
|
e47a7448e9 | ||
|
|
1ad50a36ac | ||
|
|
52bf20b07d | ||
|
|
1e87f6bf70 | ||
|
|
48794b9f88 | ||
|
|
c0058fda2f | ||
|
|
919a68c64d | ||
|
|
ef799fd57a | ||
|
|
5a3b75de33 | ||
|
|
c67629fe0c | ||
|
|
148a34679f | ||
|
|
359d871293 | ||
|
|
e311ffdcb9 | ||
|
|
f5e5256632 | ||
|
|
af5a1fbddb | ||
|
|
231e5c618f | ||
|
|
f48f31aac9 | ||
|
|
1d74ecd71f | ||
|
|
aa9db998f7 | ||
|
|
4429ee7d2e | ||
|
|
90419df663 | ||
|
|
bb3a0c9545 | ||
|
|
027ea5f08b | ||
|
|
7d77680d9f | ||
|
|
581fbea1d6 | ||
|
|
54e6e3d7da | ||
|
|
126dcb0d9e | ||
|
|
f98068dac2 | ||
|
|
7beebc9afc | ||
|
|
7969f1f07c | ||
|
|
388019f5b6 | ||
|
|
adc20fed0d | ||
|
|
46888f5afb | ||
|
|
1a2228d291 | ||
|
|
797d574dfd | ||
|
|
0eb8f34000 | ||
|
|
9289a502bb | ||
|
|
de1ac12f1c | ||
|
|
d6e568ec95 | ||
|
|
32d429e647 | ||
|
|
b85147ff76 | ||
|
|
b743506549 | ||
|
|
29a35f04a9 | ||
|
|
eb8e892df9 | ||
|
|
a3228977fb | ||
|
|
b40c679630 | ||
|
|
65c94df872 | ||
|
|
165d62b15f | ||
|
|
11a56db5c1 | ||
|
|
c5bc4b6892 | ||
|
|
cf772079c6 | ||
|
|
44ad65f02b | ||
|
|
8291537710 | ||
|
|
ac599c9e53 | ||
|
|
9d037d2f5a | ||
|
|
3b0ed18b86 | ||
|
|
4e168de6d9 | ||
|
|
98b96182f8 | ||
|
|
2a54427aba | ||
|
|
82ca6ecdde | ||
|
|
8b665e0d70 | ||
|
|
afb17eade9 | ||
|
|
3766bbb674 | ||
|
|
0f120c09ba | ||
|
|
f3145f6db8 | ||
|
|
ad7fa6c387 | ||
|
|
823f13c6e4 | ||
|
|
565f4314fe | ||
|
|
c9d9067931 | ||
|
|
2a4dd89253 | ||
|
|
4aa08e9d79 | ||
|
|
58a0b077c1 | ||
|
|
eecd758e39 | ||
|
|
29d3b65a83 | ||
|
|
52e2d4e16a | ||
|
|
9cb71f7672 | ||
|
|
20cbc1f216 | ||
|
|
099037cca6 | ||
|
|
9d68c6768a | ||
|
|
9f0bf1c71e | ||
|
|
d117ed183a | ||
|
|
005eeca06f | ||
|
|
d50ad19e4b | ||
|
|
62be4eb21e | ||
|
|
9061d1e4c3 | ||
|
|
e20147a1b6 | ||
|
|
62fb87641e | ||
|
|
30d9777b3f | ||
|
|
092321dc9e | ||
|
|
f31311d104 | ||
|
|
914287642d | ||
|
|
1e20babcf7 | ||
|
|
53dff569b8 | ||
|
|
77f904d35c | ||
|
|
dc0c54c7f1 | ||
|
|
83753535eb | ||
|
|
2d748e4ac1 | ||
|
|
13e917e292 | ||
|
|
f0721452a8 | ||
|
|
8d4928b505 | ||
|
|
25f0b5dda3 | ||
|
|
9720358bad | ||
|
|
554b32feea | ||
|
|
402b826ba2 | ||
|
|
7c5bf1c675 |
@@ -10,6 +10,9 @@ description: Run Blacksmith Testbox for CI-parity checks, secrets, hosted servic
|
||||
Use Testbox when you need remote CI parity, injected secrets, hosted services,
|
||||
or an OS/runtime image that your local machine cannot provide cheaply.
|
||||
|
||||
For OpenClaw, Crabbox is a supported alternative when Blacksmith is unavailable
|
||||
or owned cloud capacity is preferable.
|
||||
|
||||
Do not default to Testbox for every local test/build loop. If the repo has
|
||||
documented local commands for normal iteration, use those first so you keep
|
||||
warm caches, local build state, and fast feedback.
|
||||
|
||||
81
.agents/skills/crabbox/SKILL.md
Normal file
81
.agents/skills/crabbox/SKILL.md
Normal file
@@ -0,0 +1,81 @@
|
||||
---
|
||||
name: crabbox
|
||||
description: Use Crabbox for OpenClaw remote Linux validation, warmed reusable boxes, GitHub Actions hydration, sync timing, logs, results, caches, and lease cleanup.
|
||||
---
|
||||
|
||||
# Crabbox
|
||||
|
||||
Use Crabbox when OpenClaw needs remote Linux proof on owned capacity, a large
|
||||
runner class, reusable warm state, or a Blacksmith alternative.
|
||||
|
||||
## Before Running
|
||||
|
||||
- Run from the repo root. Crabbox sync mirrors the current checkout.
|
||||
- Prefer local targeted tests for tight edit loops.
|
||||
- Prefer Blacksmith Testbox when the task explicitly asks for Blacksmith or a
|
||||
Blacksmith-specific CI comparison.
|
||||
- Use Crabbox for broad OpenClaw gates when owned AWS/Hetzner capacity is the
|
||||
right remote lane.
|
||||
- Check `.crabbox.yaml` for repo defaults before adding flags.
|
||||
- Install with `brew install openclaw/tap/crabbox`; auth is required before use:
|
||||
`printf '%s' "$CRABBOX_COORDINATOR_TOKEN" | crabbox login --url https://crabbox-coordinator.steipete.workers.dev --provider aws --token-stdin`.
|
||||
- On macOS the user config is `~/Library/Application Support/crabbox/config.yaml`;
|
||||
it must include `broker.url`, `broker.token`, and usually `provider: aws`.
|
||||
|
||||
## OpenClaw Flow
|
||||
|
||||
Warm a reusable box:
|
||||
|
||||
```sh
|
||||
pnpm crabbox:warmup -- --idle-timeout 90m
|
||||
```
|
||||
|
||||
Hydrate it through the repository workflow:
|
||||
|
||||
```sh
|
||||
pnpm crabbox:hydrate -- --id <cbx_id-or-slug>
|
||||
```
|
||||
|
||||
Run broad proof:
|
||||
|
||||
```sh
|
||||
pnpm crabbox:run -- --id <cbx_id-or-slug> --shell "OPENCLAW_TESTBOX=1 pnpm check:changed"
|
||||
pnpm crabbox:run -- --id <cbx_id-or-slug> --shell "corepack enable && pnpm install --frozen-lockfile && pnpm test"
|
||||
```
|
||||
|
||||
Stop boxes you created before handoff:
|
||||
|
||||
```sh
|
||||
pnpm crabbox:stop -- <cbx_id-or-slug>
|
||||
```
|
||||
|
||||
## Useful Commands
|
||||
|
||||
```sh
|
||||
crabbox status --id <id-or-slug> --wait
|
||||
crabbox inspect --id <id-or-slug> --json
|
||||
crabbox sync-plan
|
||||
crabbox history --lease <id-or-slug>
|
||||
crabbox logs <run_id>
|
||||
crabbox results <run_id>
|
||||
crabbox cache stats --id <id-or-slug>
|
||||
crabbox ssh --id <id-or-slug>
|
||||
```
|
||||
|
||||
Use `--debug` on `run` when measuring sync timing.
|
||||
|
||||
## Hydration Boundary
|
||||
|
||||
`.github/workflows/crabbox-hydrate.yml` is repo-specific on purpose. It owns
|
||||
OpenClaw checkout, setup-node, pnpm setup, provider env hydration, ready marker,
|
||||
and keepalive. Crabbox owns runner registration, workflow dispatch, SSH sync,
|
||||
command execution, logs/results, local lease claims, and idle cleanup.
|
||||
|
||||
Do not add OpenClaw-specific setup to Crabbox. Put repo setup in the hydration
|
||||
workflow and generic lease/sync behavior in Crabbox.
|
||||
|
||||
## Cleanup
|
||||
|
||||
Crabbox has coordinator-owned idle expiry and local lease claims, so OpenClaw
|
||||
does not need a custom ledger. Default idle timeout is 30 minutes unless config
|
||||
or flags set a different value. Still stop boxes you created when done.
|
||||
@@ -41,9 +41,11 @@ Use this skill for release and publish-time workflow. Keep ordinary development
|
||||
recommended replacement can shift as plugin ownership, externalization, and
|
||||
config footprint move, so do not blindly copy stale replacement annotations
|
||||
into release notes.
|
||||
- Do not delete or rewrite beta tags after they leave the machine. If a
|
||||
published or pushed beta needs a fix, commit the fix on the release branch and
|
||||
increment to the next `-beta.N`.
|
||||
- Do not delete or rewrite beta tags after their matching npm package has been
|
||||
published. If a pushed beta tag fails preflight before npm publish, delete and
|
||||
recreate the tag and prerelease at the fixed commit so npm prerelease versions
|
||||
stay contiguous. If a published 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 fast local preflight first, publish the
|
||||
beta to npm `beta`, then run the expensive published-package roster focused
|
||||
on install/update/Docker/Parallels/NPM Telegram. If anything fails, fix it on
|
||||
@@ -367,8 +369,10 @@ 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.
|
||||
Exception: never delete or recreate a beta tag whose matching npm package has
|
||||
already been published; increment to the next beta number instead. If only the
|
||||
pushed tag/prerelease exists and npm publish has not happened, recreate that
|
||||
same beta tag at the fixed commit.
|
||||
- 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
|
||||
@@ -561,6 +565,9 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
|
||||
commit, and rerun all relevant preflights from scratch before continuing.
|
||||
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.
|
||||
For preflight-only failures where npm did not publish the beta version,
|
||||
delete/recreate the same beta tag and prerelease at the fixed commit instead
|
||||
of skipping a prerelease number.
|
||||
20. 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
|
||||
@@ -573,9 +580,9 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
|
||||
for critical fixes that landed after the release branch cut; backport only
|
||||
important low-risk fixes before starting expensive lanes, or increment to
|
||||
the next beta if the fix must change the already-published package. If any
|
||||
lane fails after the beta tag/package is pushed or published, fix,
|
||||
commit/push/pull, increment to the next beta tag, and rerun the affected
|
||||
beta evidence. Once the beta is live, start remote/manual rosters where they
|
||||
lane fails after the beta package is published, fix, commit/push/pull,
|
||||
increment to the next beta tag, and rerun the affected beta evidence. Once
|
||||
the beta is live, start remote/manual rosters where they
|
||||
can overlap safely, but keep local Docker and Parallels load controlled.
|
||||
Ensure the full expensive roster has passed at least once before
|
||||
stable/latest promotion. The roster includes the manual Actions >
|
||||
|
||||
63
.agents/skills/openclaw-small-bugfix-sweep/SKILL.md
Normal file
63
.agents/skills/openclaw-small-bugfix-sweep/SKILL.md
Normal file
@@ -0,0 +1,63 @@
|
||||
---
|
||||
name: openclaw-small-bugfix-sweep
|
||||
description: Fix only small, high-certainty OpenClaw bugs from a pasted issue/PR list after deep code review.
|
||||
---
|
||||
|
||||
# OpenClaw Small Bugfix Sweep
|
||||
|
||||
Batch workflow for pasted OpenClaw issue/PR refs.
|
||||
Execute, do not summarize.
|
||||
Triage does not commit, push, create PRs, comment, close, label, land, or merge.
|
||||
|
||||
## Companion Skills
|
||||
|
||||
Use `$gitcrawl` first, `$openclaw-pr-maintainer` for live GitHub hygiene, `$github-deep-review` posture for source tracing, and `$openclaw-testing` for proof.
|
||||
|
||||
## Loop
|
||||
|
||||
For each ref:
|
||||
|
||||
1. Read live target with `gh`.
|
||||
2. Check `gitcrawl` for related, duplicate, closed, or already-fixed threads.
|
||||
3. Read body, comments, linked refs, changed files, current code, adjacent tests, and dependency contracts when relevant.
|
||||
4. Trace the real runtime path.
|
||||
5. For issues: fix locally only if this is a bug, current code proves root cause, the implicated path is clear, and a narrow patch is cleaner than refactor.
|
||||
6. For PRs: decide `ready-to-merge`, `needs-fixup`, or `skip`; do not alter PR branches unless explicitly asked.
|
||||
7. Add focused regression proof when practical for local issue fixes or PR readiness checks.
|
||||
8. Run the smallest meaningful gate.
|
||||
9. Continue until every pasted ref is fixed or classified.
|
||||
|
||||
No subagents unless explicitly requested.
|
||||
|
||||
## Skip If
|
||||
|
||||
- not a bug
|
||||
- config/docs/workflow/release/support/dependency/product work
|
||||
- repro or root cause is uncertain
|
||||
- larger refactor or owner-boundary change is cleaner
|
||||
- already fixed on current `main`
|
||||
- dependency behavior is guessed
|
||||
- no focused proof is feasible
|
||||
|
||||
Skip with terse reason. Do not pad with low-confidence fixes.
|
||||
|
||||
## Fix Rules
|
||||
|
||||
- owner module first; generic seam only when required
|
||||
- existing patterns/helpers/types
|
||||
- no drive-by refactors
|
||||
- tests near failing surface
|
||||
- docs only for changed public behavior
|
||||
- no commit/push/create PR/comment/close/label/land/merge unless explicitly asked
|
||||
|
||||
## PR Rules
|
||||
|
||||
- `ready-to-merge`: code is good, current head checked, required proof is green or clearly pending only external CI; list for maintainer merge or `@clawsweeper automerge`
|
||||
- `needs-fixup`: small bug is clear, but PR branch needs changes; list exact files/tests and wait for explicit fix/push/automerge instruction
|
||||
- `skip`: broad, stale, speculative, config/product/security/release, owner-boundary, or refactor-sized
|
||||
- if source PR is untrusted/uneditable, do not create a replacement PR during sweep
|
||||
|
||||
## Output Shape
|
||||
|
||||
Ledger: `fixed-local`, `ready-to-merge`, `needs-fixup`, `skipped`, `needs-human`.
|
||||
Final: issue files left on disk, PRs ready for merge/automerge, tests/gates, skip reasons.
|
||||
@@ -7,6 +7,8 @@ description: Investigate OpenClaw pnpm test memory growth, Vitest OOMs, RSS spik
|
||||
|
||||
Use this skill for test-memory investigations. Do not guess from RSS alone when heap snapshots are available. Treat snapshot-name deltas as triage evidence, not proof, until retainers or dominators support the call.
|
||||
|
||||
For **runtime fixes** (e.g., closure leaks in long-running services like the gateway), see [Validating runtime fixes](#validating-runtime-fixes-not-test-memory) below — that uses a dedicated harness, not the test-parallel snapshot machinery.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. Reproduce the failing shape first.
|
||||
@@ -63,6 +65,38 @@ Use this skill for test-memory investigations. Do not guess from RSS alone when
|
||||
|
||||
Read the top positive deltas first. Large positive growth in module-transform artifacts suggests lane isolation; large positive growth in runtime objects suggests a real leak. If the names alone do not settle it, open the same snapshot pair in DevTools and inspect retainers/dominators for the top rows before declaring root cause.
|
||||
|
||||
## Validating runtime fixes (not test-memory)
|
||||
|
||||
The workflow above is for diagnosing Vitest worker memory growth. For
|
||||
validating that a runtime/closure fix actually releases captured state, use the
|
||||
dedicated harness:
|
||||
|
||||
- `pnpm leak:embedded-run` — runs `scripts/embedded-run-abort-leak.ts`. Loops N
|
||||
aborted runs in a function-shaped scope mimicking `runEmbeddedAttempt`,
|
||||
writes heap snapshots, and reports a PASS/FAIL verdict on retention growth
|
||||
using `FinalizationRegistry` for tracked-instance counting plus RSS delta.
|
||||
|
||||
Modes:
|
||||
|
||||
- `closure-extracted` (default) — production fix shape (helper at module scope).
|
||||
- `closure-inline` — pre-fix shape (closure inside the runner scope). Use as a
|
||||
sensitivity check: if it passes you've broken the harness, not fixed a bug.
|
||||
- `synthetic-leak` — deliberately retains via a module-level bucket. Use to
|
||||
confirm the harness can detect leaks before trusting a PASS on a real fix.
|
||||
|
||||
Snapshots land in `.tmp/embedded-run-abort-leak/`. Diff with the same script
|
||||
as above:
|
||||
|
||||
```
|
||||
node .agents/skills/openclaw-test-heap-leaks/scripts/heapsnapshot-delta.mjs \
|
||||
.tmp/embedded-run-abort-leak/baseline-*.heapsnapshot \
|
||||
.tmp/embedded-run-abort-leak/batch-N-*.heapsnapshot --top 30
|
||||
```
|
||||
|
||||
When fixing a different runtime leak, add a new harness alongside this one
|
||||
rather than retrofitting it. The fixture function should mimic the lexical
|
||||
scope of the function where the leak lives, not be a generic abort-loop.
|
||||
|
||||
## Output Expectations
|
||||
|
||||
When using this skill, report:
|
||||
|
||||
41
.crabbox.yaml
Normal file
41
.crabbox.yaml
Normal file
@@ -0,0 +1,41 @@
|
||||
profile: openclaw-check
|
||||
provider: aws
|
||||
class: beast
|
||||
capacity:
|
||||
market: spot
|
||||
strategy: most-available
|
||||
fallback: on-demand-after-120s
|
||||
regions:
|
||||
- eu-west-1
|
||||
actions:
|
||||
workflow: .github/workflows/crabbox-hydrate.yml
|
||||
job: hydrate
|
||||
ref: main
|
||||
runnerLabels:
|
||||
- crabbox
|
||||
- openclaw
|
||||
runnerVersion: latest
|
||||
ephemeral: true
|
||||
aws:
|
||||
region: eu-west-1
|
||||
rootGB: 400
|
||||
sync:
|
||||
delete: true
|
||||
checksum: false
|
||||
gitSeed: true
|
||||
fingerprint: true
|
||||
baseRef: main
|
||||
exclude:
|
||||
- .artifacts
|
||||
- .codex
|
||||
- .DS_Store
|
||||
- playwright-report
|
||||
- test-results
|
||||
env:
|
||||
allow:
|
||||
- CI
|
||||
- NODE_OPTIONS
|
||||
- OPENCLAW_*
|
||||
ssh:
|
||||
user: crabbox
|
||||
port: "2222"
|
||||
72
.github/workflows/clawsweeper-dispatch.yml
vendored
72
.github/workflows/clawsweeper-dispatch.yml
vendored
@@ -3,6 +3,8 @@ name: ClawSweeper Dispatch
|
||||
on:
|
||||
issues:
|
||||
types: [opened, reopened, edited, labeled, unlabeled]
|
||||
issue_comment:
|
||||
types: [created, edited]
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request_target: # zizmor: ignore[dangerous-triggers] maintainer-owned external dispatch; no checkout or untrusted PR code execution
|
||||
@@ -18,7 +20,7 @@ concurrency:
|
||||
jobs:
|
||||
dispatch:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ !(endsWith(github.actor, '[bot]') && (github.event.action == 'labeled' || github.event.action == 'unlabeled')) }}
|
||||
if: ${{ github.event_name == 'issue_comment' || !(endsWith(github.actor, '[bot]') && (github.event.action == 'labeled' || github.event.action == 'unlabeled')) }}
|
||||
env:
|
||||
HAS_CLAWSWEEPER_APP_PRIVATE_KEY: ${{ secrets.CLAWSWEEPER_APP_PRIVATE_KEY != '' }}
|
||||
CLAWSWEEPER_APP_CLIENT_ID: Iv23liOECG0slfuhz093
|
||||
@@ -39,8 +41,20 @@ jobs:
|
||||
repositories: clawsweeper
|
||||
permission-contents: write
|
||||
|
||||
- name: Create target comment token
|
||||
id: target_token
|
||||
if: ${{ github.event_name == 'issue_comment' && env.HAS_CLAWSWEEPER_APP_PRIVATE_KEY == 'true' }}
|
||||
uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
|
||||
with:
|
||||
client-id: ${{ env.CLAWSWEEPER_APP_CLIENT_ID }}
|
||||
private-key: ${{ secrets.CLAWSWEEPER_APP_PRIVATE_KEY }}
|
||||
owner: ${{ github.repository_owner }}
|
||||
repositories: ${{ github.event.repository.name }}
|
||||
permission-issues: write
|
||||
permission-pull-requests: read
|
||||
|
||||
- name: Dispatch exact ClawSweeper review
|
||||
if: ${{ github.event_name != 'push' }}
|
||||
if: ${{ github.event_name != 'push' && github.event_name != 'issue_comment' }}
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.token.outputs.token }}
|
||||
TARGET_REPO: ${{ github.repository }}
|
||||
@@ -69,6 +83,60 @@ jobs:
|
||||
echo "::warning::Skipping ClawSweeper dispatch because the configured credential could not dispatch to openclaw/clawsweeper."
|
||||
fi
|
||||
|
||||
- name: Acknowledge and dispatch ClawSweeper comment
|
||||
if: ${{ github.event_name == 'issue_comment' }}
|
||||
env:
|
||||
DISPATCH_TOKEN: ${{ steps.token.outputs.token }}
|
||||
TARGET_TOKEN: ${{ steps.target_token.outputs.token }}
|
||||
TARGET_REPO: ${{ github.repository }}
|
||||
ITEM_NUMBER: ${{ github.event.issue.number }}
|
||||
COMMENT_ID: ${{ github.event.comment.id }}
|
||||
COMMENT_BODY: ${{ github.event.comment.body }}
|
||||
SOURCE_ACTION: ${{ github.event.action }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ -z "$DISPATCH_TOKEN" ]; then
|
||||
echo "::notice::Skipping ClawSweeper comment dispatch because no ClawSweeper app token is configured."
|
||||
exit 0
|
||||
fi
|
||||
body_file="$RUNNER_TEMP/clawsweeper-comment-body.txt"
|
||||
printf '%s\n' "$COMMENT_BODY" > "$body_file"
|
||||
if ! grep -Eiq '(^|[[:space:]])@(clawsweeper|openclaw-clawsweeper)\b(\[bot\])?|(^|[[:space:]])/(clawsweeper|review|automerge|autoclose)\b' "$body_file"; then
|
||||
echo "No ClawSweeper command found in comment."
|
||||
exit 0
|
||||
fi
|
||||
if [ -n "$TARGET_TOKEN" ]; then
|
||||
err="$(mktemp)"
|
||||
if GH_TOKEN="$TARGET_TOKEN" gh api -X POST \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
"repos/$TARGET_REPO/issues/comments/$COMMENT_ID/reactions" \
|
||||
-f content="eyes" 2>"$err" >/dev/null; then
|
||||
echo "Acknowledged ClawSweeper command comment."
|
||||
elif grep -qi "HTTP 422\\|already exists" "$err"; then
|
||||
echo "ClawSweeper command comment already acknowledged."
|
||||
else
|
||||
cat "$err" >&2
|
||||
echo "::warning::Could not acknowledge ClawSweeper command comment."
|
||||
fi
|
||||
rm -f "$err"
|
||||
else
|
||||
echo "::notice::Skipping ClawSweeper comment acknowledgement because no target token is configured."
|
||||
fi
|
||||
payload="$(jq -nc \
|
||||
--arg target_repo "$TARGET_REPO" \
|
||||
--argjson item_number "$ITEM_NUMBER" \
|
||||
--argjson comment_id "$COMMENT_ID" \
|
||||
--arg source_event "issue_comment" \
|
||||
--arg source_action "$SOURCE_ACTION" \
|
||||
'{event_type:"clawsweeper_comment",client_payload:{target_repo:$target_repo,item_number:$item_number,comment_id:$comment_id,source_event:$source_event,source_action:$source_action}}')"
|
||||
if GH_TOKEN="$DISPATCH_TOKEN" gh api repos/openclaw/clawsweeper/dispatches \
|
||||
--method POST \
|
||||
--input - <<< "$payload"; then
|
||||
echo "Dispatched ClawSweeper comment router."
|
||||
else
|
||||
echo "::warning::Skipping ClawSweeper comment dispatch because the configured credential could not dispatch to openclaw/clawsweeper."
|
||||
fi
|
||||
|
||||
- name: Dispatch ClawSweeper commit review
|
||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && github.event.deleted != true }}
|
||||
env:
|
||||
|
||||
145
.github/workflows/crabbox-hydrate.yml
vendored
Normal file
145
.github/workflows/crabbox-hydrate.yml
vendored
Normal file
@@ -0,0 +1,145 @@
|
||||
name: Crabbox Hydrate
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
crabbox_id:
|
||||
description: "Crabbox lease ID"
|
||||
required: true
|
||||
type: string
|
||||
ref:
|
||||
description: "Git ref to hydrate"
|
||||
required: false
|
||||
type: string
|
||||
crabbox_runner_label:
|
||||
description: "Dynamic Crabbox runner label"
|
||||
required: true
|
||||
type: string
|
||||
crabbox_job:
|
||||
description: "Hydration job identifier expected by Crabbox"
|
||||
required: false
|
||||
default: "hydrate"
|
||||
type: string
|
||||
crabbox_keep_alive_minutes:
|
||||
description: "Minutes to keep the hydrated job alive"
|
||||
required: false
|
||||
default: "90"
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
|
||||
jobs:
|
||||
hydrate:
|
||||
name: hydrate
|
||||
runs-on: [self-hosted, "${{ inputs.crabbox_runner_label }}"]
|
||||
timeout-minutes: 120
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ inputs.ref || github.ref }}
|
||||
|
||||
- name: Setup Node environment
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
install-bun: "false"
|
||||
|
||||
- name: Prepare Crabbox shell
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
git fetch --no-tags --depth=50 origin "+refs/heads/main:refs/remotes/origin/main"
|
||||
|
||||
node_bin="$(dirname "$(node -p 'process.execPath')")"
|
||||
pnpm_bin="$(command -v pnpm)"
|
||||
sudo ln -sf "$node_bin/node" /usr/local/bin/node
|
||||
sudo ln -sf "$node_bin/npm" /usr/local/bin/npm
|
||||
sudo ln -sf "$node_bin/npx" /usr/local/bin/npx
|
||||
sudo ln -sf "$node_bin/corepack" /usr/local/bin/corepack
|
||||
sudo ln -sf "$pnpm_bin" /usr/local/bin/pnpm
|
||||
|
||||
- name: Hydrate provider env helper
|
||||
shell: bash
|
||||
env:
|
||||
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
ANTHROPIC_API_KEY_OLD: ${{ secrets.ANTHROPIC_API_KEY_OLD }}
|
||||
ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }}
|
||||
CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}
|
||||
DEEPINFRA_API_KEY: ${{ secrets.DEEPINFRA_API_KEY }}
|
||||
FIREWORKS_API_KEY: ${{ secrets.FIREWORKS_API_KEY }}
|
||||
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
|
||||
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
|
||||
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
|
||||
KIMI_API_KEY: ${{ secrets.KIMI_API_KEY }}
|
||||
MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }}
|
||||
MISTRAL_API_KEY: ${{ secrets.MISTRAL_API_KEY }}
|
||||
MOONSHOT_API_KEY: ${{ secrets.MOONSHOT_API_KEY }}
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}
|
||||
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
|
||||
QWEN_API_KEY: ${{ secrets.QWEN_API_KEY }}
|
||||
TOGETHER_API_KEY: ${{ secrets.TOGETHER_API_KEY }}
|
||||
XAI_API_KEY: ${{ secrets.XAI_API_KEY }}
|
||||
ZAI_API_KEY: ${{ secrets.ZAI_API_KEY }}
|
||||
Z_AI_API_KEY: ${{ secrets.Z_AI_API_KEY }}
|
||||
run: bash scripts/ci-hydrate-testbox-env.sh
|
||||
|
||||
- name: Mark Crabbox ready
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
job="${{ inputs.crabbox_job }}"
|
||||
if [ -z "$job" ]; then job=hydrate; fi
|
||||
mkdir -p "$HOME/.crabbox/actions"
|
||||
state="$HOME/.crabbox/actions/${{ inputs.crabbox_id }}.env"
|
||||
env_file="$HOME/.crabbox/actions/${{ inputs.crabbox_id }}.env.sh"
|
||||
services_file="$HOME/.crabbox/actions/${{ inputs.crabbox_id }}.services"
|
||||
write_export() {
|
||||
key="$1"
|
||||
value="${!key-}"
|
||||
if [ -n "$value" ]; then
|
||||
printf 'export %s=%q\n' "$key" "$value"
|
||||
fi
|
||||
}
|
||||
{
|
||||
for key in CI GITHUB_ACTIONS GITHUB_WORKSPACE GITHUB_REPOSITORY GITHUB_RUN_ID GITHUB_RUN_NUMBER GITHUB_RUN_ATTEMPT GITHUB_REF GITHUB_REF_NAME GITHUB_SHA GITHUB_EVENT_NAME GITHUB_ACTOR RUNNER_OS RUNNER_ARCH RUNNER_TEMP RUNNER_TOOL_CACHE; do
|
||||
write_export "$key"
|
||||
done
|
||||
} > "${env_file}.tmp"
|
||||
mv "${env_file}.tmp" "$env_file"
|
||||
{
|
||||
echo "# Docker containers visible from the hydrated runner"
|
||||
docker ps --format '{{.Names}}\t{{.Image}}\t{{.Ports}}' 2>/dev/null || true
|
||||
} > "${services_file}.tmp"
|
||||
mv "${services_file}.tmp" "$services_file"
|
||||
tmp="${state}.tmp"
|
||||
{
|
||||
echo "WORKSPACE=${GITHUB_WORKSPACE}"
|
||||
echo "RUN_ID=${GITHUB_RUN_ID}"
|
||||
echo "JOB=${job}"
|
||||
echo "ENV_FILE=${env_file}"
|
||||
echo "SERVICES_FILE=${services_file}"
|
||||
echo "READY_AT=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
} > "$tmp"
|
||||
mv "$tmp" "$state"
|
||||
|
||||
- name: Keep Crabbox job alive
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
minutes="${{ inputs.crabbox_keep_alive_minutes }}"
|
||||
case "$minutes" in
|
||||
''|*[!0-9]*) minutes=90 ;;
|
||||
esac
|
||||
stop="$HOME/.crabbox/actions/${{ inputs.crabbox_id }}.stop"
|
||||
deadline=$(( $(date +%s) + minutes * 60 ))
|
||||
while [ "$(date +%s)" -lt "$deadline" ]; do
|
||||
if [ -f "$stop" ]; then
|
||||
exit 0
|
||||
fi
|
||||
sleep 15
|
||||
done
|
||||
36
.github/workflows/full-release-validation.yml
vendored
36
.github/workflows/full-release-validation.yml
vendored
@@ -29,7 +29,7 @@ on:
|
||||
release_profile:
|
||||
description: Release coverage profile for live/Docker/provider breadth
|
||||
required: false
|
||||
default: full
|
||||
default: stable
|
||||
type: choice
|
||||
options:
|
||||
- minimum
|
||||
@@ -88,7 +88,7 @@ permissions:
|
||||
|
||||
concurrency:
|
||||
group: full-release-validation-${{ inputs.ref }}-${{ inputs.rerun_group }}
|
||||
cancel-in-progress: false
|
||||
cancel-in-progress: ${{ inputs.ref == 'main' && inputs.rerun_group == 'all' }}
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
@@ -222,6 +222,14 @@ jobs:
|
||||
echo "Dispatched ${workflow}: https://github.com/${GITHUB_REPOSITORY}/actions/runs/${run_id}"
|
||||
echo "run_id=${run_id}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
cancel_child() {
|
||||
if [[ -n "${run_id:-}" ]]; then
|
||||
echo "Cancelling child workflow ${workflow}: ${run_id}" >&2
|
||||
gh run cancel "$run_id" >/dev/null 2>&1 || true
|
||||
fi
|
||||
}
|
||||
trap cancel_child EXIT INT TERM
|
||||
|
||||
while true; do
|
||||
status="$(gh run view "$run_id" --json status --jq '.status')"
|
||||
if [[ "$status" == "completed" ]]; then
|
||||
@@ -307,6 +315,14 @@ jobs:
|
||||
echo "Dispatched ${workflow}: https://github.com/${GITHUB_REPOSITORY}/actions/runs/${run_id}"
|
||||
echo "run_id=${run_id}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
cancel_child() {
|
||||
if [[ -n "${run_id:-}" ]]; then
|
||||
echo "Cancelling child workflow ${workflow}: ${run_id}" >&2
|
||||
gh run cancel "$run_id" >/dev/null 2>&1 || true
|
||||
fi
|
||||
}
|
||||
trap cancel_child EXIT INT TERM
|
||||
|
||||
while true; do
|
||||
status="$(gh run view "$run_id" --json status --jq '.status')"
|
||||
if [[ "$status" == "completed" ]]; then
|
||||
@@ -397,6 +413,14 @@ jobs:
|
||||
echo "Dispatched ${workflow}: https://github.com/${GITHUB_REPOSITORY}/actions/runs/${run_id}"
|
||||
echo "run_id=${run_id}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
cancel_child() {
|
||||
if [[ -n "${run_id:-}" ]]; then
|
||||
echo "Cancelling child workflow ${workflow}: ${run_id}" >&2
|
||||
gh run cancel "$run_id" >/dev/null 2>&1 || true
|
||||
fi
|
||||
}
|
||||
trap cancel_child EXIT INT TERM
|
||||
|
||||
while true; do
|
||||
status="$(gh run view "$run_id" --json status --jq '.status')"
|
||||
if [[ "$status" == "completed" ]]; then
|
||||
@@ -501,6 +525,14 @@ jobs:
|
||||
echo "Dispatched npm-telegram-beta-e2e.yml: https://github.com/${GITHUB_REPOSITORY}/actions/runs/${run_id}"
|
||||
echo "run_id=${run_id}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
cancel_child() {
|
||||
if [[ -n "${run_id:-}" ]]; then
|
||||
echo "Cancelling child workflow npm-telegram-beta-e2e.yml: ${run_id}" >&2
|
||||
gh run cancel "$run_id" >/dev/null 2>&1 || true
|
||||
fi
|
||||
}
|
||||
trap cancel_child EXIT INT TERM
|
||||
|
||||
while true; do
|
||||
status="$(gh run view "$run_id" --json status --jq '.status')"
|
||||
if [[ "$status" == "completed" ]]; then
|
||||
|
||||
19
.github/workflows/labeler.yml
vendored
19
.github/workflows/labeler.yml
vendored
@@ -296,6 +296,25 @@ jobs:
|
||||
.filter((name) => typeof name === "string"),
|
||||
);
|
||||
|
||||
if (pullRequest.user?.type === "Bot" || /\[bot\]$/i.test(authorLogin) || authorLogin.startsWith("app/")) {
|
||||
if (labelNames.has(activePrLimitLabel)) {
|
||||
try {
|
||||
await github.rest.issues.removeLabel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: pullRequest.number,
|
||||
name: activePrLimitLabel,
|
||||
});
|
||||
} catch (error) {
|
||||
if (error?.status !== 404) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
core.info(`Skipping active PR limit for GitHub App author ${authorLogin}.`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (labelNames.has(activePrLimitOverrideLabel)) {
|
||||
if (labelNames.has(activePrLimitLabel)) {
|
||||
try {
|
||||
|
||||
15
.github/workflows/macos-release.yml
vendored
15
.github/workflows/macos-release.yml
vendored
@@ -12,6 +12,11 @@ on:
|
||||
required: true
|
||||
default: true
|
||||
type: boolean
|
||||
public_release_branch:
|
||||
description: Public branch that contains the release tag commit, usually main or release/YYYY.M.D
|
||||
required: false
|
||||
default: main
|
||||
type: string
|
||||
|
||||
concurrency:
|
||||
group: macos-release-${{ inputs.tag }}
|
||||
@@ -66,13 +71,17 @@ jobs:
|
||||
- name: Validate release tag and package metadata
|
||||
env:
|
||||
RELEASE_TAG: ${{ inputs.tag }}
|
||||
WORKFLOW_REF_NAME: ${{ github.ref_name }}
|
||||
PUBLIC_RELEASE_BRANCH: ${{ inputs.public_release_branch }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ "${PUBLIC_RELEASE_BRANCH}" != "main" && ! "${PUBLIC_RELEASE_BRANCH}" =~ ^release/[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*$ ]]; then
|
||||
echo "public_release_branch must be main or release/YYYY.M.D, got ${PUBLIC_RELEASE_BRANCH}." >&2
|
||||
exit 1
|
||||
fi
|
||||
RELEASE_SHA=$(git rev-parse HEAD)
|
||||
RELEASE_MAIN_REF="refs/remotes/origin/${WORKFLOW_REF_NAME}"
|
||||
RELEASE_MAIN_REF="refs/remotes/origin/${PUBLIC_RELEASE_BRANCH}"
|
||||
export RELEASE_SHA RELEASE_TAG RELEASE_MAIN_REF
|
||||
git fetch --no-tags origin "+refs/heads/${WORKFLOW_REF_NAME}:refs/remotes/origin/${WORKFLOW_REF_NAME}"
|
||||
git fetch --no-tags origin "+refs/heads/${PUBLIC_RELEASE_BRANCH}:refs/remotes/origin/${PUBLIC_RELEASE_BRANCH}"
|
||||
pnpm release:openclaw:npm:check
|
||||
|
||||
- name: Summarize next step
|
||||
|
||||
@@ -28,6 +28,26 @@ on:
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
targeted_docker_lane_group_size:
|
||||
description: Number of targeted Docker lanes to batch into one runner job
|
||||
required: false
|
||||
default: 1
|
||||
type: number
|
||||
published_upgrade_survivor_baseline:
|
||||
description: Published OpenClaw package baseline for the published-upgrade-survivor Docker lane
|
||||
required: false
|
||||
default: openclaw@latest
|
||||
type: string
|
||||
published_upgrade_survivor_baselines:
|
||||
description: Optional exact baseline list for published-upgrade-survivor lane expansion
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
published_upgrade_survivor_scenarios:
|
||||
description: Optional scenario list for published-upgrade-survivor lane expansion
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
package_artifact_name:
|
||||
description: Existing workflow artifact containing openclaw-current.tgz; blank packs the selected ref
|
||||
required: false
|
||||
@@ -71,7 +91,7 @@ on:
|
||||
release_test_profile:
|
||||
description: Release coverage profile for live/Docker/provider breadth
|
||||
required: false
|
||||
default: full
|
||||
default: stable
|
||||
type: choice
|
||||
options:
|
||||
- minimum
|
||||
@@ -103,6 +123,26 @@ on:
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
targeted_docker_lane_group_size:
|
||||
description: Number of targeted Docker lanes to batch into one runner job
|
||||
required: false
|
||||
default: 1
|
||||
type: number
|
||||
published_upgrade_survivor_baseline:
|
||||
description: Published OpenClaw package baseline for the published-upgrade-survivor Docker lane
|
||||
required: false
|
||||
default: openclaw@latest
|
||||
type: string
|
||||
published_upgrade_survivor_baselines:
|
||||
description: Optional exact baseline list for published-upgrade-survivor lane expansion
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
published_upgrade_survivor_scenarios:
|
||||
description: Optional scenario list for published-upgrade-survivor lane expansion
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
package_artifact_name:
|
||||
description: Existing workflow artifact containing openclaw-current.tgz; blank packs the selected ref
|
||||
required: false
|
||||
@@ -146,7 +186,7 @@ on:
|
||||
release_test_profile:
|
||||
description: Release coverage profile for live/Docker/provider breadth
|
||||
required: false
|
||||
default: full
|
||||
default: stable
|
||||
type: string
|
||||
secrets:
|
||||
OPENAI_API_KEY:
|
||||
@@ -374,6 +414,10 @@ jobs:
|
||||
add_profile_suite native-live-extensions-xai "full"
|
||||
|
||||
add_profile_suite live-gateway-docker "minimum stable full"
|
||||
add_profile_suite live-gateway-anthropic-docker "stable full"
|
||||
add_profile_suite live-gateway-google-docker "stable full"
|
||||
add_profile_suite live-gateway-minimax-docker "stable full"
|
||||
add_profile_suite live-gateway-advisory-docker "full"
|
||||
add_profile_suite live-cli-backend-docker "stable full"
|
||||
add_profile_suite live-acp-bind-docker "stable full"
|
||||
add_profile_suite live-codex-harness-docker "stable full"
|
||||
@@ -670,6 +714,9 @@ jobs:
|
||||
OPENCLAW_DOCKER_E2E_REPO_ROOT: ${{ github.workspace }}
|
||||
OPENCLAW_DOCKER_E2E_SELECTED_SHA: ${{ needs.validate_selected_ref.outputs.selected_sha }}
|
||||
OPENCLAW_CURRENT_PACKAGE_TGZ: .artifacts/docker-e2e-package/openclaw-current.tgz
|
||||
OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC: ${{ inputs.published_upgrade_survivor_baseline }}
|
||||
OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPECS: ${{ inputs.published_upgrade_survivor_baselines }}
|
||||
OPENCLAW_UPGRADE_SURVIVOR_SCENARIOS: ${{ inputs.published_upgrade_survivor_scenarios }}
|
||||
OPENCLAW_SKIP_DOCKER_BUILD: "1"
|
||||
INCLUDE_OPENWEBUI: ${{ inputs.include_openwebui }}
|
||||
DOCKER_E2E_CHUNK: ${{ matrix.chunk_id }}
|
||||
@@ -815,16 +862,27 @@ jobs:
|
||||
shell: bash
|
||||
env:
|
||||
LANES: ${{ inputs.docker_lanes }}
|
||||
GROUP_SIZE: ${{ inputs.targeted_docker_lane_group_size }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
groups_json="$(
|
||||
LANES="$LANES" node <<'NODE'
|
||||
LANES="$LANES" GROUP_SIZE="$GROUP_SIZE" node <<'NODE'
|
||||
const lanes = [...new Set(String(process.env.LANES || "").split(/[,\s]+/u).map((lane) => lane.trim()).filter(Boolean))];
|
||||
if (lanes.length === 0) {
|
||||
throw new Error("docker_lanes is required when planning targeted Docker lane groups.");
|
||||
}
|
||||
const rawGroupSize = Number.parseInt(process.env.GROUP_SIZE || "1", 10);
|
||||
const groupSize = Number.isFinite(rawGroupSize) && rawGroupSize > 0 ? rawGroupSize : 1;
|
||||
const sanitize = (lane) => lane.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "targeted";
|
||||
process.stdout.write(JSON.stringify(lanes.map((lane) => ({ label: sanitize(lane), docker_lanes: lane }))));
|
||||
const groups = [];
|
||||
for (let index = 0; index < lanes.length; index += groupSize) {
|
||||
const groupLanes = lanes.slice(index, index + groupSize);
|
||||
const first = sanitize(groupLanes[0]);
|
||||
const last = sanitize(groupLanes[groupLanes.length - 1]);
|
||||
const label = groupLanes.length === 1 ? first : `${first}--${last}`;
|
||||
groups.push({ label, docker_lanes: groupLanes.join(" ") });
|
||||
}
|
||||
process.stdout.write(JSON.stringify(groups));
|
||||
NODE
|
||||
)"
|
||||
echo "groups_json=${groups_json}" >> "$GITHUB_OUTPUT"
|
||||
@@ -834,7 +892,7 @@ jobs:
|
||||
if: inputs.docker_lanes != ''
|
||||
name: Docker E2E targeted lanes (${{ matrix.group.label }})
|
||||
runs-on: blacksmith-32vcpu-ubuntu-2404
|
||||
timeout-minutes: 180
|
||||
timeout-minutes: 90
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -892,6 +950,9 @@ jobs:
|
||||
OPENCLAW_DOCKER_E2E_REPO_ROOT: ${{ github.workspace }}
|
||||
OPENCLAW_DOCKER_E2E_SELECTED_SHA: ${{ needs.validate_selected_ref.outputs.selected_sha }}
|
||||
OPENCLAW_CURRENT_PACKAGE_TGZ: .artifacts/docker-e2e-package/openclaw-current.tgz
|
||||
OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC: ${{ inputs.published_upgrade_survivor_baseline }}
|
||||
OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPECS: ${{ inputs.published_upgrade_survivor_baselines }}
|
||||
OPENCLAW_UPGRADE_SURVIVOR_SCENARIOS: ${{ inputs.published_upgrade_survivor_scenarios }}
|
||||
OPENCLAW_SKIP_DOCKER_BUILD: "1"
|
||||
INCLUDE_OPENWEBUI: ${{ inputs.include_openwebui }}
|
||||
DOCKER_E2E_LANES: ${{ matrix.group.docker_lanes }}
|
||||
@@ -1468,7 +1529,7 @@ jobs:
|
||||
needs: [validate_selected_ref, prepare_live_test_image]
|
||||
if: inputs.include_live_suites && inputs.live_model_providers == '' && (inputs.live_suite_filter == '' || inputs.live_suite_filter == 'docker-live-models')
|
||||
runs-on: blacksmith-32vcpu-ubuntu-2404
|
||||
timeout-minutes: 75
|
||||
timeout-minutes: 45
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -1536,6 +1597,8 @@ jobs:
|
||||
FIREWORKS_API_KEY: ${{ secrets.FIREWORKS_API_KEY }}
|
||||
OPENCLAW_LIVE_PROVIDERS: ${{ matrix.providers }}
|
||||
OPENCLAW_LIVE_IMAGE: ${{ needs.prepare_live_test_image.outputs.live_image }}
|
||||
OPENCLAW_LIVE_MAX_MODELS: "6"
|
||||
OPENCLAW_LIVE_MODEL_TIMEOUT_MS: "45000"
|
||||
OPENCLAW_SKIP_DOCKER_BUILD: "1"
|
||||
OPENCLAW_VITEST_MAX_WORKERS: "2"
|
||||
steps:
|
||||
@@ -1611,14 +1674,14 @@ jobs:
|
||||
|
||||
- name: Run Docker live model sweep
|
||||
if: contains(matrix.profiles, inputs.release_test_profile)
|
||||
run: OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" bash .release-harness/scripts/test-live-models-docker.sh
|
||||
run: OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 35m bash .release-harness/scripts/test-live-models-docker.sh
|
||||
|
||||
validate_live_models_docker_targeted:
|
||||
name: Docker live models (selected providers)
|
||||
needs: [validate_selected_ref, prepare_live_test_image]
|
||||
if: inputs.include_live_suites && inputs.live_model_providers != '' && (inputs.live_suite_filter == '' || inputs.live_suite_filter == 'docker-live-models')
|
||||
runs-on: blacksmith-32vcpu-ubuntu-2404
|
||||
timeout-minutes: 75
|
||||
timeout-minutes: 45
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}
|
||||
@@ -1655,6 +1718,8 @@ jobs:
|
||||
FIREWORKS_API_KEY: ${{ secrets.FIREWORKS_API_KEY }}
|
||||
REQUESTED_LIVE_MODEL_PROVIDERS: ${{ inputs.live_model_providers }}
|
||||
OPENCLAW_LIVE_IMAGE: ${{ needs.prepare_live_test_image.outputs.live_image }}
|
||||
OPENCLAW_LIVE_MAX_MODELS: "6"
|
||||
OPENCLAW_LIVE_MODEL_TIMEOUT_MS: "45000"
|
||||
OPENCLAW_SKIP_DOCKER_BUILD: "1"
|
||||
OPENCLAW_VITEST_MAX_WORKERS: "2"
|
||||
steps:
|
||||
@@ -1785,7 +1850,7 @@ jobs:
|
||||
done
|
||||
|
||||
- name: Run Docker live model sweep
|
||||
run: OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" bash .release-harness/scripts/test-live-models-docker.sh
|
||||
run: OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 35m bash .release-harness/scripts/test-live-models-docker.sh
|
||||
|
||||
validate_live_provider_suites:
|
||||
needs: validate_selected_ref
|
||||
@@ -2099,27 +2164,51 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- suite_id: live-gateway-docker
|
||||
label: Docker live gateway
|
||||
command: OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" bash .release-harness/scripts/test-live-gateway-models-docker.sh
|
||||
timeout_minutes: 120
|
||||
label: Docker live gateway OpenAI
|
||||
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=openai OPENCLAW_LIVE_GATEWAY_MAX_MODELS=2 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=30000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=60000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 25m bash .release-harness/scripts/test-live-gateway-models-docker.sh
|
||||
timeout_minutes: 30
|
||||
profile_env_only: false
|
||||
profiles: minimum stable full
|
||||
- suite_id: live-gateway-anthropic-docker
|
||||
label: Docker live gateway Anthropic
|
||||
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=anthropic OPENCLAW_LIVE_GATEWAY_MAX_MODELS=2 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=30000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=60000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 25m bash .release-harness/scripts/test-live-gateway-models-docker.sh
|
||||
timeout_minutes: 30
|
||||
profile_env_only: false
|
||||
profiles: stable full
|
||||
- suite_id: live-gateway-google-docker
|
||||
label: Docker live gateway Google
|
||||
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=google OPENCLAW_LIVE_GATEWAY_MODELS=google/gemini-3.1-pro-preview,google/gemini-3-flash-preview OPENCLAW_LIVE_GATEWAY_MAX_MODELS=2 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=30000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=60000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 25m bash .release-harness/scripts/test-live-gateway-models-docker.sh
|
||||
timeout_minutes: 30
|
||||
profile_env_only: false
|
||||
profiles: stable full
|
||||
- suite_id: live-gateway-minimax-docker
|
||||
label: Docker live gateway MiniMax
|
||||
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=minimax,minimax-portal OPENCLAW_LIVE_GATEWAY_MAX_MODELS=2 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=30000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=60000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 25m bash .release-harness/scripts/test-live-gateway-models-docker.sh
|
||||
timeout_minutes: 30
|
||||
profile_env_only: false
|
||||
profiles: stable full
|
||||
- suite_id: live-gateway-advisory-docker
|
||||
label: Docker live gateway advisory providers
|
||||
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=deepseek,fireworks,opencode-go,openrouter,xai,zai OPENCLAW_LIVE_GATEWAY_MAX_MODELS=6 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=30000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=60000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 35m bash .release-harness/scripts/test-live-gateway-models-docker.sh
|
||||
timeout_minutes: 40
|
||||
profile_env_only: false
|
||||
profiles: full
|
||||
- suite_id: live-cli-backend-docker
|
||||
label: Docker live CLI backend
|
||||
command: OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" bash .release-harness/scripts/test-live-cli-backend-docker.sh
|
||||
timeout_minutes: 120
|
||||
command: OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 45m bash .release-harness/scripts/test-live-cli-backend-docker.sh
|
||||
timeout_minutes: 50
|
||||
profile_env_only: false
|
||||
profiles: stable full
|
||||
- suite_id: live-acp-bind-docker
|
||||
label: Docker live ACP bind
|
||||
command: OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" bash .release-harness/scripts/test-live-acp-bind-docker.sh
|
||||
timeout_minutes: 120
|
||||
command: OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 45m bash .release-harness/scripts/test-live-acp-bind-docker.sh
|
||||
timeout_minutes: 50
|
||||
profile_env_only: false
|
||||
profiles: stable full
|
||||
- suite_id: live-codex-harness-docker
|
||||
label: Docker live Codex harness
|
||||
command: OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" bash .release-harness/scripts/test-live-codex-harness-docker.sh
|
||||
timeout_minutes: 120
|
||||
command: OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 35m bash .release-harness/scripts/test-live-codex-harness-docker.sh
|
||||
timeout_minutes: 40
|
||||
profile_env_only: false
|
||||
profiles: stable full
|
||||
env:
|
||||
|
||||
@@ -33,7 +33,7 @@ on:
|
||||
release_profile:
|
||||
description: Release coverage profile for live/Docker/provider breadth
|
||||
required: false
|
||||
default: full
|
||||
default: stable
|
||||
type: choice
|
||||
options:
|
||||
- minimum
|
||||
|
||||
7
.github/workflows/opengrep-precise-full.yml
vendored
7
.github/workflows/opengrep-precise-full.yml
vendored
@@ -11,6 +11,9 @@ concurrency:
|
||||
group: opengrep-full-${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
security-events: write
|
||||
@@ -22,7 +25,7 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -50,7 +53,7 @@ jobs:
|
||||
scripts/run-opengrep.sh --sarif --error
|
||||
|
||||
- name: Upload SARIF to GitHub Code Scanning
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
uses: github/codeql-action/upload-sarif@v4
|
||||
# Only upload if the scan actually produced a SARIF file.
|
||||
if: always() && hashFiles('.opengrep-out/precise.sarif') != ''
|
||||
with:
|
||||
|
||||
34
.github/workflows/opengrep-precise.yml
vendored
34
.github/workflows/opengrep-precise.yml
vendored
@@ -9,11 +9,26 @@ name: OpenGrep — PR Diff
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
paths:
|
||||
- ".github/actions/ensure-base-commit/**"
|
||||
- ".github/workflows/opengrep-precise.yml"
|
||||
- ".github/workflows/opengrep-precise-full.yml"
|
||||
- ".semgrepignore"
|
||||
- "apps/**"
|
||||
- "extensions/**"
|
||||
- "packages/**"
|
||||
- "scripts/**"
|
||||
- "security/opengrep/**"
|
||||
- "src/**"
|
||||
|
||||
concurrency:
|
||||
group: opengrep-pr-diff-${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
security-events: write
|
||||
@@ -21,15 +36,24 @@ permissions:
|
||||
jobs:
|
||||
scan:
|
||||
name: Scan changed paths (precise)
|
||||
runs-on: blacksmith-16vcpu-ubuntu-2404
|
||||
if: ${{ !github.event.pull_request.draft }}
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ github.sha }}
|
||||
fetch-depth: 1
|
||||
fetch-tags: false
|
||||
persist-credentials: false
|
||||
# `scripts/run-opengrep.sh --changed` diffs base...HEAD.
|
||||
fetch-depth: 0
|
||||
submodules: false
|
||||
|
||||
- name: Ensure PR base commit
|
||||
uses: ./.github/actions/ensure-base-commit
|
||||
with:
|
||||
base-sha: ${{ github.event.pull_request.base.sha }}
|
||||
fetch-ref: ${{ github.event.pull_request.base.ref }}
|
||||
|
||||
- name: Install opengrep
|
||||
env:
|
||||
@@ -59,7 +83,7 @@ jobs:
|
||||
scripts/run-opengrep.sh --changed --sarif --error
|
||||
|
||||
- name: Upload SARIF to GitHub Code Scanning
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
uses: github/codeql-action/upload-sarif@v4
|
||||
# Only upload if the scan actually produced a SARIF file.
|
||||
if: always() && hashFiles('.opengrep-out/precise.sarif') != ''
|
||||
with:
|
||||
|
||||
83
.github/workflows/package-acceptance.yml
vendored
83
.github/workflows/package-acceptance.yml
vendored
@@ -64,6 +64,21 @@ on:
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
published_upgrade_survivor_baseline:
|
||||
description: Published OpenClaw package baseline for the published-upgrade-survivor Docker lane
|
||||
required: false
|
||||
default: openclaw@latest
|
||||
type: string
|
||||
published_upgrade_survivor_baselines:
|
||||
description: Optional baseline list for published-upgrade-survivor; use release-history for last 6 plus key legacy releases
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
published_upgrade_survivor_scenarios:
|
||||
description: Optional scenario list for published-upgrade-survivor; use reported-issues for known upgrade failure shapes
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
telegram_mode:
|
||||
description: Optional Telegram QA lane for the resolved package candidate
|
||||
required: true
|
||||
@@ -129,6 +144,21 @@ on:
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
published_upgrade_survivor_baseline:
|
||||
description: Published OpenClaw package baseline for the published-upgrade-survivor Docker lane
|
||||
required: false
|
||||
default: openclaw@latest
|
||||
type: string
|
||||
published_upgrade_survivor_baselines:
|
||||
description: Optional baseline list for published-upgrade-survivor; use release-history for last 6 plus key legacy releases
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
published_upgrade_survivor_scenarios:
|
||||
description: Optional scenario list for published-upgrade-survivor; use reported-issues for known upgrade failure shapes
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
telegram_mode:
|
||||
description: Optional Telegram QA lane for the resolved package candidate
|
||||
required: false
|
||||
@@ -265,6 +295,8 @@ jobs:
|
||||
package_source_sha: ${{ steps.resolve.outputs.package_source_sha }}
|
||||
package_sha256: ${{ steps.resolve.outputs.sha256 }}
|
||||
package_version: ${{ steps.resolve.outputs.package_version }}
|
||||
published_upgrade_survivor_baselines: ${{ steps.upgrade_survivor_baselines.outputs.baselines }}
|
||||
published_upgrade_survivor_scenarios: ${{ inputs.published_upgrade_survivor_scenarios }}
|
||||
telegram_enabled: ${{ steps.profile.outputs.telegram_enabled }}
|
||||
telegram_mode: ${{ steps.profile.outputs.telegram_mode }}
|
||||
steps:
|
||||
@@ -354,10 +386,10 @@ jobs:
|
||||
docker_lanes="npm-onboard-channel-agent gateway-network config-reload"
|
||||
;;
|
||||
package)
|
||||
docker_lanes="npm-onboard-channel-agent doctor-switch update-channel-switch bundled-channel-deps-compat plugins-offline plugin-update"
|
||||
docker_lanes="npm-onboard-channel-agent doctor-switch update-channel-switch upgrade-survivor published-upgrade-survivor bundled-channel-deps-compat plugins-offline plugin-update"
|
||||
;;
|
||||
product)
|
||||
docker_lanes="npm-onboard-channel-agent doctor-switch update-channel-switch bundled-channel-deps-compat plugins plugin-update mcp-channels cron-mcp-cleanup openai-web-search-minimal openwebui"
|
||||
docker_lanes="npm-onboard-channel-agent doctor-switch update-channel-switch upgrade-survivor published-upgrade-survivor bundled-channel-deps-compat plugins plugin-update mcp-channels cron-mcp-cleanup openai-web-search-minimal openwebui"
|
||||
include_openwebui=true
|
||||
;;
|
||||
full)
|
||||
@@ -395,6 +427,44 @@ jobs:
|
||||
echo "package_artifact_name=${PACKAGE_ARTIFACT_NAME}"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Resolve published upgrade survivor baselines
|
||||
id: upgrade_survivor_baselines
|
||||
env:
|
||||
FALLBACK_BASELINE: ${{ inputs.published_upgrade_survivor_baseline }}
|
||||
REQUESTED_BASELINES: ${{ inputs.published_upgrade_survivor_baselines }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ -z "${REQUESTED_BASELINES// }" ]]; then
|
||||
echo "baselines=" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
releases_json=""
|
||||
npm_versions_json=""
|
||||
if [[ "$REQUESTED_BASELINES" == *"release-history"* ]]; then
|
||||
releases_json=".artifacts/package-candidate-input/openclaw-releases.json"
|
||||
npm_versions_json=".artifacts/package-candidate-input/openclaw-npm-versions.json"
|
||||
mkdir -p "$(dirname "$releases_json")"
|
||||
gh release list --repo "$GITHUB_REPOSITORY" --limit 100 --json tagName,publishedAt,isPrerelease > "$releases_json"
|
||||
npm view openclaw versions --json > "$npm_versions_json"
|
||||
fi
|
||||
args=(
|
||||
--requested "$REQUESTED_BASELINES"
|
||||
--fallback "$FALLBACK_BASELINE"
|
||||
--github-output "$GITHUB_OUTPUT"
|
||||
)
|
||||
if [[ -n "$releases_json" ]]; then
|
||||
args+=(
|
||||
--releases-json "$releases_json"
|
||||
--npm-versions-json "$npm_versions_json"
|
||||
--history-count 6
|
||||
--include-version 2026.4.23
|
||||
--pre-date 2026-03-15T00:00:00Z
|
||||
)
|
||||
fi
|
||||
node scripts/resolve-upgrade-survivor-baselines.mjs "${args[@]}" >/dev/null
|
||||
|
||||
- name: Upload package-under-test artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
@@ -413,6 +483,9 @@ jobs:
|
||||
SOURCE: ${{ inputs.source }}
|
||||
SUITE_PROFILE: ${{ inputs.suite_profile }}
|
||||
WORKFLOW_REF: ${{ inputs.workflow_ref }}
|
||||
PUBLISHED_UPGRADE_SURVIVOR_BASELINE: ${{ inputs.published_upgrade_survivor_baseline }}
|
||||
PUBLISHED_UPGRADE_SURVIVOR_BASELINES: ${{ steps.upgrade_survivor_baselines.outputs.baselines }}
|
||||
PUBLISHED_UPGRADE_SURVIVOR_SCENARIOS: ${{ inputs.published_upgrade_survivor_scenarios }}
|
||||
shell: bash
|
||||
run: |
|
||||
{
|
||||
@@ -426,6 +499,9 @@ jobs:
|
||||
echo "- Version: \`${PACKAGE_VERSION}\`"
|
||||
echo "- SHA-256: \`${PACKAGE_SHA256}\`"
|
||||
echo "- Profile: \`${SUITE_PROFILE}\`"
|
||||
echo "- Published upgrade survivor baseline: \`${PUBLISHED_UPGRADE_SURVIVOR_BASELINE}\`"
|
||||
echo "- Published upgrade survivor baselines: \`${PUBLISHED_UPGRADE_SURVIVOR_BASELINES}\`"
|
||||
echo "- Published upgrade survivor scenarios: \`${PUBLISHED_UPGRADE_SURVIVOR_SCENARIOS}\`"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
docker_acceptance:
|
||||
@@ -438,6 +514,9 @@ jobs:
|
||||
include_release_path_suites: ${{ needs.resolve_package.outputs.include_release_path_suites == 'true' }}
|
||||
include_openwebui: ${{ needs.resolve_package.outputs.include_openwebui == 'true' }}
|
||||
docker_lanes: ${{ needs.resolve_package.outputs.docker_lanes }}
|
||||
published_upgrade_survivor_baseline: ${{ inputs.published_upgrade_survivor_baseline }}
|
||||
published_upgrade_survivor_baselines: ${{ needs.resolve_package.outputs.published_upgrade_survivor_baselines }}
|
||||
published_upgrade_survivor_scenarios: ${{ needs.resolve_package.outputs.published_upgrade_survivor_scenarios }}
|
||||
package_artifact_name: ${{ needs.resolve_package.outputs.package_artifact_name }}
|
||||
include_live_suites: ${{ needs.resolve_package.outputs.include_live_suites == 'true' }}
|
||||
live_models_only: false
|
||||
|
||||
17
.github/workflows/parity-gate.yml
vendored
17
.github/workflows/parity-gate.yml
vendored
@@ -1,18 +1,10 @@
|
||||
name: Parity gate
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize, ready_for_review]
|
||||
paths:
|
||||
- "extensions/qa-lab/**"
|
||||
- "extensions/qa-channel/**"
|
||||
- "extensions/openai/**"
|
||||
- "qa/scenarios/**"
|
||||
- "src/agents/**"
|
||||
- "src/context-engine/**"
|
||||
- "src/gateway/**"
|
||||
- "src/media/**"
|
||||
- ".github/workflows/parity-gate.yml"
|
||||
schedule:
|
||||
- cron: "17 3 * * *"
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
@@ -25,7 +17,6 @@ concurrency:
|
||||
jobs:
|
||||
parity-gate:
|
||||
name: Run the OpenAI / Opus 4.6 parity gate against the qa-lab mock
|
||||
if: ${{ github.event.pull_request.draft != true }}
|
||||
runs-on: blacksmith-32vcpu-ubuntu-2404
|
||||
timeout-minutes: 30
|
||||
env:
|
||||
|
||||
1
.github/workflows/plugin-prerelease.yml
vendored
1
.github/workflows/plugin-prerelease.yml
vendored
@@ -362,6 +362,7 @@ jobs:
|
||||
include_release_path_suites: false
|
||||
include_openwebui: false
|
||||
docker_lanes: ${{ needs.preflight.outputs.plugin_prerelease_docker_lanes }}
|
||||
targeted_docker_lane_group_size: 4
|
||||
include_live_suites: false
|
||||
live_models_only: false
|
||||
|
||||
|
||||
293
.github/workflows/stale.yml
vendored
293
.github/workflows/stale.yml
vendored
@@ -4,6 +4,32 @@ on:
|
||||
schedule:
|
||||
- cron: "17 3 * * *"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
backfill_stale_closures:
|
||||
description: "Close currently stale-eligible issues and PRs with the Barnacle app"
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
dry_run:
|
||||
description: "List matching stale-eligible items without closing them"
|
||||
required: false
|
||||
type: boolean
|
||||
default: true
|
||||
include_issues:
|
||||
description: "Include stale-eligible issues in the backfill"
|
||||
required: false
|
||||
type: boolean
|
||||
default: true
|
||||
include_prs:
|
||||
description: "Include stale-eligible pull requests in the backfill"
|
||||
required: false
|
||||
type: boolean
|
||||
default: true
|
||||
max_closures:
|
||||
description: "Maximum items to close when dry_run is false"
|
||||
required: false
|
||||
type: number
|
||||
default: 50
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
@@ -12,6 +38,7 @@ permissions: {}
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
if: ${{ github.event_name != 'workflow_dispatch' || inputs.backfill_stale_closures != true }}
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
@@ -35,10 +62,10 @@ jobs:
|
||||
uses: actions/stale@v10
|
||||
with:
|
||||
repo-token: ${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token }}
|
||||
days-before-issue-stale: 7
|
||||
days-before-issue-close: 5
|
||||
days-before-pr-stale: 5
|
||||
days-before-pr-close: 3
|
||||
days-before-issue-stale: 14
|
||||
days-before-issue-close: 7
|
||||
days-before-pr-stale: 14
|
||||
days-before-pr-close: 7
|
||||
stale-issue-label: stale
|
||||
stale-pr-label: stale
|
||||
exempt-issue-labels: enhancement,maintainer,pinned,security,no-stale,bad-barnacle
|
||||
@@ -95,7 +122,7 @@ jobs:
|
||||
days-before-issue-stale: -1
|
||||
days-before-issue-close: -1
|
||||
days-before-pr-stale: 27
|
||||
days-before-pr-close: 3
|
||||
days-before-pr-close: 7
|
||||
stale-pr-label: stale
|
||||
exempt-pr-labels: maintainer,no-stale,bad-barnacle
|
||||
operations-per-run: 2000
|
||||
@@ -139,10 +166,10 @@ jobs:
|
||||
uses: actions/stale@v10
|
||||
with:
|
||||
repo-token: ${{ steps.app-token-fallback.outputs.token }}
|
||||
days-before-issue-stale: 7
|
||||
days-before-issue-close: 5
|
||||
days-before-pr-stale: 5
|
||||
days-before-pr-close: 3
|
||||
days-before-issue-stale: 14
|
||||
days-before-issue-close: 7
|
||||
days-before-pr-stale: 14
|
||||
days-before-pr-close: 7
|
||||
stale-issue-label: stale
|
||||
stale-pr-label: stale
|
||||
exempt-issue-labels: enhancement,maintainer,pinned,security,no-stale,bad-barnacle
|
||||
@@ -197,7 +224,7 @@ jobs:
|
||||
days-before-issue-stale: -1
|
||||
days-before-issue-close: -1
|
||||
days-before-pr-stale: 27
|
||||
days-before-pr-close: 3
|
||||
days-before-pr-close: 7
|
||||
stale-pr-label: stale
|
||||
exempt-pr-labels: maintainer,no-stale,bad-barnacle
|
||||
operations-per-run: 2000
|
||||
@@ -213,7 +240,253 @@ jobs:
|
||||
If you believe this PR should be revived, post in #clawtributors on Discord to talk to a maintainer.
|
||||
That channel is the escape hatch for high-quality PRs that get auto-closed.
|
||||
|
||||
backfill-stale-closures:
|
||||
if: ${{ github.event_name == 'workflow_dispatch' && inputs.backfill_stale_closures == true }}
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
runs-on: blacksmith-16vcpu-ubuntu-2404
|
||||
steps:
|
||||
- uses: actions/create-github-app-token@v3
|
||||
id: app-token
|
||||
with:
|
||||
app-id: "2971289"
|
||||
private-key: ${{ secrets.GH_APP_PRIVATE_KEY_FALLBACK }}
|
||||
- name: Backfill stale closures
|
||||
uses: actions/github-script@v9
|
||||
env:
|
||||
DRY_RUN: ${{ inputs.dry_run }}
|
||||
INCLUDE_ISSUES: ${{ inputs.include_issues }}
|
||||
INCLUDE_PRS: ${{ inputs.include_prs }}
|
||||
MAX_CLOSURES: ${{ inputs.max_closures }}
|
||||
with:
|
||||
github-token: ${{ steps.app-token.outputs.token }}
|
||||
script: |
|
||||
const dayMs = 24 * 60 * 60 * 1000;
|
||||
const dryRun = process.env.DRY_RUN !== "false";
|
||||
const includeIssues = process.env.INCLUDE_ISSUES !== "false";
|
||||
const includePrs = process.env.INCLUDE_PRS !== "false";
|
||||
const maxClosures = Math.max(0, Number(process.env.MAX_CLOSURES || "50"));
|
||||
const nowMs = Date.now();
|
||||
const { owner, repo } = context.repo;
|
||||
|
||||
const issueExemptLabels = new Set([
|
||||
"enhancement",
|
||||
"maintainer",
|
||||
"pinned",
|
||||
"security",
|
||||
"no-stale",
|
||||
"bad-barnacle",
|
||||
]);
|
||||
const prExemptLabels = new Set(["maintainer", "no-stale", "bad-barnacle"]);
|
||||
const maintainerAssociations = new Set(["OWNER", "MEMBER", "COLLABORATOR"]);
|
||||
const maintainerLogins = new Set([
|
||||
"altaywtf",
|
||||
"BunsDev",
|
||||
"cpojer",
|
||||
"gumadeiras",
|
||||
"hydro13",
|
||||
"hxy91819",
|
||||
"jalehman",
|
||||
"joshavant",
|
||||
"joshp123",
|
||||
"mbelinky",
|
||||
"mukhtharcm",
|
||||
"ngutman",
|
||||
"obviyus",
|
||||
"odysseus0",
|
||||
"onutc",
|
||||
"osolmaz",
|
||||
"sebslight",
|
||||
"sliverp",
|
||||
"steipete",
|
||||
"thewilloftheshadow",
|
||||
"tyler6204",
|
||||
"velvet-shark",
|
||||
"vignesh07",
|
||||
"vincentkoc",
|
||||
"visionik",
|
||||
].map(login => login.toLowerCase()));
|
||||
|
||||
const issueCloseMessage = [
|
||||
"Closing due to inactivity.",
|
||||
"If this is still an issue, please retry on the latest OpenClaw release and share updated details.",
|
||||
"If you are absolutely sure it still happens on the latest release, open a new issue with fresh steps to reproduce.",
|
||||
].join("\n");
|
||||
const prCloseMessage = [
|
||||
"Closing due to inactivity.",
|
||||
"If you believe this PR should be revived, post in #clawtributors on Discord to talk to a maintainer.",
|
||||
"That channel is the escape hatch for high-quality PRs that get auto-closed.",
|
||||
].join("\n");
|
||||
|
||||
const hasAny = (labels, exemptLabels) => {
|
||||
for (const label of labels) {
|
||||
if (exemptLabels.has(label)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
const isOlderThan = (dateString, days) => {
|
||||
const timestamp = Date.parse(dateString);
|
||||
return Number.isFinite(timestamp) && timestamp < nowMs - days * dayMs;
|
||||
};
|
||||
|
||||
const candidates = [];
|
||||
const skipped = {
|
||||
missingStale: 0,
|
||||
exemptLabel: 0,
|
||||
maintainerAuthor: 0,
|
||||
maintainerAssignee: 0,
|
||||
notOldEnough: 0,
|
||||
disabledType: 0,
|
||||
};
|
||||
|
||||
for await (const response of github.paginate.iterator(github.rest.issues.listForRepo, {
|
||||
owner,
|
||||
repo,
|
||||
state: "open",
|
||||
sort: "updated",
|
||||
direction: "asc",
|
||||
per_page: 100,
|
||||
})) {
|
||||
for (const item of response.data) {
|
||||
const isPr = Boolean(item.pull_request);
|
||||
if ((isPr && !includePrs) || (!isPr && !includeIssues)) {
|
||||
skipped.disabledType += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
const labels = new Set((item.labels || []).map(label => label.name));
|
||||
if (!labels.has("stale")) {
|
||||
skipped.missingStale += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
const exemptLabels = isPr ? prExemptLabels : issueExemptLabels;
|
||||
if (hasAny(labels, exemptLabels)) {
|
||||
skipped.exemptLabel += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (maintainerAssociations.has(item.author_association)) {
|
||||
skipped.maintainerAuthor += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
const assigned = (item.assignees || []).length > 0;
|
||||
const assignedToMaintainer = (item.assignees || []).some(assignee =>
|
||||
maintainerLogins.has(assignee.login.toLowerCase()),
|
||||
);
|
||||
if (assignedToMaintainer) {
|
||||
skipped.maintainerAssignee += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
let eligible = false;
|
||||
let lane = "";
|
||||
if (isPr && assigned) {
|
||||
lane = "assigned-pr";
|
||||
eligible = isOlderThan(item.created_at, 34) && isOlderThan(item.updated_at, 7);
|
||||
} else if (isPr) {
|
||||
lane = "unassigned-pr";
|
||||
eligible = isOlderThan(item.updated_at, 7);
|
||||
} else if (assigned) {
|
||||
lane = "assigned-issue";
|
||||
eligible = isOlderThan(item.updated_at, 10);
|
||||
} else {
|
||||
lane = "unassigned-issue";
|
||||
eligible = isOlderThan(item.updated_at, 7);
|
||||
}
|
||||
|
||||
if (!eligible) {
|
||||
skipped.notOldEnough += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
candidates.push({
|
||||
number: item.number,
|
||||
title: item.title,
|
||||
lane,
|
||||
isPr,
|
||||
assigned,
|
||||
createdAt: item.created_at,
|
||||
updatedAt: item.updated_at,
|
||||
authorAssociation: item.author_association,
|
||||
url: item.html_url,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const countsByLane = candidates.reduce((counts, candidate) => {
|
||||
counts[candidate.lane] = (counts[candidate.lane] || 0) + 1;
|
||||
return counts;
|
||||
}, {});
|
||||
const selected = candidates.slice(0, maxClosures);
|
||||
|
||||
core.info(`Dry run: ${dryRun}`);
|
||||
core.info(`Candidates: ${candidates.length}`);
|
||||
core.info(`Selected: ${selected.length}`);
|
||||
core.info(`Counts by lane: ${JSON.stringify(countsByLane)}`);
|
||||
core.info(`Skipped: ${JSON.stringify(skipped)}`);
|
||||
for (const candidate of selected) {
|
||||
core.info(`${dryRun ? "Would close" : "Closing"} ${candidate.lane} #${candidate.number}: ${candidate.title} (${candidate.url})`);
|
||||
}
|
||||
|
||||
await core.summary
|
||||
.addHeading("Stale Closure Backfill")
|
||||
.addRaw(`Dry run: ${dryRun}\n\n`)
|
||||
.addRaw(`Candidates: ${candidates.length}\n\n`)
|
||||
.addRaw(`Selected: ${selected.length}\n\n`)
|
||||
.addCodeBlock(JSON.stringify({ countsByLane, skipped }, null, 2), "json")
|
||||
.addTable([
|
||||
[
|
||||
{ data: "Lane", header: true },
|
||||
{ data: "Number", header: true },
|
||||
{ data: "Title", header: true },
|
||||
{ data: "URL", header: true },
|
||||
],
|
||||
...selected.map(candidate => [
|
||||
candidate.lane,
|
||||
String(candidate.number),
|
||||
candidate.title,
|
||||
candidate.url,
|
||||
]),
|
||||
])
|
||||
.write();
|
||||
|
||||
if (dryRun) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const candidate of selected) {
|
||||
await github.rest.issues.createComment({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: candidate.number,
|
||||
body: candidate.isPr ? prCloseMessage : issueCloseMessage,
|
||||
});
|
||||
|
||||
if (candidate.isPr) {
|
||||
await github.rest.pulls.update({
|
||||
owner,
|
||||
repo,
|
||||
pull_number: candidate.number,
|
||||
state: "closed",
|
||||
});
|
||||
} else {
|
||||
await github.rest.issues.update({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: candidate.number,
|
||||
state: "closed",
|
||||
state_reason: "not_planned",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
lock-closed-issues:
|
||||
if: ${{ github.event_name != 'workflow_dispatch' || inputs.backfill_stale_closures != true }}
|
||||
permissions:
|
||||
issues: write
|
||||
runs-on: blacksmith-16vcpu-ubuntu-2404
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -6,6 +6,7 @@ docker-compose.extra.yml
|
||||
docker-compose.sandbox.yml
|
||||
dist
|
||||
dist-runtime/
|
||||
dist-sea/
|
||||
pnpm-lock.yaml
|
||||
bun.lock
|
||||
bun.lockb
|
||||
@@ -103,6 +104,8 @@ USER.md
|
||||
.agents/skills/*
|
||||
!.agents/skills/blacksmith-testbox/
|
||||
!.agents/skills/blacksmith-testbox/**
|
||||
!.agents/skills/crabbox/
|
||||
!.agents/skills/crabbox/**
|
||||
!.agents/skills/gitcrawl/
|
||||
!.agents/skills/gitcrawl/**
|
||||
!.agents/skills/openclaw-ghsa-maintainer/
|
||||
@@ -187,6 +190,8 @@ changelog/fragments/
|
||||
test/fixtures/openclaw-vitest-unit-report.json
|
||||
analysis/
|
||||
.artifacts/qa-e2e/
|
||||
/runs/
|
||||
/data/rtt.jsonl
|
||||
extensions/qa-lab/web/dist/
|
||||
|
||||
# Generated bundled plugin runtime dependency manifests
|
||||
|
||||
@@ -143,7 +143,7 @@ Telegraph style. Root rules only. Read scoped `AGENTS.md` before subtree work.
|
||||
|
||||
- Docs change with behavior/API. Use docs list/read_when hints; docs links per `docs/AGENTS.md`.
|
||||
- Docs final answers: when doc files changed, end with the relevant full `https://docs.openclaw.ai/...` URL(s).
|
||||
- Changelog user-facing only; pure test/internal usually no entry.
|
||||
- Changelog user-facing only; fixing an issue or landing/merging a PR needs one unless pure test/internal.
|
||||
- Changelog placement: active version `### Changes`/`### Fixes`; every added entry must include at least one `Thanks @author` attribution, using credited GitHub username(s). Never add `Thanks @codex`, `Thanks @openclaw`, or `Thanks @steipete`.
|
||||
- Changelog bullets are always single-line. No wrapping/continuation across multiple lines. Long entries stay on one long line so dedupe, PR-ref, and credit-audit tooling work and so the visual style stays uniform.
|
||||
|
||||
|
||||
179
CHANGELOG.md
179
CHANGELOG.md
@@ -2,6 +2,159 @@
|
||||
|
||||
Docs: https://docs.openclaw.ai
|
||||
|
||||
## 2026.4.30
|
||||
|
||||
### Changes
|
||||
|
||||
- Dependencies: refresh bundled runtime and plugin dependency pins, including Pi 0.71.1, OpenAI 6.35.0, Codex 0.128.0, Zod 4.4.1, and Matrix 41.4.0. Thanks @mariozechner.
|
||||
- Agents/workspace: add `agents.defaults.skipOptionalBootstrapFiles` for skipping selected optional workspace files during bootstrap without disabling required workspace setup. (#62110) Thanks @mainstay22.
|
||||
- Plugins/CLI: add first-class `git:` plugin installs with ref checkout, commit metadata, normal scanner/staging, and `plugins update` support for recorded git sources. Thanks @badlogic.
|
||||
- Google Meet: add live caption health for Chrome transcribe mode, including caption observer state, transcript counters, last caption text, and recent transcript lines in status and doctor output. Refs #72478. Thanks @DougButdorf.
|
||||
- Voice Call/Google Meet: add Twilio Meet join phase logs around pre-connect DTMF, realtime stream setup, and initial greeting handoff for easier live-call debugging. Thanks @donkeykong91 and @PfanP.
|
||||
- Agents/Codex: default Codex-harness direct source replies to the OpenClaw `message` tool when visible reply delivery is not explicitly configured, keeping channel-visible output as a deliberate tool call. Thanks @pashpashpash.
|
||||
- macOS app: move recent session context rows into a Context submenu while keeping usage and cost details root-level, so the menu bar companion stays compact with many active sessions. Thanks @guti.
|
||||
- Gateway/SDK: add SDK-facing tools.invoke RPC with shared HTTP policy, typed approval/refusal results, and SDK helper support. Refs #74705. Thanks @BunsDev and @ai-hpc.
|
||||
- Discord: keep active buttons, selects, and forms working across Gateway restarts until they expire, so multi-step Discord interactions are less likely to break during upgrades or restarts. Thanks @amknight.
|
||||
- Messages/docs: clarify that `BodyForAgent` is the primary inbound model text while `Body` is the legacy envelope fallback, and add Signal coverage so channel hardening patches target the real prompt path. Refs #66198. Thanks @defonota3box.
|
||||
- Slack: publish a safe default App Home tab view on `app_home_opened` and include the Home tab event in setup manifests. Fixes #11655; refs #52020. Thanks @TinyTb.
|
||||
- Slack: keep track of bot-participated threads across restarts, so ongoing threaded conversations can continue auto-replying after the Gateway is restarted. Thanks @amknight.
|
||||
- Control UI/Usage: add UTC quarter-hour token buckets for the Usage Mosaic and reuse them for hour filtering, keeping the legacy session-span fallback for older summaries. (#74337) Thanks @konanok.
|
||||
- BlueBubbles: add opt-in `channels.bluebubbles.replyContextApiFallback` that fetches the original message from the BlueBubbles HTTP API when the in-memory reply-context cache misses (multi-instance deployments sharing one BB account, post-restart, after long-lived TTL/LRU eviction). Off by default; channel-level setting propagates to accounts that omit the flag through `mergeAccountConfig`; routed through the typed `BlueBubblesClient` so every fetch is SSRF-guarded by the same three-mode policy as every other BB client request; reply-id shape is validated and part-index prefixes (`p:0/<guid>`) are stripped before the request; concurrent webhooks for the same `replyToId` coalesce into one fetch and successful responses populate the reply cache for subsequent hits. Also promotes BlueBubbles attachment download failures from verbose to runtime error so silently-dropped inbound images are visible at default log level, and extends `sanitizeForLog` to redact `?password=…`/`?token=…` query params and `Authorization:` headers before they reach the log sink (CWE-532). (#71820) Thanks @coletebou and @zqchris.
|
||||
- CLI/proxy: add `openclaw proxy validate` so operators can verify effective proxy configuration, proxy reachability, and expected allow/deny destination behavior before deploying proxy-routed OpenClaw commands. (#73438) Thanks @jesse-merhi.
|
||||
- Agents/Codex: default Codex app-server dynamic tools to native-first, keeping OpenClaw integration tools while leaving file, patch, exec, and process ownership to the Codex harness. (#75308) Thanks @pashpashpash.
|
||||
|
||||
### Fixes
|
||||
|
||||
- fix: block workspace CLOUDSDK_PYTHON override and always set trusted interpreter for gcloud. (#74492) Thanks @pgondhi987.
|
||||
- Providers/Z.AI: move the bundled GLM catalog and auth env metadata into the plugin manifest, so `models list --all --provider zai` shows the full known catalog without duplicated runtime seed data. Thanks @shakkernerd.
|
||||
- Providers/Qianfan and Providers/Stepfun: declare setup auth metadata (`api-key` method, `QIANFAN_API_KEY`, `STEPFUN_API_KEY`) in the plugin manifest so onboarding and `models setup` surface the expected env var without falling back to legacy `providerAuthEnvVars` runtime seed data. Thanks @shakkernerd.
|
||||
- fix(infra): block ambient Homebrew env vars from brew resolution. (#74463) Thanks @pgondhi987.
|
||||
- Onboarding/configure: avoid staging every default plugin runtime dependency after config writes, so skipped setup flows only prepare config-selected plugin deps instead of pulling broad feature-plugin packages. Thanks @vincentkoc.
|
||||
- Thinking/providers: resolve bundled provider thinking profiles through lightweight provider policy artifacts when startup-lazy providers are not active, so OpenAI Codex GPT-5.x keeps xhigh available in Gateway session validation. Fixes #74796. Thanks @maxschachere.
|
||||
- Security/Windows: ignore workspace `.env` system-path variables and resolve stale-process `taskkill.exe` from the validated Windows install root, preventing repository-local env files from redirecting cleanup helpers. Thanks @pgondhi987.
|
||||
- CLI/plugins: refresh persisted plugin registry policy in place for `plugins enable` and `plugins disable`, so routine toggles no longer rebuild and hash every plugin source when the target is already indexed. Thanks @vincentkoc.
|
||||
- CLI/plugins: scope install and enable slot selection to the selected plugin manifest/runtime fallback, so plugin installs no longer load every plugin runtime or broad status snapshot just to update memory/context slots. Thanks @vincentkoc.
|
||||
- Plugins/TTS: keep bundled speech-provider discovery available on cold package Gateway paths and add bundled plugin matrix runtime probes for health, readiness, RPC, TTS discovery, and post-ready runtime-deps watchdog coverage. Refs #75283. Thanks @vincentkoc.
|
||||
- Google Meet/Twilio: show delegated voice call ID, DTMF, and intro-greeting state in `googlemeet doctor`, and avoid claiming DTMF was sent when no Meet PIN sequence was configured. Refs #72478. Thanks @DougButdorf.
|
||||
- Plugins/tools: prefer built bundled plugin code during tool discovery and skip channel runtime hydration while preserving companion provider registrations, reducing per-run plugin-tool prep cost without dropping executable plugin tools. Fixes #75290. Thanks @thanos-openclaw.
|
||||
- Voice Call/Twilio: send notify-mode initial TwiML directly in the outbound create-call request while keeping conversation and pre-connect DTMF calls webhook-driven, so one-shot notify calls do not depend on a first-answer webhook fetch. Supersedes #72758. Thanks @tyshepps.
|
||||
- Discord/Slack: defer status-reaction cleanup until run finalization so queued, thinking, tool, and terminal reactions no longer flicker during normal progress updates. (#75582)
|
||||
- Discord/voice: leave Discord voice off for text-only configs unless `channels.discord.voice` is explicitly configured, avoiding default `GuildVoiceStates` traffic and idle gateway CPU pressure for bots that do not use `/vc`. Fixes #73753; refs #74044. Thanks @sanchezm86 and @SecureCloudProjO.
|
||||
- Discord/voice: rerun configured voice auto-join after Discord gateway RESUMED events and ignore already-destroyed stale voice connections during reconnect cleanup, so health-monitor account restarts can rejoin configured channels. Fixes #40665. Thanks @liz709.
|
||||
- Plugins/CLI: reuse the cold manifest registry while building plugin status and inspect reports, so large configured plugin sets no longer rediscover the bundled/plugin registry once per inspect row. Thanks @vincentkoc.
|
||||
- Discord/voice: lengthen the default voice join Ready wait, add configurable `voice.connectTimeoutMs`/`voice.reconnectGraceMs`, and warn before destroying unrecovered disconnected sessions so slow Discord voice handshakes and reconnects no longer fail silently. Fixes #63098; refs #39825 and #65039. Thanks @darealgege, @kzicherman, and @ayochim.
|
||||
- Gateway/health: refresh cached health RPC snapshots when channel runtime state diverges, so Discord and other channel status reads no longer report stale running or connected values until the cache TTL expires. (#75423) Thanks @clawsweeper.
|
||||
- Gateway/sessions: keep session-store reads from running stale prune and entry-count cap maintenance during startup, so oversized stores no longer block chat history readiness after updates while writes and `sessions cleanup --enforce` still preserve the cleanup safeguards. Fixes #70050. Thanks @tangda18.
|
||||
- Security/audit: keep plain `security audit` on the cold config/filesystem path and reserve plugin runtime security collectors for `--deep`, so large plugin installs cannot execute every plugin runtime during routine audits. Thanks @vincentkoc.
|
||||
- Discord/voice: merge configured media-understanding providers such as Deepgram into partial active provider registries, so follow-up voice turns keep transcribing after another media plugin is already active. Fixes #65687. Thanks @OneMintJulep.
|
||||
- WhatsApp: stage `qrcode` through root mirrored runtime dependencies so packaged QR pairing can render from staged plugin-runtime-deps installs. Fixes #75394. Thanks @FelipeX2001.
|
||||
- Discord/voice: apply per-channel Discord `systemPrompt` overrides to voice transcript turns by forwarding the trusted channel prompt through the voice agent run. Fixes #47095. Thanks @qearlyao.
|
||||
- Discord/native commands: send component-only interaction replies from slash command and status handlers instead of treating renderable Discord components as an empty response. Thanks @vincentkoc.
|
||||
- Slack/slash commands: send block-only slash command replies instead of dropping Slack block payloads with no plain-text fallback. Thanks @vincentkoc.
|
||||
- Telegram/messages: derive fallback text from interactive button/select labels before sending button-only payloads, so Telegram replies are not rejected as empty messages. Thanks @vincentkoc.
|
||||
- LINE/messages: send quick-reply-only payloads with fallback option text instead of accepting the payload and returning an empty delivery. Thanks @vincentkoc.
|
||||
- Auto-reply/docking: require `/dock-*` route switches to start from direct chats, so group or channel participants cannot reroute a shared session's future replies into a linked DM. Thanks @vincentkoc.
|
||||
- Discord: keep text-DM main-session route updates pinned to the configured DM owner, matching component interactions so another direct-message sender cannot redirect future main-session replies. Thanks @vincentkoc.
|
||||
- Mattermost/Matrix: keep direct-message main-session route updates pinned to the configured DM owner so paired or temporarily allowed senders cannot redirect future shared-session replies. Thanks @vincentkoc.
|
||||
- Discord: keep SecretRef-backed bot tokens discoverable for message actions without resolving the token during schema generation, and resolve scoped channel SecretRefs before outbound agent message sends even when the tool is built from a config snapshot. Fixes #75324. Thanks @slideshow-dingo and @Conan-Scott.
|
||||
- Updates: run package post-install doctor repair with the managed Gateway service profile and state paths when a daemon is installed, so shell/profile mismatches no longer repair the caller state while the restarted Gateway keeps stale config. Thanks @vincentkoc.
|
||||
- Models/DeepInfra: declare DeepInfra manifest catalog discovery and derive its runtime fallback catalog from the manifest, restoring provider-filtered `models list --all --provider deepinfra` rows without duplicated static model data. Thanks @shakkernerd.
|
||||
- CLI/update: verify managed gateway restarts against the installed service port instead of the caller shell port, so package updates do not report a healthy daemon as failed when profiles use different gateway ports. Thanks @vincentkoc.
|
||||
- Gateway/agent: reject strict `openclaw agent --deliver` requests with missing delivery targets before starting the agent run, so users do not wait for a completed turn that cannot send anywhere. Thanks @vincentkoc.
|
||||
- Setup/import: honor non-interactive `--import-from` onboarding flags by running the migration import path instead of silently completing normal setup without importing anything. Thanks @vincentkoc.
|
||||
- Discord/voice: run voice-channel turns under a voice-output policy that hides the agent `tts` tool and asks for spoken reply text, so `/vc join` sessions synthesize and play agent replies instead of ending with `NO_REPLY`. Fixes #61536. Thanks @aounakram.
|
||||
- Doctor/plugins: keep plain `doctor --non-interactive` from installing bundled plugin runtime dependencies, so headless health checks report missing deps while `doctor --fix` remains the explicit repair path. Thanks @vincentkoc.
|
||||
- Doctor/gateway: require an interactive confirmation before installing or rewriting the Gateway service, so `doctor --fix --non-interactive` can repair plugin/config drift without replacing the operator's launchd/systemd service from a temporary environment. Thanks @vincentkoc.
|
||||
- Plugins/runtime-deps: include packaged OpenClaw identity in bundled plugin loader cache keys, so same-path package upgrades stop reusing stale versioned runtime-deps mirrors. Fixes #75045. Thanks @sahilsatralkar.
|
||||
- Plugin SDK: restore reply-prefix and reply-pipeline helpers on the deprecated root/compat SDK surface so external plugins still using `openclaw/plugin-sdk` do not fail message dispatch after update. Fixes #75171. Thanks @zhangxiliang.
|
||||
- Plugins/runtime-deps: prune inactive same-package versioned runtime-deps roots after bundled dependency repair, so upgrades do not leave old `openclaw-<version>-<hash>` package caches behind after doctor runs. Thanks @vincentkoc.
|
||||
- Plugins/runtime-deps: prune legacy version-scoped plugin runtime-deps roots during bundled dependency repair and cover the path in Package Acceptance's upgrade-survivor matrix, so upgrades from 2026.4.x no longer leave stale per-plugin runtime trees after doctor runs. Thanks @vincentkoc.
|
||||
- Plugins/runtime-deps: keep Gateway startup plugin imports and runtime plugin fallback loads verify-only after startup/config repair planning, so packaged installs no longer spawn package-manager repair from hot paths after readiness. Refs #75283 and #75069. Thanks @brokemac79 and @xiaohuaxi.
|
||||
- Plugins/runtime-deps: treat package.json runtime-deps manifests as supersets when generated materialization metadata is absent, so bundled plugin activation stops restaging already-installed dependency subsets on every activation. Fixes #75429. (#75431) Thanks @loyur.
|
||||
- iMessage: add stdin write callback and error listener to IMessageRpcClient so async EPIPE from a closed child process rejects the pending request instead of crashing the gateway with uncaughtException. Fixes #75438.
|
||||
- MCP/stdio: settle MCP stdio transport send() from the write callback instead of resolving immediately on buffer acceptance, so async write errors reject the promise instead of being lost. Refs #75438.
|
||||
- Process/exec: add stdin error listener in runCommandWithTimeout so EPIPE from a prematurely-exited child is swallowed instead of escaping to uncaughtException. Refs #75438.
|
||||
- Voice Call/realtime: add default-off fast memory/session context for `openclaw_agent_consult`, giving live calls a bounded answer-or-miss path before the full agent consult. Fixes #71849. Thanks @amzzzzzzz.
|
||||
|
||||
- Google Meet: interrupt Realtime provider output when local barge-in clears playback, so command-pair audio stops model speech instead of only restarting Chrome playback. Fixes #73850. (#73834) Thanks @shhtheonlyperson.
|
||||
- Gateway/config: cap oversized plugin-owned schemas in the full `config.schema` response so large installed plugin sets cannot balloon Gateway RSS or crash schema clients. Thanks @vincentkoc.
|
||||
- Plugins/update: skip ClawHub and marketplace plugin updates when the bundled version is newer than the recorded installed version, so `openclaw update` no longer overwrites working bundled plugins with older external packages. Fixes #75447. Thanks @amknight.
|
||||
- Gateway/sessions: use bounded tail reads for sessions-list transcript usage fallbacks and cap bulk title/last-message hydration, keeping large session stores responsive when rows request derived previews. Thanks @vincentkoc.
|
||||
- Gateway/sessions: yield during bulk transcript title/preview hydration and copy compaction checkpoints asynchronously, keeping the Gateway event loop responsive for large session stores and large transcripts. Refs #75330 and #75414. Thanks @amknight.
|
||||
- Gateway/sessions: stream bounded transcript reads for session detail, history, artifacts, compaction, and send/subscribe sequence paths so small Gateway requests no longer materialize large transcripts or OOM on oversized session logs. Thanks @vincentkoc.
|
||||
- Gateway/chat: bound chat-history transcript reads to the requested display window so large session logs no longer OOM the Gateway when clients ask for a small history page. Thanks @vincentkoc.
|
||||
- Voice Call/Twilio: honor stored pre-connect TwiML before realtime webhook shortcuts and reject DTMF sequences outside conversation mode, so Meet PIN entry cannot be skipped or silently dropped. Thanks @donkeykong91 and @PfanP.
|
||||
- Docs/sandboxing: clarify that sandbox setup scripts (`sandbox-setup.sh`, `sandbox-common-setup.sh`, `sandbox-browser-setup.sh`) are only available from a source checkout, and add inline `docker build` commands for npm-installed users so sandbox image setup works without cloning the repo. Fixes #75485. Thanks @amknight.
|
||||
- Google Meet/Voice Call: play Twilio Meet DTMF before opening the realtime media stream and carry the intro as the initial Voice Call message, so the greeting is generated after Meet admits the phone participant instead of racing a live-call TwiML update. Thanks @donkeykong91 and @PfanP.
|
||||
- Google Meet/Voice Call: make Twilio setup preflight honor explicit `--transport twilio` and fail local/private Voice Call webhook URLs, including IPv6 loopback and unique-local forms, before joins. Thanks @donkeykong91 and @PfanP.
|
||||
- Voice Call/Twilio: retry transient 21220 live-call TwiML updates and catch answered-path initial-greeting failures, so a fast answered callback no longer crashes the Gateway or drops the Twilio greeting/listen transition. (#74606) Thanks @Sivan22.
|
||||
- CLI/startup: preserve `OPENCLAW_HIDE_BANNER` banner suppression for route-first startup callers that rely on the default process environment while keeping read-only status/channel paths from repairing bundled plugin runtime dependencies. Refs #75183.
|
||||
- Voice Call/Twilio: register accepted media streams immediately but wait for realtime transcription readiness before speaking the initial greeting, so reconnect grace handling stays live while OpenAI STT startup is no longer starved by TTS. Fixes #75197. (#75257) Thanks @donkeykong91 and @PfanP.
|
||||
- Voice Call CLI: run gateway-delegated `voicecall continue` through operation-id polling and protocol-shaped errors, so long conversational turns keep their transcript result without blocking a single Gateway RPC. (#75459) Thanks @serrurco and @DougButdorf.
|
||||
- Voice Call CLI: delegate operational `voicecall` commands to the running Gateway runtime and skip webhook startup during CLI-only plugin loading, preventing webhook port conflicts and `setup --json` hangs. Fixes #72345. Thanks @serrurco and @DougButdorf.
|
||||
- Agents/pi-embedded-runner: extract the `abortable` provider-call wrapper from `runEmbeddedAttempt` to module scope so its promise handlers no longer close over the run lexical context, releasing transcripts, tool buffers, and subscription callbacks when a provider call hangs past abort. (#74182) Thanks @cjboy007.
|
||||
- Docker: restore `python3` in the gateway runtime image after the slim-runtime switch. Fixes #75041.
|
||||
- Agents/session-repair: fix resumed sessions failing with repeated 400 errors on Anthropic and strict OpenAI-compatible providers (Qwen, mlx-vlm) after an interrupted conversation or blank user input. Fixes #75271 and #75313. Thanks @amknight.
|
||||
- CLI/Voice Call: scope `voicecall` command activation to the Voice Call plugin so setup and smoke checks no longer broad-load unrelated plugin runtimes or hang after printing JSON. Thanks @vincentkoc.
|
||||
- Doctor/plugins: warn when restrictive `plugins.allow` is paired with wildcard or plugin-owned tool allowlists, making the exclusive plugin allowlist behavior visible before users hit empty callable-tool runs. Refs #58009 and #64982. Thanks @KR-Python and @BKF-Gitty.
|
||||
- Google Meet/Voice Call: keep Twilio Meet joins in conversation mode and reuse the realtime intro prompt when no voice-call-specific intro is configured, so answered phone bridge calls speak instead of joining silently. Refs #72478. Thanks @DougButdorf.
|
||||
- Auto-reply/group chats: keep the `message` tool available for message-tool-only visible replies and apply group-scoped tool policy before deciding fallback delivery, so Discord/Slack-style rooms reply visibly in the correct channel after upgrades. Fixes #74842; refs #75207. Thanks @davelutztx and @aa-on-ai.
|
||||
- Agents/commitments: keep inferred follow-ups internal when heartbeat target is none, strip raw source text from stored commitments, disable tools during due-commitment heartbeat turns, bound hidden extraction queue growth, expire stale commitments, and add QA/Docker safety coverage. Thanks @vignesh07.
|
||||
- Telegram/agents: keep typing indicators and optional generation tools off the reply critical path, so fresh Telegram replies no longer stall while provider catalogs and media models load. (#75360) Thanks @obviyus.
|
||||
- Agents/commitments: run hidden follow-up extraction on the configured agent/default model instead of falling back to direct OpenAI, so OpenAI Codex OAuth-only gateways no longer spam background API-key failures. Fixes #75334. Thanks @sene1337.
|
||||
- Agents/media: keep async music generation completions on the requester-session wake path even when direct-send completion is enabled, so finished audio stays agent-mediated while video can still opt into direct channel delivery. (#75335) Thanks @vincentkoc.
|
||||
- Security/config-audit: redact CLI argv and execArgv secrets before persisting config audit records, covering write, observe, and recovery paths. Fixes #60826. Thanks @koshaji.
|
||||
- Gateway/models: keep default and configured model-list views responsive when provider catalog discovery stalls, without hiding real catalog load failures, while `--all` still waits for the exact full catalog. Fixes #75297; refs #74404. Thanks @lisandromachado and @najef1979-code.
|
||||
- Plugins/runtime-deps: accept already materialized package-level runtime-deps supersets as converged, so later lazy plugin activation no longer prunes and relaunches `pnpm install` after gateway startup pre-staging, reducing event-loop pressure from repeated runtime-deps repair on packaged installs. Fixes #75283; refs #75297 and #72338. Thanks @brokemac79, @lisandromachado, and @midhunmonachan.
|
||||
- Plugins/runtime-deps: remove OpenClaw-owned legacy runtime-deps symlinks before replacing staged bundled plugin dependencies, so updates can recover from older symlinked installs instead of failing the symlink safety guard. Thanks @goldmar.
|
||||
- Discord: retry queued REST 429s against learned bucket/global cooldowns and reacquire fresh voice upload URLs after CDN upload rate limits, so outbound sends recover without reusing stale single-use upload URLs. Thanks @discord.
|
||||
- TTS/providers: keep bundled speech-provider compat fallback available when plugins are globally disabled, so cold gateway and CLI startup can still resolve fallback speech providers instead of leaving explicit TTS provider selection with no registered providers. Refs #75265. Thanks @sliekens.
|
||||
- Discord: collapse repeated native slash-command deploy rate-limit startup logs into one non-fatal warning while keeping per-request REST timing in verbose output. Thanks @discord.
|
||||
- Discord: report native slash-command deploy aborts as REST timeouts with method, path, timeout budget, and observed duration, so startup logs explain slow Discord API calls instead of showing a generic aborted operation. Thanks @discord.
|
||||
- Security/logging: redact payment credential field names such as card number, CVC/CVV, shared payment token, and payment credential across default log and tool-payload redaction patterns so wallet-style MCP tools do not expose raw payment credentials in UI events or transcripts. Thanks @stainlu.
|
||||
- Providers/OpenAI Codex: preserve existing wrapped Codex streams during OpenAI attribution so PI OAuth bearer injection reaches ChatGPT/Codex Responses, and strip native Codex-only unsupported payload fields without touching custom compatible endpoints. (#75111) Thanks @keshavbotagent.
|
||||
- Plugins/runtime-deps: materialize newly required bundled plugin packages after local `openclaw onboard` and `openclaw configure` config writes, while keeping remote setup read-only, so first Gateway startup no longer discovers missing channel/provider deps after setup claimed success. Fixes #75309; refs #75069. Thanks @scottgl9 and @xiaohuaxi.
|
||||
- Plugins/runtime-deps: expire stale legacy install locks whose live PID cannot be tied to the current process incarnation, so Docker PID reuse no longer leaves bundled dependency repair stuck behind old `.openclaw-runtime-deps.lock` directories. Fixes #74948; refs #74950 and #74346. Thanks @dchekmarev.
|
||||
- Plugins/runtime-deps: recover interrupted bundled runtime-dependency installs whose package sentinels exist but generated materialization is incomplete, forcing npm/pnpm repair in Gateway startup, doctor, and lazy plugin loads instead of leaving channels crash-looping on missing packages. Fixes #75309; refs #75310, #75296, and #75304. Thanks @scottgl9.
|
||||
- Plugins/runtime-deps: treat no-main and export-map package sentinels without reachable entry files as incomplete, so Gateway startup, doctor, and lazy plugin loads repair interrupted bundled dependency installs instead of accepting package.json-only partial installs. Fixes #75309; refs #75183. Thanks @shakkernerd.
|
||||
- Plugins/runtime-deps: keep runtime inspection and channel maintenance commands from downloading bundled plugin dependencies, route explicit repairs through `openclaw plugins deps --repair`, and still allow Gateway/DO paths to repair missing deps before import. Refs #75069. Thanks @xiaohuaxi.
|
||||
- Updates: force non-deferred, no-cooldown update restarts after package-manager updates requested through the live Gateway control plane and fail release validation on post-swap stale chunk import crashes, so Telegram/Discord imports do not stay pointed at removed dist files. Fixes #75206. Thanks @xonaman and @faux123.
|
||||
- Agents/tool-result guard: use the resolved runtime context token budget for non-context-engine tool-result overflow checks, so long tool-heavy sessions no longer compact early when `contextTokens` is larger than native `contextWindow`. Fixes #74917. Thanks @kAIborg24.
|
||||
- Gateway/systemd: exit with sysexits 78 for supervised lock and `EADDRINUSE` conflicts so `RestartPreventExitStatus=78` stops `Restart=always` restart loops instead of repeatedly reloading plugins against an occupied port. Fixes #75115. Thanks @yhyatt.
|
||||
- Agents/runtime: skip blank visible user prompts at the embedded-runner boundary before provider submission while still allowing internal runtime-only turns and media-only prompts, so Telegram/group sessions no longer leak raw empty-input provider errors when replay history exists. Fixes #74137. Thanks @yelog, @Gracker, and @nhaener.
|
||||
- Agents/Codex: isolate local Codex app-server `CODEX_HOME` and `HOME` per agent and add a deliberate Codex migration path with selectable skill copies, so personal Codex CLI skills, plugins, config, and hooks no longer leak into OpenClaw agents unless the operator migrates them into the workspace. Thanks @pashpashpash.
|
||||
- Security/Nextcloud Talk: make webhook signature validation use the padded timing-safe compare path even when the supplied signature length is wrong, keep normalized header lookup behavior, and extend regression coverage for tampered bodies, wrong secrets, array-backed headers, and truncated signatures. Carries forward earlier contributor work from #50516 by teddytennant. (#58097) Thanks @gavyngong.
|
||||
- Plugins/runtime-deps: replace stale symlinked mirror target roots before writing runtime-mirror temp files and skip rewriting already materialized hardlinks, so cross-version container upgrades no longer crash-loop on read-only image-layer paths while warm mirrors do less churn. Fixes #75108; refs #75069. Thanks @coletebou and @xiaohuaxi.
|
||||
- Auto-reply/group chats: fall back to automatic source delivery when a channel precomputes message-tool-only replies but the `message` tool is unavailable, so Discord/Slack-style group turns do not silently complete without a visible reply. Fixes #74868. Thanks @kagura-agent.
|
||||
- Browser/gateway: share one browser control runtime across the HTTP control server and `browser.request`, and refresh browser profile config from the source snapshot, so CLI status/start honors configured `browser.executablePath`, `headless`, and `noSandbox` instead of falling back to stale auto-detection. Fixes #75087; repairs #73617. Thanks @civiltox and @martingarramon.
|
||||
- Agents/subagents: bound automatic orphan recovery with persisted recovery attempts and a wedged-session tombstone, and teach task maintenance/doctor to reconcile those sessions so restart loops no longer require manual `sessions.json` surgery. Fixes #74864. Thanks @solosage1.
|
||||
- Plugins/runtime-deps: keep bundled provider policy config loading from staging plugin runtime dependencies, so config reads no longer fail on locked-down `/var/lib/openclaw/plugin-runtime-deps` directories. Fixes #74971. Thanks @eurojojo.
|
||||
- Memory/runtime-deps: retain the native `node-llama-cpp` runtime only when local memory search is configured, so packaged installs can repair local embeddings without relying on unreachable global npm installs. Fixes #74777. Thanks @LLagoon3.
|
||||
- Gateway/startup: skip pre-bind web-fetch provider discovery for credential-free `tools.web.fetch` config, so Docker/Kubernetes gateways bind even when optional fetch limits are present. Fixes #74896. Thanks @KoykL.
|
||||
- Signal: match group allowlists against inbound Signal group ids as well as sender ids, and process explicitly configured Signal groups without requiring mentions unless `requireMention` is set. Fixes #53308. Thanks @minupla and @juan-flores077.
|
||||
- Signal: bound `signal-cli` installer release and archive downloads with explicit timeouts, declared and streamed size checks, and partial-file cleanup. Fixes #54153. Thanks @jinduwang1001-max and @juan-flores077.
|
||||
- Slack: require bot-authored room messages with `allowBots=true` to come from an explicitly channel-allowlisted bot or from a room where an explicit Slack owner is present, so broad bot relays cannot run unattended. Fixes #59284. Thanks @andrewhong-translucent.
|
||||
- Signal: derive `getAttachment` HTTP response caps from `channels.signal.mediaMaxMb` with base64 headroom, so inbound photos and videos no longer drop behind the 1 MiB RPC default. Fixes #73564. Thanks @heyhudson.
|
||||
- Signal: keep the long-lived receive SSE monitor open while idle instead of applying the 10s RPC/check deadline, so `signal-cli` 0.14.3 event streams no longer reconnect before inbound messages arrive. Fixes #74741. Thanks @fgabelmannjr and @k7n4n5t3w4rt.
|
||||
- CLI/progress: suppress nested progress spinners and line clears while TUI input owns raw stdin, so Crestodian `/status` no longer disturbs the active input row. (#75003) Thanks @velvet-shark.
|
||||
- Models/OpenAI Codex: restore `openai-codex/gpt-5.4-mini` for ChatGPT/Codex OAuth PI runs after live OAuth proof, and align the manifest, forward-compat metadata, docs, and regression tests so stale cron and heartbeat configs resolve again. Fixes #74451. Thanks @0xCyda, @hclsys, and @Marvae.
|
||||
- Plugins/runtime-deps: always write a dependency map in generated runtime-deps install manifests, so npm does not crash or prune staged bundled-plugin packages when the plan is empty. Fixes #74949. Thanks @hclsys.
|
||||
- Telegram: use durable message edits for streaming previews instead of native draft state, so generated replies no longer flicker through draft-to-message transitions that look like duplicates. (#75073) Thanks @obviyus.
|
||||
- Telegram: echo preflighted DM voice-note transcripts back to the originating chat, including Telegram DM topic thread metadata, instead of only echoing later media-understanding transcripts. Fixes #75084. Thanks @M-Lietz.
|
||||
- Telegram: clamp low long-polling client timeouts so configured `timeoutSeconds` values below the `getUpdates` poll window no longer force a fresh HTTPS connection every few seconds. Fixes #75114. Thanks @hpinho77.
|
||||
- Web search: describe `web_search` as using the configured provider instead of hard-coding Brave when DuckDuckGo or another provider is active. Fixes #75088. Thanks @sun-rongyang.
|
||||
- Infra/tmp: tolerate concurrent temp-dir permission repairs by rechecking directories that another process already tightened, so parallel ACP subprocess startup no longer throws `Unsafe fallback OpenClaw temp dir`. Fixes #66867. Thanks @Kane808-AI and @jarvisz8.
|
||||
- Agents/compaction: add an opt-in `agents.defaults.compaction.midTurnPrecheck` mid-turn precheck that detects tool-loop context pressure and triggers compaction before the next tool call instead of waiting for end-of-turn. (#73499) Thanks @marchpure and @haoxingjun.
|
||||
- Gateway/approvals: let loopback token/password-backed native approval clients resolve exec approvals without attaching stale paired Gateway identities, while remote and unauthenticated approval clients keep normal device identity behavior. (#74472)
|
||||
- Gateway/config: include rejected validation paths in foreground and service last-known-good recovery logs plus main-agent notices, so unsupported direct edits explain which key caused restore instead of looking like silent reversion. Fixes #75060. Thanks @amknight.
|
||||
- Plugins/runtime-deps: hash the OS-canonical `packageRoot` via `fs.realpathSync.native` (with `path.resolve` fallback) when computing the bundled runtime-deps stage key, so loader and channel `bundled-root` callers no longer derive divergent stage directories under `~/.openclaw/plugin-runtime-deps/openclaw-<version>-<hash>/` and bundled channels stop failing with `ENOENT` on shared dist chunks under Windows npm symlinks, junctions, or PM2 multi-instance worker layouts. Fixes #74963. (#75048) Thanks @openperf and @vincentkoc.
|
||||
- fix(logging): add redaction patterns for Tencent Cloud, Alibaba Cloud, HuggingFace and Replicate API keys (#58162). Thanks @gavyngong
|
||||
- Pairing: surface unexpected allowlist filesystem stat errors instead of treating the allowlist as missing, so permission and I/O failures are visible during pairing authorization checks. (#63324) Thanks @franciscomaestre.
|
||||
- macOS app: reserve layout space for exec approval command details so the allow dialog no longer overlaps the command, context, and action buttons. (#75470) Thanks @ngutman.
|
||||
- Agents/failover: carry `sessionId`, `lane`, `provider`, `model`, and `profileId` attribution through `FailoverError` and `describeFailoverError`/`coerceToFailoverError` so structured error logs (e.g. `gateway.err.log` ingestion) can attribute exhausted-fallback wrapper errors to the originating session and last-attempted provider instead of dropping the metadata after the per-profile errors. Fixes #42713. (#73506) Thanks @wenxu007.
|
||||
- Context Engine: treat assembled prompt as the default authority for preemptive overflow prechecks so engines that return a windowed, self-contained context no longer trigger false hard-fail compactions on huge raw history. Engines whose assembled view can hide overflow risk can opt back into the legacy behavior with `AssembleResult.promptAuthority: "preassembly_may_overflow"`. (#74255) Thanks @100yenadmin.
|
||||
- Mattermost: refresh current native slash command registrations before accepting callbacks so stale tokens from deleted or regenerated commands stop being accepted without a gateway restart while failed validations stay briefly cached and lookup starts are rate-limited per command, gate each callback against the resolved command's own startup token so a token leaked for one slash command cannot poison another command's failure cache, redact slash validation lookup errors, and add a body read timeout to the multi-account routing path so slow callback senders cannot tie up the dispatcher. Thanks @feynman-hou and @eleqtrizit.
|
||||
|
||||
## 2026.4.29
|
||||
|
||||
### Highlights
|
||||
@@ -15,6 +168,8 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Changes
|
||||
|
||||
- Security/tools: configured tool sections (`tools.exec`, `tools.fs`) no longer implicitly widen restrictive profiles (`messaging`, `minimal`). Users who need those tools under a restricted profile must add explicit `alsoAllow` entries; a startup warning identifies affected configs. Fixes #47487. Thanks @amknight.
|
||||
- Gateway/SDK: add SDK-facing artifact list/get/download RPCs and App SDK helpers with transcript provenance and download-source guardrails. Refs #74706. Thanks @tmimmanuel.
|
||||
- Agents/commitments: add opt-in inferred follow-up commitments with hidden batched extraction, per-agent/per-channel scoping, heartbeat delivery, CLI management, a simple `commitments.enabled`/`commitments.maxPerDay` config, and heartbeat-interval due-time clamping so magical check-ins do not echo immediately. (#74189) Thanks @vignesh07.
|
||||
- Messages/queue: make `steer` drain all pending Pi steering messages at the next model boundary, keep legacy one-at-a-time steering as `queue`, and add a dedicated steering queue docs page. Thanks @vincentkoc.
|
||||
- Messages/queue: default active-run queueing to `steer` with a 500ms followup fallback debounce, and document the queue modes, precedence, and drop policies on the command queue page. Thanks @vincentkoc.
|
||||
@@ -34,16 +189,25 @@ Docs: https://docs.openclaw.ai
|
||||
- Gateway/diagnostics: emit an opt-in startup diagnostics timeline that records gateway lifecycle and plugin-load phases behind a config flag, so slow-start diagnosis no longer requires bespoke instrumentation. Thanks @shakkernerd.
|
||||
- Control UI/i18n: extend the locale registry with new Persian (fa), Dutch (nl), Vietnamese (vi), Italian (it), Arabic (ar), and Thai (th) entries and ship `fa`, `nl`, `vi`, and `zh-TW` docs glossaries, so the docs translation pipeline and the Control UI language picker stay aligned across surfaces. Thanks @vincentkoc.
|
||||
- Channels: add Yuanbao channel docs entrance so the Tencent Yuanbao bot appears in the channel listing and sidebar navigation. (#73443) Thanks @loongfay.
|
||||
- Channels/Yuanbao: update plugin GitHub location to YuanbaoTeam/yuanbao-openclaw-plugin and add "yuanbao" alias to channel catalog. (#74253) Thanks @loongfay.
|
||||
- Docker setup: add `OPENCLAW_SKIP_ONBOARDING` so automated Docker installs can skip the interactive onboarding step while still applying gateway defaults. (#55518) Thanks @jinjimz.
|
||||
- Security policy: classify media/base64 decode and format-conversion overhead after configured acceptance limits as performance-only for GHSA triage unless a report demonstrates a limit bypass, crash, exhaustion, data exposure, or another boundary bypass. (#74311)
|
||||
- Security/OpenGrep: add a precise OpenGrep rulepack, source-rule compiler, provenance metadata check, and PR/full scan workflows that validate first-party code and rulepack-only changes while uploading SARIF to GitHub Code Scanning. (#69483) Thanks @jesse-merhi.
|
||||
|
||||
### Fixes
|
||||
|
||||
- Voice Call: resolve SecretRef-backed Twilio auth tokens and realtime/streaming provider API keys before initializing call providers, so SecretRef-backed voice-call credentials reach runtime as strings. (#73632) Thanks @VACInc.
|
||||
- Security/outbound: strip re-formed HTML tags during plain-text sanitization so nested tag fragments cannot leave a CodeQL-detected `<script>` sequence behind. Thanks @vincentkoc.
|
||||
- Security/secrets: compare credential bytes with padded timing-safe buffers instead of hashing candidate passwords before equality checks. Thanks @vincentkoc.
|
||||
- Security/QQBot: sanitize debug log arguments before writing to `console.*`, so gateway payload fields cannot forge extra log lines when debug logging is enabled. Thanks @vincentkoc.
|
||||
- QQBot: unify slash command auth and c2cOnly gating in the command registry, pass `allowQQBotDataDownloads` when sending slash command file attachments, align clear-storage with actual downloads directory, and add `/bot-me` to display sender user ID. (#73616) Thanks @cxyhhhhh.
|
||||
- CLI/agents/status: keep `openclaw agents`, text `agents list`, and plain text `status` on read-only metadata paths so human output no longer preloads plugin runtimes or live channel scans before printing. Fixes #74195. Thanks @NianJiuZst.
|
||||
- Agents/local models: derive context-window guard thresholds from the effective model window with 4k/8k safety floors, so small local models are no longer rejected by fixed 16k/32k preflight cutoffs. Fixes #42999. Thanks @chengjialu8888.
|
||||
- PDF extraction: resolve PDF.js standard fonts from the installed package root and pass a filesystem path to the Node fallback extractor, so built-in font PDFs render without `file://` URL lookup failures. Fixes #51455; carries forward #70936, #54447, and #62175. Thanks @anyech, @JuanRdBO, and @solomonneas.
|
||||
- Media: treat legacy Word/OLE attachments with `application/msword` or `application/x-cfb` MIME as binary so printable-looking `.doc` files are not embedded into prompts as text. Fixes #54176; carries forward #54380. Thanks @andyliu.
|
||||
- Config: accept documented `browser.tabCleanup` keys in strict root config validation, so configured tab cleanup no longer fails before runtime reads it. Fixes #74577. Thanks @lonexreb and @ezdlp.
|
||||
- Cron: validate disabled job schedule edits before persisting updates, so invalid cron changes no longer partially mutate stored jobs. Fixes #74459. Thanks @yfge.
|
||||
- CLI/cron: warn when `openclaw cron add --message` omits a nonblank `--agent`, including blank agent values and session-key jobs, so scheduled agent-turn jobs make default-agent fallback explicit while system events stay quiet. Fixes #42196; carries forward #42245. Thanks @ethanclaw.
|
||||
- Channels/status: keep Telegram, Slack, and Google Chat read-only allowlist/default-target accessors on config-only paths, so status and channel summaries do not resolve SecretRef-backed runtime credentials. Thanks @eusine.
|
||||
- Active Memory: clarify the deprecated `modelFallbackPolicy` warning and config help so `modelFallback` is described as a chain-resolution last resort, not runtime failover. (#74602) Thanks @jeffrey701.
|
||||
- Channels/Discord: keep read-only allowlist/default-target accessors from resolving SecretRef-backed bot tokens, so status and channel summaries no longer fail when tokens are only available in gateway runtime. (#74737) Thanks @eusine.
|
||||
@@ -54,6 +218,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Feishu: skip empty-text messages (e.g. `{"text":""}`) that carry no media, so no blank user turn is written to the session and downstream LLM providers cannot reject the request with "messages must not be empty". (#74634) Thanks @xdengli and @hclsys.
|
||||
- Feishu/Bitable: clean up newly created placeholder rows whose fields contain only default empty values while preserving meaningful link, attachment, user, number, boolean, and location values during create-app cleanup. (#73920) Carries forward #40602. Thanks @boat2moon.
|
||||
- macOS app: keep attach-only mode and the Debug Settings launchd toggle marker-only, so launching with `--attach-only`/`--no-launchd` no longer uninstalls the Gateway LaunchAgent or drops active sessions. (#72174) Thanks @DolencLuka.
|
||||
- macOS Canvas: stop auto-reloading the current A2UI host during push/eval/snapshot flows, so pushed A2UI content remains visible instead of returning to the empty Canvas shell. Fixes #73337. Thanks @Gr4via.
|
||||
- Plugin SDK: restore the deprecated `plugin-sdk/zalouser` command-auth facade so published Lark/Zalo plugins that import it load on current hosts. Fixes #74702. Thanks @Goron01.
|
||||
- Plugins/runtime-deps: include bundled provider plugins when `models.providers`, auth profiles, agent defaults, or subagent model refs configure that provider, while keeping inactive default-enabled provider plugins out of doctor repair. Refs #74307. Thanks @Skeptomenos.
|
||||
- Plugins/runtime: resolve relative plugin `api.resolvePath` inputs against the plugin root instead of the host working directory, while keeping absolute and home paths user-resolved. Fixes #74718. Thanks @jimdawdy-hub.
|
||||
@@ -73,6 +238,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Gateway/models: serve the last successful model catalog while stale reloads refresh in the background, so Gateway control-plane and OpenAI-compatible requests no longer block behind model-provider rediscovery after model config changes. Refs #74135, #74630, and #74633. Thanks @DerFlash, @moltar-bot, and @Saboor711.
|
||||
- CLI/status: resolve read-only channel setup runtime fallback from the packaged OpenClaw dist root, so `status --all`, `status --deep`, channel, and doctor paths do not crash when an external channel plugin needs setup metadata. Fixes #74693. Thanks @giangthb.
|
||||
- SDK/events: keep per-run SDK event streams from surfacing duplicate raw chat projection frames, while normalizing chat-only projection frames and preserving raw access through `rawEvents`. Refs #74704. Thanks @BunsDev.
|
||||
- SDK: report Gateway terminal `agent.wait` timeout snapshots with lifecycle metadata as `timed_out` while keeping bare wait deadlines non-terminal. Thanks @clawsweeper.
|
||||
- Google Meet: block managed Chrome intro/test speech until browser health proves the participant is in-call, and expose `speechReady` diagnostics so login, admission, permission, and audio-bridge blockers no longer look like successful speech. Refs #72478. Thanks @DougButdorf.
|
||||
- Slack/commands: keep native command argument menus on select controls for encoded choice values up to Slack's option limit and truncate fallback button labels to Slack's button-text limit, so long valid choices no longer render invalid Slack blocks. Thanks @slackapi.
|
||||
- Agents/Codex: flush accepted debounced steering messages before normal app-server turn cleanup, so inbound follow-ups acknowledged as queued are not dropped when the turn completes before the debounce fires. Thanks @vincentkoc.
|
||||
@@ -99,8 +265,10 @@ Docs: https://docs.openclaw.ai
|
||||
- ACP/resolver: fall through to thread-bound session resolution when an explicit `--session` token cannot be resolved while preserving the bad-token diagnostic when no thread binding exists, so Discord slash commands that auto-fill the current thread ID as the positional ACP target no longer return "Unable to resolve session target" errors. Fixes #66299. Thanks @hclsys, @kindomLee, and @martingarramon.
|
||||
- Agents/sessions: emit a terminal lifecycle backstop when embedded timeout/error turns return without `agent_end`, so Gateway sessions no longer stay stuck in `running` after failover surfaces a timeout. Fixes #74607. Thanks @millerc79.
|
||||
- Gateway/diagnostics: include stuck-session reason hints and recovery skip causes in warnings, so operators can tell whether a lane is waiting on active work, queued work, or stale bookkeeping. Thanks @vincentkoc.
|
||||
- Providers/DeepSeek: expose native DeepSeek V4 `xhigh` and `max` thinking levels through the provider `resolveThinkingProfile` hook so `/think xhigh|max` applies the intended effort instead of falling back to base levels. (#73008) Thanks @ai-hpc.
|
||||
- Agents/Codex: bound embedded-run cleanup, trajectory flushing, and command-lane task timeouts after runtime failures, so Discord and other chat sessions return to idle instead of staying stuck in processing. Thanks @vincentkoc.
|
||||
- Heartbeat/exec: consume successful metadata-only async exec completions silently so Telegram and other chat surfaces no longer ask users for missing command logs after `No session found`. Fixes #74595. Thanks @gkoch02.
|
||||
- Active Memory/Memory: materialize allowlisted memory plugin tools for lightweight embedded recall runs so Memory Core tools do not collapse to an empty runtime allowlist. Fixes #74572. (#74592) Thanks @LaFleurAdvertising and @vyctorbrzezowski.
|
||||
- Web fetch: add a documented `tools.web.fetch.ssrfPolicy.allowIpv6UniqueLocalRange` opt-in and thread it through cache keys and DNS/IP checks so trusted fake-IP proxy stacks using `fc00::/7` can work without broad private-network access. Fixes #74351. Thanks @jeffrey701.
|
||||
- OpenAI Codex: restore `/verbose full` persistence and app-server tool-output forwarding, and retry Gateway E2E temp-home cleanup so debug runs do not regress on stale validation or cleanup flakes. Thanks @vincentkoc.
|
||||
- Anthropic/Meridian: preserve text and thinking content seeded on `content_block_start` in anthropic-messages streams, so `[thinking, text]` replies no longer persist as empty turns or trigger empty-response fallbacks. Fixes #74410. Thanks @vyctorbrzezowski.
|
||||
@@ -109,7 +277,8 @@ Docs: https://docs.openclaw.ai
|
||||
- Sandbox/Docker: tolerate Docker daemon unavailability when sandbox mode is off, so doctor and preflight checks no longer fail on installs that do not run the Docker daemon. Fixes #73671. Thanks @kaseonedge.
|
||||
- Control UI/mobile: persist mobile chat settings through Lit-managed state and route mobile navigation through the same view-state path so chat panel toggles survive transitions on small viewports. Thanks @BunsDev.
|
||||
- Control UI/exports: align sidebar trigger affordances across the resizable divider, mobile layout, and exported-HTML transcript template so the sidebar toggle and exported transcript sidebar render with consistent hit areas and styling. Thanks @BunsDev.
|
||||
- Control UI/chat: disable the page refresh affordance while a chat run is active so accidental refreshes do not abort an in-flight reply. Thanks @BunsDev.
|
||||
- Control UI/chat: disable the page refresh affordance while a chat run is active so accidental refreshes do not abort an in-flight reply. Thanks @Angfr95 and @BunsDev.
|
||||
- Memory/LanceDB: return real memory records from `openclaw ltm list` (with optional `--limit` and createdAt ordering) instead of an empty placeholder, so the CLI surface matches the documented LTM listing contract. (#67952) Thanks @zhangyue19921010.
|
||||
- Media: include redacted per-attempt resize failures and resolved model input capabilities in vision-pipeline errors so ARM64 image failures are diagnosable without closing the remaining routing investigation. Refs #74552. Thanks @1yihui.
|
||||
- Control UI/i18n: route zh-CN agent, debug, channel-refresh, and exec-approval copy through the locale source while preserving the English `Cron Jobs` agent tab label and the security-audit command styling. Carries forward #39692 repair context. Thanks @hepeng154833488 and @vincentkoc.
|
||||
- Auto-reply: honor explicit `silentReply.direct: "allow"` for clean empty or reasoning-only direct chat turns while keeping the default direct-chat empty-response guard conservative. Fixes #74409. Thanks @jesuskannolis.
|
||||
@@ -148,6 +317,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Channels/Discord: treat bare numeric outbound targets that match the effective Discord DM allowlist as user DMs while preserving account-specific legacy `dm.allowFrom` precedence over inherited root `allowFrom`. (#74303) Thanks @Squirbie.
|
||||
- Channels/Discord/Slack: share one DM policy/allowlist resolver across runtime, setup, allowlist editing, and doctor repair, so legacy `dm.policy` / `dm.allowFrom` compatibility migrates to canonical `dmPolicy` / `allowFrom` without divergent access checks. Thanks @Squirbie.
|
||||
- Control UI: make the chat sidebar split divider focusable, keyboard-resizable, ARIA-described, and pointer-event based so sidebar resizing works without a mouse. Thanks @BunsDev.
|
||||
- Control UI/chat: wire the slash-command autocomplete menu to the composer with stable ARIA relationships so screen readers announce the active command or argument option. Thanks @BunsDev.
|
||||
- Agents/usage: keep PI embedded-run telemetry attributed to the resolved model provider instead of the PI harness label, so OpenRouter and other provider-backed turns report the right provider in session usage and traces. Thanks @vincentkoc.
|
||||
- Agents/attribution: send OpenClaw attribution headers on native OpenAI and Codex traffic, including SDK transports, realtime voice and TTS, device-code auth, WHAM usage, and remote embeddings, so PI-origin defaults no longer leak into provider requests. Thanks @vincentkoc.
|
||||
- Agents/auth: keep OAuth auth profiles inherited from the main agent read-through instead of copying refresh tokens into secondary agents, and refresh Codex app-server tokens against the owning store so multi-agent swarms avoid reused refresh-token failures. Fixes #74055. Thanks @ClarityInvest.
|
||||
@@ -260,6 +430,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Outbound/security: strip known internal runtime scaffolding such as `<system-reminder>` and `<previous_response>` at the final channel delivery boundary and keep Discord output on targeted tag stripping, so degraded harness replies cannot leak those tags to users. Fixes #73595. Thanks @gabrielexito-stack and @martingarramon.
|
||||
- Security/Telegram: load Telegram security adapters in read-only audit/doctor, audit malformed Telegram DM `allowFrom` entries even when groups are disabled, and keep allowlist DM audits from counting stale pairing-store senders, so public/shared-DM risk checks stay accurate. Refs #73698. Thanks @xace1825.
|
||||
- Plugins: remove hidden manifest, provider-owner, bootstrap, and channel metadata caches so plugin installs, manifest edits, and bundled-root changes are visible on the next metadata read while keeping runtime/module loader caches for actual plugin code. Thanks @shakkernerd.
|
||||
- Control UI/WebChat: create a fresh dashboard session from the New Chat button instead of resetting the current transcript with `/new`, while keeping explicit `/new` reset behavior, preserving in-progress composer edits during delayed session creation or when creation cannot safely switch sessions, and showing clear retry feedback when creation is blocked, refreshing, or returns no new session. Carries forward #52042 and #52746. Thanks @bobashopcashier and @vincentkoc.
|
||||
- CLI/plugins: use plugin metadata snapshots for install slot selection and add opt-in plugin lifecycle timing traces, so plugin install avoids runtime-loading the plugin registry for metadata-only decisions. Thanks @shakkernerd.
|
||||
- fix(plugins): restrict bundled plugin dir resolution to trusted package roots. (#73275) Thanks @pgondhi987.
|
||||
- fix(security): prevent workspace PATH injection via service env and trash helpers. (#73264) Thanks @pgondhi987.
|
||||
@@ -318,6 +489,8 @@ Docs: https://docs.openclaw.ai
|
||||
- Providers/GitHub Copilot: support the GUI/RPC wizard device-code auth flow so onboarding from non-TTY clients (gateway RPC bridge, GUI wizards) completes instead of returning empty profiles. Dangerous-state handling now distinguishes `access_denied` and `expired_token` from transport errors. (#73290) Thanks @indierawk2k2.
|
||||
- Installer/Linux: warn before switching an unwritable npm global prefix to `~/.npm-global`, then tell users to run future global updates with `npm i -g openclaw@latest` without `sudo` so npm keeps using the redirected user prefix. Fixes #44365; carries forward #50479. Thanks @Sayeem3051.
|
||||
- Gateway/plugins: enable the native `require()` fast path on Windows for bundled plugin modules so plugin loading uses `require()` instead of Jiti's transform pipeline, reducing startup from ~39s to ~2s on typical 6-plugin setups. Fixes #68656. (#74173) Thanks @galiniliev.
|
||||
- macOS app: detect stale Gateway TLS certificate pins, automatically repair trusted Tailscale Serve rotations, and surface paired-but-disconnected Mac companion nodes so partial Gateway connections no longer look healthy. Thanks @guti.
|
||||
- Feishu: recreate WebSocket clients with monitor-owned backoff only after SDK reconnect exhaustion, preserving heartbeat defaults and shutdown cleanup without treating recoverable SDK callback errors as terminal, so persistent connections recover without manual gateway restart. Fixes #52618; duplicate evidence #59753; related #55532, #68766, #72411, and #73739. Thanks @vincentkoc, @schumilin, @alex-xuweilong, @120106835, @sirfengyu, and @tianhaocui.
|
||||
|
||||
## 2026.4.27
|
||||
|
||||
@@ -349,6 +522,8 @@ Docs: https://docs.openclaw.ai
|
||||
- Plugin SDK/models: add a shared manifest-backed provider catalog builder and move Qianfan, Xiaomi, NVIDIA, Cerebras, Mistral, Moonshot, DeepSeek, Tencent TokenHub, and StepFun provider catalogs onto their plugin manifest `modelCatalog` rows. Thanks @shakkernerd.
|
||||
- Plugin SDK/models: move BytePlus and Volcano Engine standard and plan-provider catalogs into plugin manifest `modelCatalog` rows and remove the now-unused Volcengine-family shared catalog SDK subpath. Thanks @shakkernerd.
|
||||
- CLI/models: move Fireworks and Together AI fixed provider catalogs into plugin manifest `modelCatalog` rows so provider-filtered listing can use manifest-backed static rows. Thanks @shakkernerd.
|
||||
- CLI/models: move Groq's fixed text model catalog into the Groq plugin manifest and declare its setup auth env metadata so provider-filtered listing can use manifest-backed rows without deprecated auth metadata. Thanks @shakkernerd.
|
||||
- CLI/models: move Venice's 41-row seed catalog into the Venice plugin manifest, derive runtime fallback rows from that manifest, and keep Venice API discovery as refreshable runtime work instead of a second hard-coded catalog. Thanks @shakkernerd.
|
||||
- Channels/Yuanbao: register the Tencent Yuanbao external channel plugin (`openclaw-plugin-yuanbao`) in the official channel catalog, contract suites, and community plugin docs, with a new `docs/channels/yuanbao.md` quick-start guide for WebSocket bot DMs and group chats. (#72756) Thanks @loongfay.
|
||||
- Channels/QQBot: add full group chat support (history tracking, @-mention gating, activation modes, per-group config, FIFO message queue with deliver debounce), C2C `stream_messages` streaming with a `StreamingController` lifecycle manager, unified `sendMedia` with chunked upload for large files, and refactor the engine into pipeline stages, focused outbound submodules, builtin slash-command modules, and explicit DI ports via `createEngineAdapters()`. (#70624) Thanks @cxyhhhhh.
|
||||
- Plugins/startup: migrate bundled plugin manifests to explicit `activation.onStartup` declarations so Gateway startup imports only the bundled plugins that intentionally register startup-time runtime surfaces. Thanks @shakkernerd.
|
||||
@@ -508,7 +683,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Doctor/channels: suppress disabled bundled-plugin blocker warnings when a trusted external plugin owns the configured channel, so Lark/Feishu installs no longer get Feishu repair noise after switching to `openclaw-lark`. Fixes #56794. Thanks @wuji-tech-dev.
|
||||
- CLI/status: show skipped fast-path memory checks as `not checked` and report active custom memory plugin runtime status from `status --json --all` without requiring built-in `agents.defaults.memorySearch`, so plugins such as memory-lancedb-pro and memory-cms no longer look unavailable when their own runtime is healthy. Fixes #56968. Thanks @Tony-ooo and @aderius.
|
||||
- Gateway/channels: record and log unexpected clean channel monitor exits so channels that return without throwing no longer appear stopped with no error. Fixes #73099. Thanks @balaji1968-kingler.
|
||||
- Discord/group chats: keep group/channel replies private by default unless the agent explicitly uses the message tool, so always-on rooms can lurk without leaking automatic final, block, preview, or status-reaction output; `messages.groupChat.visibleReplies: "automatic"` restores legacy auto-posting. (#73046) Thanks @scoootscooob.
|
||||
- Group/channel chats (all channels): keep group/channel replies private by default unless the agent explicitly uses the message tool, fall back to automatic visible replies when the message tool is unavailable, and have `openclaw doctor` warn about that policy mismatch; `messages.groupChat.visibleReplies: "automatic"` restores legacy auto-posting. (#73046) Thanks @scoootscooob.
|
||||
- Plugins/package: force nested bundled-plugin runtime dependency installs out of inherited npm dry-run mode during prepack and package smoke checks, so packed installs materialize required plugin modules instead of reporting missing bundled files. Refs #73128. Thanks @Adam-Researchh.
|
||||
- Discord: skip reaction events before REST channel fetch when notifications are off, guild reactions are disabled, or allowlist mode cannot match without channel overrides, reducing reconnect bursts that caused slow listener warnings. Fixes #73133. Thanks @isaacsummers.
|
||||
- Channels/Telegram: centralize polling update tracking so accepted offsets remain durable across restarts, same-process handler failures can still retry, and slow offset writes cannot overwrite newer accepted watermarks. Refs #73115. Thanks @vdruts.
|
||||
|
||||
@@ -167,7 +167,7 @@ RUN --mount=type=cache,id=openclaw-bookworm-apt-cache,target=/var/cache/apt,shar
|
||||
--mount=type=cache,id=openclaw-bookworm-apt-lists,target=/var/lib/apt,sharing=locked \
|
||||
apt-get update && \
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
||||
ca-certificates procps hostname curl git lsof openssl && \
|
||||
ca-certificates procps hostname curl git lsof openssl python3 && \
|
||||
update-ca-certificates
|
||||
|
||||
RUN chown node:node /app
|
||||
@@ -239,9 +239,16 @@ RUN --mount=type=cache,id=openclaw-bookworm-apt-cache,target=/var/cache/apt,shar
|
||||
ca-certificates curl gnupg && \
|
||||
install -m 0755 -d /etc/apt/keyrings && \
|
||||
# Verify Docker apt signing key fingerprint before trusting it as a root key.
|
||||
# Require exactly one primary key (`pub` in --with-colons; subkeys use `sub`) so we
|
||||
# never pin the first fingerprint while apt trusts extra keys from the same file.
|
||||
# Update OPENCLAW_DOCKER_GPG_FINGERPRINT when Docker rotates release keys.
|
||||
curl -fsSL https://download.docker.com/linux/debian/gpg -o /tmp/docker.gpg.asc && \
|
||||
expected_fingerprint="$(printf '%s' "$OPENCLAW_DOCKER_GPG_FINGERPRINT" | tr '[:lower:]' '[:upper:]' | tr -d '[:space:]')" && \
|
||||
docker_gpg_pub_count="$(gpg --batch --show-keys --with-colons /tmp/docker.gpg.asc | awk -F: '$1 == "pub" { c++ } END { print c+0 }')" && \
|
||||
if [ "$docker_gpg_pub_count" != "1" ]; then \
|
||||
echo "ERROR: Docker apt key must contain exactly one public key (found $docker_gpg_pub_count); refusing a multi-key file." >&2; \
|
||||
exit 1; \
|
||||
fi && \
|
||||
actual_fingerprint="$(gpg --batch --show-keys --with-colons /tmp/docker.gpg.asc | awk -F: '$1 == "fpr" { print toupper($10); exit }')" && \
|
||||
if [ -z "$actual_fingerprint" ] || [ "$actual_fingerprint" != "$expected_fingerprint" ]; then \
|
||||
echo "ERROR: Docker apt key fingerprint mismatch (expected $expected_fingerprint, got ${actual_fingerprint:-<empty>})" >&2; \
|
||||
|
||||
142
SECURITY.md
142
SECURITY.md
@@ -1,8 +1,14 @@
|
||||
# Security Policy
|
||||
|
||||
If you believe you've found a security issue in OpenClaw, please report it privately.
|
||||
If you believe you've found a security issue in OpenClaw, report it privately first.
|
||||
|
||||
## Reporting
|
||||
This policy does two things: it gives researchers a clear disclosure path, and it spells out the trust model maintainers use when triaging reports. OpenClaw is local-first agent infrastructure for trusted operators; it is not designed as a shared multi-tenant boundary between adversarial users on one gateway.
|
||||
|
||||
The fastest useful reports show a current, reproducible boundary bypass with demonstrated impact. Scanner output, prompt-injection-only chains, or reports that rely on hostile users sharing one trusted gateway are usually not security vulnerabilities under this model.
|
||||
|
||||
Security work is shared across a number of OpenClaw maintainers, including engineers and security researchers from organizations such as NVIDIA and Tencent. See the [maintainer list](CONTRIBUTING.md#maintainers).
|
||||
|
||||
## Report a Security Issue
|
||||
|
||||
Report vulnerabilities directly to the repository where the issue lives:
|
||||
|
||||
@@ -15,22 +21,50 @@ Report vulnerabilities directly to the repository where the issue lives:
|
||||
|
||||
For issues that don't fit a specific repo, or if you're unsure, email **[security@openclaw.ai](mailto:security@openclaw.ai)** and we'll route it.
|
||||
|
||||
For OpenClaw core issues, submit through a private [GitHub Security Advisory](https://github.com/openclaw/openclaw/security/advisories/new). Do not open a public issue or PR that discloses an unpatched vulnerability, exploit path, secret, or security-sensitive proof of concept.
|
||||
|
||||
Maintainers may close, hide, delete, or otherwise take down public issues and PRs that disclose vulnerabilities or active security issues. We will redirect those reports through the private disclosure process so the issue can be triaged and fixed without giving attackers a public playbook.
|
||||
|
||||
For full reporting instructions see our [Trust page](https://trust.openclaw.ai).
|
||||
|
||||
### Required in Reports
|
||||
OpenClaw does not currently run a paid bug bounty program. Please still disclose responsibly so we can fix real issues quickly. The best way to help the project right now is to send high-signal reports and, when practical, focused PRs.
|
||||
|
||||
1. **Title**
|
||||
2. **Severity Assessment**
|
||||
3. **Impact**
|
||||
4. **Affected Component**
|
||||
5. **Technical Reproduction**
|
||||
6. **Demonstrated Impact**
|
||||
7. **Environment**
|
||||
8. **Remediation Advice**
|
||||
### What We Need
|
||||
|
||||
Reports without reproduction steps, demonstrated impact, and remediation advice will be deprioritized. Given the volume of AI-generated scanner findings, we must ensure we're receiving vetted reports from researchers who understand the issues.
|
||||
Make the report easy to reproduce and easy to route:
|
||||
|
||||
### Report Acceptance Gate (Triage Fast Path)
|
||||
- What you found and why you believe it is security-relevant.
|
||||
- The affected component, version, and commit SHA when possible.
|
||||
- Reproduction steps or a proof of concept against latest `main` or the latest released version.
|
||||
- The actual impact, including which OpenClaw trust boundary is crossed.
|
||||
- Any remediation advice or focused patch you can provide.
|
||||
|
||||
Reports without reproduction steps, demonstrated impact, and remediation advice are deprioritized. We receive a high volume of AI-generated scanner findings, so we prioritize vetted reports from researchers who can show how the issue crosses an OpenClaw security boundary.
|
||||
|
||||
### What Usually Is Not a Security Bug
|
||||
|
||||
These patterns are usually not vulnerabilities by themselves:
|
||||
|
||||
- Prompt injection without a policy, auth, approval, sandbox, or tool-boundary bypass.
|
||||
- A trusted operator using an intentional local feature, such as local shell access or browser/script execution.
|
||||
- A malicious plugin after a trusted operator installs or enables it.
|
||||
- Multiple adversarial users sharing one Gateway host/config and expecting per-user isolation.
|
||||
- Scanner-only, dependency-only, or stale-path reports without a working repro and demonstrated OpenClaw impact.
|
||||
- Public internet exposure or risky deployment choices that the docs already recommend against.
|
||||
|
||||
If you are unsure, report privately. We would rather route a careful report than miss a real boundary issue.
|
||||
|
||||
### Duplicate Report Handling
|
||||
|
||||
- Search existing advisories before filing.
|
||||
- Include likely duplicate GHSA IDs in your report when applicable.
|
||||
- Maintainers may close lower-quality/later duplicates in favor of the earliest high-quality canonical report.
|
||||
|
||||
## Security Posture and Report Rules
|
||||
|
||||
The sections below are the normative posture maintainers use for report triage. The headings are editorial; the policy text defines the boundary.
|
||||
|
||||
### Detailed Report Acceptance Gate
|
||||
|
||||
For fastest triage, include all of the following:
|
||||
|
||||
@@ -47,7 +81,7 @@ For fastest triage, include all of the following:
|
||||
|
||||
Reports that miss these requirements may be closed as `invalid` or `no-action`.
|
||||
|
||||
### Common False-Positive Patterns
|
||||
### Detailed False-Positive Patterns
|
||||
|
||||
These are frequently reported but are typically closed with no code change:
|
||||
|
||||
@@ -78,26 +112,11 @@ These are frequently reported but are typically closed with no code change:
|
||||
- Reports that restate an already-fixed issue against later released versions without showing the vulnerable path still exists in the shipped tag or published artifact for that later version.
|
||||
- SSRF reports against the operator-managed HTTP/WebSocket proxy-routing feature whose only claim is that ordinary process-local HTTP clients (`fetch`, `node:http`, `node:https`, WebSocket clients, axios/got/node-fetch-style clients) can reach an internal, metadata, private, or otherwise sensitive destination when proxy routing is disabled, missing, or the operator-managed proxy policy allows it. For this feature, OpenClaw provides fail-closed proxy routing when enabled; the external proxy's destination policy is operator infrastructure, not an OpenClaw-controlled security boundary. See [Network proxy](https://docs.openclaw.ai/security/network-proxy).
|
||||
|
||||
### Duplicate Report Handling
|
||||
|
||||
- Search existing advisories before filing.
|
||||
- Include likely duplicate GHSA IDs in your report when applicable.
|
||||
- Maintainers may close lower-quality/later duplicates in favor of the earliest high-quality canonical report.
|
||||
|
||||
## Security & Trust
|
||||
|
||||
**Jamieson O'Reilly** ([@theonejvo](https://twitter.com/theonejvo)) is Security & Trust at OpenClaw. Jamieson is the founder of [Dvuln](https://dvuln.com) and brings extensive experience in offensive security, penetration testing, and security program development.
|
||||
|
||||
## Bug Bounties
|
||||
|
||||
OpenClaw is a labor of love. There is no bug bounty program and no budget for paid reports. Please still disclose responsibly so we can fix issues quickly.
|
||||
The best way to help the project right now is by sending PRs.
|
||||
|
||||
## Maintainers: GHSA Updates via CLI
|
||||
### Maintainer GHSA Updates via CLI
|
||||
|
||||
When patching a GHSA via `gh api`, include `X-GitHub-Api-Version: 2022-11-28` (or newer). Without it, some fields (notably CVSS) may not persist even if the request returns 200.
|
||||
|
||||
## Operator Trust Model (Important)
|
||||
### Operator Trust Model
|
||||
|
||||
OpenClaw does **not** model one gateway as a multi-tenant, adversarial user boundary.
|
||||
|
||||
@@ -122,7 +141,7 @@ OpenClaw does **not** model one gateway as a multi-tenant, adversarial user boun
|
||||
- Implicit exec calls (no explicit host in the tool call) follow the same behavior.
|
||||
- This is expected in OpenClaw's one-user trusted-operator model. If you need isolation, enable sandbox mode (`non-main`/`all`) and keep strict tool policy.
|
||||
|
||||
## Trusted Plugin Concept (Core)
|
||||
### Trusted Plugins
|
||||
|
||||
Plugins/extensions are part of OpenClaw's trusted computing base for a gateway.
|
||||
|
||||
@@ -130,7 +149,7 @@ Plugins/extensions are part of OpenClaw's trusted computing base for a gateway.
|
||||
- Plugin behavior such as reading env/files or running host commands is expected inside this trust boundary.
|
||||
- Security reports must show a boundary bypass (for example unauthenticated plugin load, allowlist/policy bypass, or sandbox/path-safety bypass), not only malicious behavior from a trusted-installed plugin.
|
||||
|
||||
## Out of Scope
|
||||
### Out of Scope
|
||||
|
||||
- Public Internet Exposure
|
||||
- Using OpenClaw in ways that the docs recommend not to
|
||||
@@ -156,7 +175,7 @@ Plugins/extensions are part of OpenClaw's trusted computing base for a gateway.
|
||||
- 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.
|
||||
- SSRF reports limited to the operator-managed HTTP/WebSocket proxy-routing feature where the demonstrated mitigation is to enable/configure `proxy.enabled` with a filtering `proxy.proxyUrl`/`OPENCLAW_PROXY_URL`, or where impact depends on a permissive/misconfigured operator proxy. This only covers normal process-local HTTP(S)/WebSocket egress (`fetch`, Node HTTP(S), and similar JavaScript clients); non-HTTP egress and other features are assessed separately. See [Network proxy](https://docs.openclaw.ai/security/network-proxy).
|
||||
|
||||
## Deployment Assumptions
|
||||
### Deployment Assumptions
|
||||
|
||||
OpenClaw security guidance assumes:
|
||||
|
||||
@@ -166,7 +185,7 @@ OpenClaw security guidance assumes:
|
||||
- Authenticated Gateway callers are treated as trusted operators. Session identifiers (for example `sessionKey`) are routing controls, not per-user authorization boundaries.
|
||||
- Multiple gateway instances can run on one machine, but the recommended model is clean per-user isolation (prefer one host/VPS per user).
|
||||
|
||||
## One-User Trust Model (Personal Assistant)
|
||||
### One-User Trust Model
|
||||
|
||||
OpenClaw's security model is "personal assistant" (one trusted operator, potentially many agents), not "shared multi-tenant bus."
|
||||
|
||||
@@ -178,7 +197,7 @@ OpenClaw's security model is "personal assistant" (one trusted operator, potenti
|
||||
- For company-shared setups, use a dedicated machine/VM/container and dedicated accounts; avoid mixing personal data on that runtime.
|
||||
- If that host/browser profile is logged into personal accounts (for example Apple/Google/personal password manager), you have collapsed the boundary and increased personal-data exposure risk.
|
||||
|
||||
## Context Visibility and Allowlists
|
||||
### Context Visibility and Allowlists
|
||||
|
||||
OpenClaw distinguishes:
|
||||
|
||||
@@ -196,7 +215,7 @@ Reports that only show supplemental-context visibility differences are typically
|
||||
|
||||
Hardening roadmap may add explicit visibility modes (for example `all`, `allowlist`, `allowlist_quote`) so operators can opt into stricter context filtering with predictable tradeoffs.
|
||||
|
||||
## Agent and Model Assumptions
|
||||
### Agent and Model Assumptions
|
||||
|
||||
- The model/agent is **not** a trusted principal. Assume prompt/content injection can manipulate behavior.
|
||||
- Security boundaries come from host/config trust, auth, tool policy, sandboxing, and exec approvals.
|
||||
@@ -204,7 +223,7 @@ Hardening roadmap may add explicit visibility modes (for example `all`, `allowli
|
||||
- Hook/webhook-driven payloads should be treated as untrusted content; keep unsafe bypass flags disabled unless doing tightly scoped debugging (`hooks.gmail.allowUnsafeExternalContent`, `hooks.mappings[].allowUnsafeExternalContent`).
|
||||
- Weak model tiers are generally easier to prompt-inject. For tool-enabled or hook-driven agents, prefer strong modern model tiers and strict tool policy (for example `tools.profile: "messaging"` or stricter), plus sandboxing where possible.
|
||||
|
||||
## Gateway and Node trust concept
|
||||
### Gateway and Node Trust Concept
|
||||
|
||||
OpenClaw separates routing from execution, but both remain inside the same operator trust boundary:
|
||||
|
||||
@@ -215,7 +234,7 @@ OpenClaw separates routing from execution, but both remain inside the same opera
|
||||
- Differences in command-risk warning heuristics between exec surfaces (`gateway`, `node`, `sandbox`) do not, by themselves, constitute a security-boundary bypass.
|
||||
- For untrusted-user isolation, split by trust boundary: separate gateways and separate OS users/hosts per boundary.
|
||||
|
||||
## Workspace Memory Trust Boundary
|
||||
### Workspace Memory Trust Boundary
|
||||
|
||||
`MEMORY.md` and `memory/*.md` are plain workspace files and are treated as trusted local operator state.
|
||||
|
||||
@@ -224,7 +243,7 @@ OpenClaw separates routing from execution, but both remain inside the same opera
|
||||
- Example report pattern considered out of scope: "attacker writes malicious content into `memory/*.md`, then `memory_search` returns it."
|
||||
- If you need isolation between mutually untrusted users, split by OS user or host and run separate gateways.
|
||||
|
||||
## Plugin Trust Boundary
|
||||
### Plugin Trust Boundary
|
||||
|
||||
Plugins/extensions are loaded **in-process** with the Gateway and are treated as trusted code.
|
||||
|
||||
@@ -232,7 +251,7 @@ Plugins/extensions are loaded **in-process** with the Gateway and are treated as
|
||||
- Runtime helpers (for example `runtime.system.runCommandWithTimeout`) are convenience APIs, not a sandbox boundary.
|
||||
- Only install plugins you trust, and prefer `plugins.allow` to pin explicit trusted plugin ids.
|
||||
|
||||
## Temp Folder Boundary (Media/Sandbox)
|
||||
### Temp Folder Boundary
|
||||
|
||||
OpenClaw uses a dedicated temp root for local media handoff and sandbox-adjacent temp artifacts:
|
||||
|
||||
@@ -249,19 +268,19 @@ Security boundary notes:
|
||||
- SDK temp helpers: `src/plugin-sdk/temp-path.ts`
|
||||
- messaging/channel tmp guardrail: `scripts/check-no-random-messaging-tmp.mjs`
|
||||
|
||||
## Operational Guidance
|
||||
### Operational Guidance
|
||||
|
||||
For threat model + hardening guidance (including `openclaw security audit --deep` and `--fix`), see:
|
||||
|
||||
- `https://docs.openclaw.ai/gateway/security`
|
||||
|
||||
### Tool filesystem hardening
|
||||
#### Tool Filesystem Hardening
|
||||
|
||||
- `tools.exec.applyPatch.workspaceOnly: true` (recommended): keeps `apply_patch` writes/deletes within the configured workspace directory.
|
||||
- `tools.fs.workspaceOnly: true` (optional): restricts `read`/`write`/`edit`/`apply_patch` paths and native prompt image auto-load paths to the workspace directory.
|
||||
- Avoid setting `tools.exec.applyPatch.workspaceOnly: false` unless you fully trust who can trigger tool execution.
|
||||
|
||||
### Sub-agent delegation hardening
|
||||
#### Sub-Agent Delegation Hardening
|
||||
|
||||
- Keep `sessions_spawn` denied unless you explicitly need delegated runs.
|
||||
- Keep `agents.list[].subagents.allowAgents` narrow, and only include agents with sandbox settings you trust.
|
||||
@@ -269,7 +288,7 @@ For threat model + hardening guidance (including `openclaw security audit --deep
|
||||
- `sandbox: "require"` rejects the spawn unless the target child runtime is sandboxed.
|
||||
- This prevents a less-restricted session from delegating work into an unsandboxed child by mistake.
|
||||
|
||||
### Web Interface Safety
|
||||
#### Web Interface Safety
|
||||
|
||||
OpenClaw's web interface (Gateway Control UI + HTTP endpoints) is intended for **local use only**.
|
||||
|
||||
@@ -321,12 +340,39 @@ docker run --read-only --cap-drop=ALL \
|
||||
|
||||
## Security Scanning
|
||||
|
||||
This project uses `detect-secrets` for automated secret detection in CI/CD.
|
||||
See `.detect-secrets.cfg` for configuration and `.secrets.baseline` for the baseline.
|
||||
OpenClaw uses several security and release-validation layers. No single scanner is treated as the boundary.
|
||||
|
||||
Run locally:
|
||||
### Secret Detection
|
||||
|
||||
OpenClaw uses `detect-secrets` with a checked-in baseline and local exclusion notes (`.secrets.baseline`, `.detect-secrets.cfg`). Secret-resolution behavior is also covered by the dedicated secrets test surface.
|
||||
|
||||
Run the baseline scan locally:
|
||||
|
||||
```bash
|
||||
pip install detect-secrets==1.5.0
|
||||
detect-secrets scan --baseline .secrets.baseline
|
||||
```
|
||||
|
||||
### Static Analysis
|
||||
|
||||
CI runs CodeQL across core TypeScript, GitHub Actions, Android, macOS, and high-risk runtime boundaries using `.github/workflows/codeql*.yml` and `.github/codeql/*.yml`.
|
||||
|
||||
OpenGrep provides a high-precision Semgrep-compatible layer. PRs run a changed-path scan; maintainers can run a full repository scan when needed. The rulepack lives under `security/opengrep/`, with `.semgrepignore` as the shared exclusion file.
|
||||
|
||||
Run the local OpenGrep wrapper after installing `opengrep`:
|
||||
|
||||
```bash
|
||||
scripts/run-opengrep.sh --changed --sarif --error
|
||||
pnpm check:opengrep-rule-metadata
|
||||
```
|
||||
|
||||
### E2E and Live Validation
|
||||
|
||||
Security-relevant behavior is also covered by runtime validation, not only static scanning:
|
||||
|
||||
- `pnpm test:e2e` for repo E2E coverage.
|
||||
- `pnpm test:live` for live provider/runtime coverage.
|
||||
- `pnpm test:docker:all` for Docker-packaged runtime scenarios.
|
||||
- Package acceptance and scheduled live/E2E workflows for release-path validation.
|
||||
|
||||
These lanes exercise packaged installs, gateway/runtime behavior, live model/provider paths, Docker scenarios, and platform smoke tests. They complement scanners by proving the security-sensitive flows still behave correctly in real runtime environments.
|
||||
|
||||
767
appcast.xml
767
appcast.xml
@@ -2,6 +2,369 @@
|
||||
<rss xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle" version="2.0">
|
||||
<channel>
|
||||
<title>OpenClaw</title>
|
||||
<item>
|
||||
<title>2026.4.29</title>
|
||||
<pubDate>Thu, 30 Apr 2026 21:47:22 +0000</pubDate>
|
||||
<link>https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml</link>
|
||||
<sparkle:version>2026042990</sparkle:version>
|
||||
<sparkle:shortVersionString>2026.4.29</sparkle:shortVersionString>
|
||||
<sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion>
|
||||
<description><![CDATA[<h2>OpenClaw 2026.4.29</h2>
|
||||
<h3>Highlights</h3>
|
||||
<ul>
|
||||
<li>Messaging and automation get active-run steering by default, visible-reply enforcement, spawned subagent routing metadata, and opt-in follow-up commitments for heartbeat-delivered reminders. Thanks @vincentkoc, @scoootscooob, @samzong, and @vignesh07.</li>
|
||||
<li>Memory grows into a people-aware wiki with provenance views, per-conversation Active Memory filters, partial recall on timeout, and bounded REM preview diagnostics. Thanks @vincentkoc, @quengh, @joeykrug, and @samzong.</li>
|
||||
<li>Provider/model coverage expands with NVIDIA onboarding/catalogs plus faster manifest-backed model/auth paths, Bedrock Opus 4.7 thinking parity, and safer Codex/OpenAI-compatible replay and streaming behavior. Thanks @eleqtrizit, @shakkernerd, @prasad-yashdeep, @woodhouse-bot, and @LyHug.</li>
|
||||
<li>Gateway and packaged-plugin reliability focuses on slow-host startup, reusable model catalogs, event-loop readiness diagnostics, runtime-dependency repair, stale-session recovery, and version-scoped update caches. Thanks @lpendeavors, @DerFlash, @vincentkoc, @pashpashpash, and @jhsmith409.</li>
|
||||
<li>Channel fixes cluster around Slack Block Kit limits, Telegram proxy/webhook/polling/send resilience, Discord startup/rate-limit handling, WhatsApp delivery/liveness, and Microsoft Teams/Matrix/Feishu edge cases. Thanks @slackapi, @SymbolStar, @djgeorg3, @TinyTb, @dseravalli, @nklock, and @alex-xuweilong.</li>
|
||||
<li>Security and operations add OpenGrep scanning, sharper GHSA triage policy, safer exec/pairing/owner-scope handling, Docker/onboarding automation, and web-fetch IPv6 ULA opt-in for trusted proxy stacks. Thanks @jesse-merhi, @pgondhi987, @mmaps, @jinjimz, and @jeffrey701.</li>
|
||||
</ul>
|
||||
<h3>Changes</h3>
|
||||
<ul>
|
||||
<li>Security/tools: configured tool sections (<code>tools.exec</code>, <code>tools.fs</code>) no longer implicitly widen restrictive profiles (<code>messaging</code>, <code>minimal</code>). Users who need those tools under a restricted profile must add explicit <code>alsoAllow</code> entries; a startup warning identifies affected configs. Fixes #47487. Thanks @amknight.</li>
|
||||
<li>Agents/commitments: add opt-in inferred follow-up commitments with hidden batched extraction, per-agent/per-channel scoping, heartbeat delivery, CLI management, a simple <code>commitments.enabled</code>/<code>commitments.maxPerDay</code> config, and heartbeat-interval due-time clamping so magical check-ins do not echo immediately. (#74189) Thanks @vignesh07.</li>
|
||||
<li>Messages/queue: make <code>steer</code> drain all pending Pi steering messages at the next model boundary, keep legacy one-at-a-time steering as <code>queue</code>, and add a dedicated steering queue docs page. Thanks @vincentkoc.</li>
|
||||
<li>Messages/queue: default active-run queueing to <code>steer</code> with a 500ms followup fallback debounce, and document the queue modes, precedence, and drop policies on the command queue page. Thanks @vincentkoc.</li>
|
||||
<li>Messages: add global <code>messages.visibleReplies</code> so operators can require visible output to go through <code>message(action=send)</code> for any source chat, while <code>messages.groupChat.visibleReplies</code> stays available as the group/channel override. Thanks @scoootscooob.</li>
|
||||
<li>Gateway/events: surface <code>spawnedBy</code> on subagent chat and agent broadcast payloads so clients can route child session events without an extra session lookup. (#63244) Thanks @samzong.</li>
|
||||
<li>Memory/wiki: add agent-facing people wiki metadata, canonical aliases, person cards, relationship graphs, privacy/provenance reports, evidence-kind drilldown, and search modes for person lookup, question routing, source evidence, and raw claims. Thanks @vincentkoc.</li>
|
||||
<li>Active Memory: add optional per-conversation <code>allowedChatIds</code> and <code>deniedChatIds</code> filters so operators can enable recall only for selected direct, group, or channel conversations while keeping broad sessions skipped. (#67977) Thanks @quengh.</li>
|
||||
<li>Active Memory: return bounded partial recall summaries when the hidden memory sub-agent times out, including the default temporary-transcript path, so useful recovered context is not discarded. (#73219) Thanks @joeykrug.</li>
|
||||
<li>Gateway/memory: add a read-only <code>doctor.memory.remHarness</code> RPC so operator clients can preview bounded REM dreaming output without running mutation paths. (#66673) Thanks @samzong.</li>
|
||||
<li>Providers/NVIDIA: add the NVIDIA provider with API-key onboarding, setup docs, static catalog metadata, and literal model-ref picker support so NVIDIA hosted models can be selected with their provider prefix intact. (#71204) Thanks @eleqtrizit.</li>
|
||||
<li>Models: suppress explicitly configured openai-codex/gpt-5.4-mini inline entries so a stale models config written by <code>openclaw doctor --fix</code> cannot bypass the manifest capability block and cause repeated assistant-turn failures when the runtime switches to that model on ChatGPT-backed Codex accounts. Conditional suppressions (e.g. qwen Coding Plan endpoint guards) remain bypassable by explicit user configuration. (#74451) Thanks @0xCyda, @hclsys, and @Marvae.</li>
|
||||
<li>Added SQLite-backed plugin state store (<code>api.runtime.state.openKeyedStore</code>) for restart-safe keyed registries with TTL, eviction, and automatic plugin isolation. Thanks @amknight.</li>
|
||||
<li>Plugin SDK: mark remaining legacy alias exports and diffs tool/config aliases with deprecation metadata, and add a guard so future legacy alias comments require <code>@deprecated</code> tags. Thanks @vincentkoc.</li>
|
||||
<li>CLI/QR/dependencies: internalize small terminal progress and QR wrapper helpers while keeping the real QR encoder dependency direct, reducing the default runtime dependency graph without changing QR output behavior. Thanks @vincentkoc.</li>
|
||||
<li>Dependencies: refresh workspace runtime, plugin, and tooling packages, including ACP, Pi, AWS SDK, TypeBox, pnpm, oxlint, oxfmt, jsdom, pdfjs, ciao, and tokenjuice, while keeping patched ACP behavior and lint gates current. Thanks @mariozechner.</li>
|
||||
<li>Gateway/dev: run <code>pnpm gateway:watch</code> through a named tmux session by default, with <code>gateway:watch:raw</code> and <code>OPENCLAW_GATEWAY_WATCH_TMUX=0</code> for foreground mode, so repeated starts respawn an inspectable watcher without trapping the invoking agent shell. Thanks @vincentkoc.</li>
|
||||
<li>Gateway/diagnostics: emit an opt-in startup diagnostics timeline that records gateway lifecycle and plugin-load phases behind a config flag, so slow-start diagnosis no longer requires bespoke instrumentation. Thanks @shakkernerd.</li>
|
||||
<li>Control UI/i18n: extend the locale registry with new Persian (fa), Dutch (nl), Vietnamese (vi), Italian (it), Arabic (ar), and Thai (th) entries and ship <code>fa</code>, <code>nl</code>, <code>vi</code>, and <code>zh-TW</code> docs glossaries, so the docs translation pipeline and the Control UI language picker stay aligned across surfaces. Thanks @vincentkoc.</li>
|
||||
<li>Channels: add Yuanbao channel docs entrance so the Tencent Yuanbao bot appears in the channel listing and sidebar navigation. (#73443) Thanks @loongfay.</li>
|
||||
<li>Channels/Yuanbao: update plugin GitHub location to YuanbaoTeam/yuanbao-openclaw-plugin and add "yuanbao" alias to channel catalog. (#74253) Thanks @loongfay.</li>
|
||||
<li>Docker setup: add <code>OPENCLAW_SKIP_ONBOARDING</code> so automated Docker installs can skip the interactive onboarding step while still applying gateway defaults. (#55518) Thanks @jinjimz.</li>
|
||||
<li>Security policy: classify media/base64 decode and format-conversion overhead after configured acceptance limits as performance-only for GHSA triage unless a report demonstrates a limit bypass, crash, exhaustion, data exposure, or another boundary bypass. (#74311)</li>
|
||||
<li>Security/OpenGrep: add a precise OpenGrep rulepack, source-rule compiler, provenance metadata check, and PR/full scan workflows that validate first-party code and rulepack-only changes while uploading SARIF to GitHub Code Scanning. (#69483) Thanks @jesse-merhi.</li>
|
||||
</ul>
|
||||
<h3>Fixes</h3>
|
||||
<ul>
|
||||
<li>Providers/OpenAI Codex: preserve existing wrapped Codex streams during OpenAI attribution so PI OAuth bearer injection reaches ChatGPT/Codex Responses, and strip native Codex-only unsupported payload fields without touching custom compatible endpoints. (#75111) Thanks @keshavbotagent.</li>
|
||||
<li>Agents/tool-result guard: use the resolved runtime context token budget for non-context-engine tool-result overflow checks, so long tool-heavy sessions no longer compact early when <code>contextTokens</code> is larger than native <code>contextWindow</code>. Fixes #74917. Thanks @kAIborg24.</li>
|
||||
<li>Gateway/systemd: exit with sysexits 78 for supervised lock and <code>EADDRINUSE</code> conflicts so <code>RestartPreventExitStatus=78</code> stops <code>Restart=always</code> restart loops instead of repeatedly reloading plugins against an occupied port. Fixes #75115. Thanks @yhyatt.</li>
|
||||
<li>Agents/runtime: skip blank visible user prompts at the embedded-runner boundary before provider submission while still allowing internal runtime-only turns and media-only prompts, so Telegram/group sessions no longer leak raw empty-input provider errors when replay history exists. Fixes #74137. Thanks @yelog, @Gracker, and @nhaener.</li>
|
||||
<li>Auto-reply/group chats: fall back to automatic source delivery when a channel precomputes message-tool-only replies but the <code>message</code> tool is unavailable, so Discord/Slack-style group turns do not silently complete without a visible reply. Fixes #74868. Thanks @kagura-agent.</li>
|
||||
<li>Browser/gateway: share one browser control runtime across the HTTP control server and <code>browser.request</code>, and refresh browser profile config from the source snapshot, so CLI status/start honors configured <code>browser.executablePath</code>, <code>headless</code>, and <code>noSandbox</code> instead of falling back to stale auto-detection. Fixes #75087; repairs #73617. Thanks @civiltox and @martingarramon.</li>
|
||||
<li>Agents/subagents: bound automatic orphan recovery with persisted recovery attempts and a wedged-session tombstone, and teach task maintenance/doctor to reconcile those sessions so restart loops no longer require manual <code>sessions.json</code> surgery. Fixes #74864. Thanks @solosage1.</li>
|
||||
<li>Gateway/startup: skip pre-bind web-fetch provider discovery for credential-free <code>tools.web.fetch</code> config, so Docker/Kubernetes gateways bind even when optional fetch limits are present. Fixes #74896. Thanks @KoykL.</li>
|
||||
<li>Infra/tmp: tolerate concurrent temp-dir permission repairs by rechecking directories that another process already tightened, so parallel ACP subprocess startup no longer throws <code>Unsafe fallback OpenClaw temp dir</code>. Fixes #66867. Thanks @Kane808-AI and @jarvisz8.</li>
|
||||
<li>Signal: match group allowlists against inbound Signal group ids as well as sender ids, and process explicitly configured Signal groups without requiring mentions unless <code>requireMention</code> is set. Fixes #53308. Thanks @minupla and @juan-flores077.</li>
|
||||
<li>Slack: require bot-authored room messages with <code>allowBots=true</code> to come from an explicitly channel-allowlisted bot or from a room where an explicit Slack owner is present, so broad bot relays cannot run unattended. Fixes #59284. Thanks @andrewhong-translucent.</li>
|
||||
<li>Signal: bound <code>signal-cli</code> installer release and archive downloads with explicit timeouts, declared and streamed size checks, and partial-file cleanup. Fixes #54153. Thanks @jinduwang1001-max and @juan-flores077.</li>
|
||||
<li>Signal: derive <code>getAttachment</code> HTTP response caps from <code>channels.signal.mediaMaxMb</code> with base64 headroom, so inbound photos and videos no longer drop behind the 1 MiB RPC default. Fixes #73564. Thanks @heyhudson.</li>
|
||||
<li>Signal: keep the long-lived receive SSE monitor open while idle instead of applying the 10s RPC/check deadline, so <code>signal-cli</code> 0.14.3 event streams no longer reconnect before inbound messages arrive. Fixes #74741. Thanks @fgabelmannjr and @k7n4n5t3w4rt.</li>
|
||||
<li>Models/OpenAI Codex: restore <code>openai-codex/gpt-5.4-mini</code> for ChatGPT/Codex OAuth PI runs after live OAuth proof, and align the manifest, forward-compat metadata, docs, and regression tests so stale cron and heartbeat configs resolve again. Fixes #74451. Thanks @0xCyda, @hclsys, and @Marvae.</li>
|
||||
<li>Memory/runtime-deps: retain the native <code>node-llama-cpp</code> runtime only when local memory search is configured, so packaged installs can repair local embeddings without relying on unreachable global npm installs. Fixes #74777. Thanks @LLagoon3.</li>
|
||||
<li>Plugins/runtime-deps: replace stale symlinked mirror target roots before writing runtime-mirror temp files and skip rewriting already materialized hardlinks, so cross-version container upgrades no longer crash-loop on read-only image-layer paths while warm mirrors do less churn. Fixes #75108; refs #75069. Thanks @coletebou and @xiaohuaxi.</li>
|
||||
<li>Plugins/runtime-deps: keep bundled provider policy config loading from staging plugin runtime dependencies, so config reads no longer fail on locked-down <code>/var/lib/openclaw/plugin-runtime-deps</code> directories. Fixes #74971. Thanks @eurojojo.</li>
|
||||
<li>Plugins/runtime-deps: always write a dependency map in generated runtime-deps install manifests, so npm does not crash or prune staged bundled-plugin packages when the plan is empty. Fixes #74949. Thanks @hclsys.</li>
|
||||
<li>Security/outbound: strip re-formed HTML tags during plain-text sanitization so nested tag fragments cannot leave a CodeQL-detected <code><script></code> sequence behind. Thanks @vincentkoc.</li>
|
||||
<li>Security/secrets: compare credential bytes with padded timing-safe buffers instead of hashing candidate passwords before equality checks. Thanks @vincentkoc.</li>
|
||||
<li>Security/QQBot: sanitize debug log arguments before writing to <code>console.*</code>, so gateway payload fields cannot forge extra log lines when debug logging is enabled. Thanks @vincentkoc.</li>
|
||||
<li>QQBot: unify slash command auth and c2cOnly gating in the command registry, pass <code>allowQQBotDataDownloads</code> when sending slash command file attachments, align clear-storage with actual downloads directory, and add <code>/bot-me</code> to display sender user ID. (#73616) Thanks @cxyhhhhh.</li>
|
||||
<li>CLI/agents/status: keep <code>openclaw agents</code>, text <code>agents list</code>, and plain text <code>status</code> on read-only metadata paths so human output no longer preloads plugin runtimes or live channel scans before printing. Fixes #74195. Thanks @NianJiuZst.</li>
|
||||
<li>Agents/local models: derive context-window guard thresholds from the effective model window with 4k/8k safety floors, so small local models are no longer rejected by fixed 16k/32k preflight cutoffs. Fixes #42999. Thanks @chengjialu8888.</li>
|
||||
<li>PDF extraction: resolve PDF.js standard fonts from the installed package root and pass a filesystem path to the Node fallback extractor, so built-in font PDFs render without <code>file://</code> URL lookup failures. Fixes #51455; carries forward #70936, #54447, and #62175. Thanks @anyech, @JuanRdBO, and @solomonneas.</li>
|
||||
<li>Media: treat legacy Word/OLE attachments with <code>application/msword</code> or <code>application/x-cfb</code> MIME as binary so printable-looking <code>.doc</code> files are not embedded into prompts as text. Fixes #54176; carries forward #54380. Thanks @andyliu.</li>
|
||||
<li>Config: accept documented <code>browser.tabCleanup</code> keys in strict root config validation, so configured tab cleanup no longer fails before runtime reads it. Fixes #74577. Thanks @lonexreb and @ezdlp.</li>
|
||||
<li>Cron: validate disabled job schedule edits before persisting updates, so invalid cron changes no longer partially mutate stored jobs. Fixes #74459. Thanks @yfge.</li>
|
||||
<li>CLI/cron: warn when <code>openclaw cron add --message</code> omits a nonblank <code>--agent</code>, including blank agent values and session-key jobs, so scheduled agent-turn jobs make default-agent fallback explicit while system events stay quiet. Fixes #42196; carries forward #42245. Thanks @ethanclaw.</li>
|
||||
<li>CLI/progress: suppress nested progress spinners and line clears while TUI input owns raw stdin, so Crestodian <code>/status</code> no longer disturbs the active input row. (#75003) Thanks @velvet-shark.</li>
|
||||
<li>Channels/status: keep Telegram, Slack, and Google Chat read-only allowlist/default-target accessors on config-only paths, so status and channel summaries do not resolve SecretRef-backed runtime credentials. Thanks @eusine.</li>
|
||||
<li>Telegram: use durable message edits for streaming previews instead of native draft state, so generated replies no longer flicker through draft-to-message transitions that look like duplicates. (#75073) Thanks @obviyus.</li>
|
||||
<li>Telegram: clamp low long-polling client timeouts so configured <code>timeoutSeconds</code> values below the <code>getUpdates</code> poll window no longer force a fresh HTTPS connection every few seconds. Fixes #75114. Thanks @hpinho77.</li>
|
||||
<li>Active Memory: clarify the deprecated <code>modelFallbackPolicy</code> warning and config help so <code>modelFallback</code> is described as a chain-resolution last resort, not runtime failover. (#74602) Thanks @jeffrey701.</li>
|
||||
<li>Channels/Discord: keep read-only allowlist/default-target accessors from resolving SecretRef-backed bot tokens, so status and channel summaries no longer fail when tokens are only available in gateway runtime. (#74737) Thanks @eusine.</li>
|
||||
<li>Gateway/sessions: align session abort wait semantics across <code>chat</code>, <code>agent</code>, and <code>sessions</code> server methods so abort RPCs return after the targeted sessions actually halt instead of resolving early while runs are still draining. (#74751) Thanks @BunsDev.</li>
|
||||
<li>Agents/output: drop copied inbound metadata-only assistant replay turns before provider replay instead of synthesizing a placeholder, so Telegram and other channels cannot receive <code>[assistant copied inbound metadata omitted]</code> as model output. Fixes #74745. Thanks @adamwdear and @Marvae.</li>
|
||||
<li>Doctor/memory: suppress skipped embedding-readiness warnings for key-optional providers such as Ollama and LM Studio while preserving timeout and not-ready diagnostics. Fixes #74608 and #73882. Thanks @hclsys.</li>
|
||||
<li>Channels/groups: preserve observe-only turn suppression for prepared dispatch paths and restore deprecated channel turn runtime aliases, so passive observer/group flows stay silent while older plugins keep compiling. Thanks @vincentkoc.</li>
|
||||
<li>Feishu: skip empty-text messages (e.g. <code>{"text":""}</code>) that carry no media, so no blank user turn is written to the session and downstream LLM providers cannot reject the request with "messages must not be empty". (#74634) Thanks @xdengli and @hclsys.</li>
|
||||
<li>Feishu/Bitable: clean up newly created placeholder rows whose fields contain only default empty values while preserving meaningful link, attachment, user, number, boolean, and location values during create-app cleanup. (#73920) Carries forward #40602. Thanks @boat2moon.</li>
|
||||
<li>macOS app: keep attach-only mode and the Debug Settings launchd toggle marker-only, so launching with <code>--attach-only</code>/<code>--no-launchd</code> no longer uninstalls the Gateway LaunchAgent or drops active sessions. (#72174) Thanks @DolencLuka.</li>
|
||||
<li>macOS Canvas: stop auto-reloading the current A2UI host during push/eval/snapshot flows, so pushed A2UI content remains visible instead of returning to the empty Canvas shell. Fixes #73337. Thanks @Gr4via.</li>
|
||||
<li>Plugin SDK: restore the deprecated <code>plugin-sdk/zalouser</code> command-auth facade so published Lark/Zalo plugins that import it load on current hosts. Fixes #74702. Thanks @Goron01.</li>
|
||||
<li>Plugins/runtime-deps: include bundled provider plugins when <code>models.providers</code>, auth profiles, agent defaults, or subagent model refs configure that provider, while keeping inactive default-enabled provider plugins out of doctor repair. Refs #74307. Thanks @Skeptomenos.</li>
|
||||
<li>Plugins/runtime: resolve relative plugin <code>api.resolvePath</code> inputs against the plugin root instead of the host working directory, while keeping absolute and home paths user-resolved. Fixes #74718. Thanks @jimdawdy-hub.</li>
|
||||
<li>Plugins/runtime-deps: refresh mirrored root chunks through a temporary file before replacing the active copy, so failed refreshes do not delete chunks that running plugin imports still need. Thanks @shakkernerd.</li>
|
||||
<li>Plugins/runtime-deps: prefer <code>require</code> conditional exports when building staged dependency aliases, so CommonJS-only plugin runtime deps such as <code>ws</code> do not resolve to ESM wrappers under Jiti. Fixes #74547. Thanks @aderius.</li>
|
||||
<li>Bonjour/Gateway: cap flapping advertiser restarts in a sliding window, so mDNS probing/name-conflict loops disable discovery instead of churning indefinitely on constrained hosts. Refs #74209 and #74242. Thanks @ndj888 and @Sanjays2402.</li>
|
||||
<li>Plugins/runtime-deps: verify staged package entry files before reusing mirrored runtime roots, so browser-control repairs incomplete <code>ajv</code>/MCP SDK installs after update instead of failing after restart on a missing <code>ajv/dist/ajv.js</code>. Refs #74630. Thanks @spickeringlr.</li>
|
||||
<li>Heartbeat: resolve <code>responsePrefix</code> template variables with the selected provider, model, and thinking context before delivering alerts or suppressing prefixed <code>HEARTBEAT_OK</code> replies. Fixes #43064; repairs #43065; supersedes #46858. Thanks @yweiii and @JunJD.</li>
|
||||
<li>Memory/LanceDB: show full memory UUIDs in the <code>memory_forget</code> candidate list so agents can pass the displayed ID back to targeted deletion without hitting the full-UUID validator. (#66913) Thanks @amittell.</li>
|
||||
<li>File-transfer plugin: require canonical read-path preflight authorization for <code>file.fetch</code>, fail closed when <code>dir.fetch</code> preflight entries are missing, absolute, or traversing, and recheck returned archive entries before handing archive bytes to callers. Carries forward #74134. Thanks @omarshahine.</li>
|
||||
<li>Channels/Feishu: retry file-typed iOS video resource downloads as <code>media</code> after a Feishu/Lark HTTP 502 and preserve the original 502 when the fallback also fails. Fixes #49855; carries forward #50164 and #73986. Thanks @alex-xuweilong.</li>
|
||||
<li>Providers/Amazon Bedrock: expose the full Claude Opus 4.7 thinking profile (<code>xhigh</code>, <code>adaptive</code>, and <code>max</code>) for Bedrock model refs, while keeping Opus/Sonnet 4.6 on adaptive-by-default, so <code>/think</code> menus and validation match the Anthropic transport behavior. Fixes #74701. Thanks @prasad-yashdeep, @sparkleHazard, @Sanjays2402, and @hclsys.</li>
|
||||
<li>Plugins/tokenjuice: compile the bundled plugin against tokenjuice 0.7.0's published OpenClaw host types instead of a local compatibility shim, so package contract drift fails in OpenClaw validation before release. Thanks @vincentkoc.</li>
|
||||
<li>OAuth/secrets: ignore root-level Google OAuth <code>client_secret_*.json</code> downloads so local client-secret files do not appear as commit candidates. (#74689) Thanks @jeongdulee.</li>
|
||||
<li>Memory: mirror <code>sqlite-vec</code> into packaged bundled-plugin runtime deps for the default memory plugin, so builtin vector search does not lose its SQLite extension after upgrading to 2026.4.27. Fixes #74692. Thanks @mozi1924.</li>
|
||||
<li>Gateway/startup: bound local discovery advertisement during startup, so a stuck discovery plugin can no longer keep the Gateway from reaching ready. Fixes #73865; refs #74630 and #74633. Thanks @lpendeavors, @moltar-bot, and @Saboor711.</li>
|
||||
<li>Gateway/models: serve the last successful model catalog while stale reloads refresh in the background, so Gateway control-plane and OpenAI-compatible requests no longer block behind model-provider rediscovery after model config changes. Refs #74135, #74630, and #74633. Thanks @DerFlash, @moltar-bot, and @Saboor711.</li>
|
||||
<li>CLI/status: resolve read-only channel setup runtime fallback from the packaged OpenClaw dist root, so <code>status --all</code>, <code>status --deep</code>, channel, and doctor paths do not crash when an external channel plugin needs setup metadata. Fixes #74693. Thanks @giangthb.</li>
|
||||
<li>SDK/events: keep per-run SDK event streams from surfacing duplicate raw chat projection frames, while normalizing chat-only projection frames and preserving raw access through <code>rawEvents</code>. Refs #74704. Thanks @BunsDev.</li>
|
||||
<li>SDK: report Gateway terminal <code>agent.wait</code> timeout snapshots with lifecycle metadata as <code>timed_out</code> while keeping bare wait deadlines non-terminal. Thanks @clawsweeper.</li>
|
||||
<li>Google Meet: block managed Chrome intro/test speech until browser health proves the participant is in-call, and expose <code>speechReady</code> diagnostics so login, admission, permission, and audio-bridge blockers no longer look like successful speech. Refs #72478. Thanks @DougButdorf.</li>
|
||||
<li>Slack/commands: keep native command argument menus on select controls for encoded choice values up to Slack's option limit and truncate fallback button labels to Slack's button-text limit, so long valid choices no longer render invalid Slack blocks. Thanks @slackapi.</li>
|
||||
<li>Agents/Codex: flush accepted debounced steering messages before normal app-server turn cleanup, so inbound follow-ups acknowledged as queued are not dropped when the turn completes before the debounce fires. Thanks @vincentkoc.</li>
|
||||
<li>Slack/interactive replies: keep rendered buttons and selects within Slack Block Kit value and count limits, and align command argument select values with Slack's option limit, so overlong agent-authored choices no longer make Slack reject the whole block payload. Thanks @slackapi.</li>
|
||||
<li>Slack/interactive replies: drop overlong Block Kit button URLs while preserving valid callback values, so malformed link buttons no longer make Slack reject the whole interactive reply. Thanks @slackapi.</li>
|
||||
<li>Slack/commands: truncate native command argument-menu confirmation text to Slack's dialog limit, so long plugin arg names no longer make fallback buttons render invalid Block Kit payloads. Thanks @slackapi.</li>
|
||||
<li>Slack/exec approvals: cap native approval metadata context to Slack's element and text limits, so large approval details no longer make Slack reject the approval card. Thanks @slackapi.</li>
|
||||
<li>Slack/exec approvals: cap native approval update fallback text to Slack's message limit while preserving the rendered approval blocks, so long commands no longer make resolved or expired approval cards stay stale after <code>chat.update</code> rejects <code>msg_too_long</code>. Thanks @slackapi.</li>
|
||||
<li>Slack/commands: cap native command argument-menu fallback rows to Slack's message block limit, so large plugin choice lists no longer make Slack reject the generated menu. Thanks @slackapi.</li>
|
||||
<li>Slack/commands: drop fallback command argument buttons whose encoded values exceed Slack's button-value limit, so one oversized plugin choice no longer makes Slack reject the whole menu. Thanks @slackapi.</li>
|
||||
<li>Slack/messages: merge message-tool presentation and interactive blocks on Slack sends, so buttons and selects are no longer dropped when a structured message body is also present. Thanks @slackapi.</li>
|
||||
<li>Slack/messages: cap Block Kit fallback text to Slack's send limit while preserving the rendered blocks, so long context fallbacks no longer make rich Slack messages fail with <code>msg_too_long</code>. Thanks @slackapi.</li>
|
||||
<li>Slack/messages: cap Block Kit fallback text on message edits while preserving the rendered blocks, so long context fallbacks no longer make Slack reject <code>chat.update</code> calls with <code>msg_too_long</code>. Thanks @slackapi.</li>
|
||||
<li>Channels/WhatsApp: require Baileys outbound message ids before marking auto-replies delivered, so transcript text and ack reactions no longer make failed group replies look sent. Fixes #49225. Thanks @TinyTb.</li>
|
||||
<li>CLI/update: scope packaged Node compile caches by OpenClaw version and install metadata, so global installs no longer reuse stale compiled chunks after package updates. Thanks @pashpashpash.</li>
|
||||
<li>Channels/Voice call: keep pre-auth webhook in-flight limiting active when socket remote address metadata is missing, so slow-body requests from stripped-IP proxy paths still share the fallback bucket. (#74453) Thanks @davidangularme.</li>
|
||||
<li>Plugin SDK/testing: lazy-load TypeScript from the plugin test-contract runtime and add release checks for critical SDK contract entrypoint imports and bundle size, so published packages fail preflight before shipping ESM-incompatible or oversized contract helpers. Thanks @vincentkoc.</li>
|
||||
<li>Channels/Microsoft Teams: treat configured <code>19:...@thread.tacv2</code> and legacy <code>19:...@thread.skype</code> team/channel IDs as already resolved during startup, avoiding false <code>channels unresolved</code> warnings while preserving Graph name lookup for display-name entries. Fixes #74683. Thanks @dseravalli.</li>
|
||||
<li>CLI/browser: preserve parent flags while lazy-loading browser subcommands, so <code>openclaw browser --json open</code> and <code>openclaw browser --json tabs</code> keep machine-readable output after reparsing. Fixes #74574. Thanks @devintegeritsm.</li>
|
||||
<li>Exec/elevated: preserve <code>turnSourceChannel</code> as <code>messageProvider</code> on approval-followup runs so <code>tools.elevated.allowFrom.<provider></code> checks no longer fail with <code>provider=null</code> after the user approves an async elevated command. Fixes #74646. Thanks @xhd2015.</li>
|
||||
<li>Plugins/runtime-deps: add <code>openclaw plugins deps</code> inspection and repair with script-free package-manager defaults shared across plugin installers, so operators can repair missing bundled runtime deps without corrupting JSON output or blocking unrelated conflict-free deps. Thanks @vincentkoc.</li>
|
||||
<li>Agents/output: strip internal <code>[tool calls omitted]</code> replay placeholders from user-facing replies while preserving visible reply whitespace. Fixes #74573. Thanks @blaspat.</li>
|
||||
<li>Providers/Google Vertex: route authorized_user ADC credentials through OpenClaw's REST transport so Docker installs using gcloud application-default credentials no longer crash in the Google SDK before requests are sent. Fixes #74628. Thanks @frankhal2001-design.</li>
|
||||
<li>ACP/resolver: fall through to thread-bound session resolution when an explicit <code>--session</code> token cannot be resolved while preserving the bad-token diagnostic when no thread binding exists, so Discord slash commands that auto-fill the current thread ID as the positional ACP target no longer return "Unable to resolve session target" errors. Fixes #66299. Thanks @hclsys, @kindomLee, and @martingarramon.</li>
|
||||
<li>Agents/sessions: emit a terminal lifecycle backstop when embedded timeout/error turns return without <code>agent_end</code>, so Gateway sessions no longer stay stuck in <code>running</code> after failover surfaces a timeout. Fixes #74607. Thanks @millerc79.</li>
|
||||
<li>Gateway/diagnostics: include stuck-session reason hints and recovery skip causes in warnings, so operators can tell whether a lane is waiting on active work, queued work, or stale bookkeeping. Thanks @vincentkoc.</li>
|
||||
<li>Providers/DeepSeek: expose native DeepSeek V4 <code>xhigh</code> and <code>max</code> thinking levels through the provider <code>resolveThinkingProfile</code> hook so <code>/think xhigh|max</code> applies the intended effort instead of falling back to base levels. (#73008) Thanks @ai-hpc.</li>
|
||||
<li>Agents/Codex: bound embedded-run cleanup, trajectory flushing, and command-lane task timeouts after runtime failures, so Discord and other chat sessions return to idle instead of staying stuck in processing. Thanks @vincentkoc.</li>
|
||||
<li>Heartbeat/exec: consume successful metadata-only async exec completions silently so Telegram and other chat surfaces no longer ask users for missing command logs after <code>No session found</code>. Fixes #74595. Thanks @gkoch02.</li>
|
||||
<li>Web fetch: add a documented <code>tools.web.fetch.ssrfPolicy.allowIpv6UniqueLocalRange</code> opt-in and thread it through cache keys and DNS/IP checks so trusted fake-IP proxy stacks using <code>fc00::/7</code> can work without broad private-network access. Fixes #74351. Thanks @jeffrey701.</li>
|
||||
<li>OpenAI Codex: restore <code>/verbose full</code> persistence and app-server tool-output forwarding, and retry Gateway E2E temp-home cleanup so debug runs do not regress on stale validation or cleanup flakes. Thanks @vincentkoc.</li>
|
||||
<li>Anthropic/Meridian: preserve text and thinking content seeded on <code>content_block_start</code> in anthropic-messages streams, so <code>[thinking, text]</code> replies no longer persist as empty turns or trigger empty-response fallbacks. Fixes #74410. Thanks @vyctorbrzezowski.</li>
|
||||
<li>Channels/Matrix: complete the cross-signing handshake on <code>openclaw matrix verify confirm-sas</code> so the operator's other Matrix device clears its <code>Verifying…</code> loop instead of staying stuck after the agent confirms. (#74542) Thanks @nklock.</li>
|
||||
<li>CLI/status: honor channel-specific model context-window overrides when reporting effective context, so channel-scoped sessions reflect the active window in <code>openclaw status</code>. Thanks @HemantSudarshan.</li>
|
||||
<li>Sandbox/Docker: tolerate Docker daemon unavailability when sandbox mode is off, so doctor and preflight checks no longer fail on installs that do not run the Docker daemon. Fixes #73671. Thanks @kaseonedge.</li>
|
||||
<li>Control UI/mobile: persist mobile chat settings through Lit-managed state and route mobile navigation through the same view-state path so chat panel toggles survive transitions on small viewports. Thanks @BunsDev.</li>
|
||||
<li>Control UI/exports: align sidebar trigger affordances across the resizable divider, mobile layout, and exported-HTML transcript template so the sidebar toggle and exported transcript sidebar render with consistent hit areas and styling. Thanks @BunsDev.</li>
|
||||
<li>Control UI/chat: disable the page refresh affordance while a chat run is active so accidental refreshes do not abort an in-flight reply. Thanks @Angfr95 and @BunsDev.</li>
|
||||
<li>Memory/LanceDB: return real memory records from <code>openclaw ltm list</code> (with optional <code>--limit</code> and createdAt ordering) instead of an empty placeholder, so the CLI surface matches the documented LTM listing contract. (#67952) Thanks @zhangyue19921010.</li>
|
||||
<li>Media: include redacted per-attempt resize failures and resolved model input capabilities in vision-pipeline errors so ARM64 image failures are diagnosable without closing the remaining routing investigation. Refs #74552. Thanks @1yihui.</li>
|
||||
<li>Control UI/i18n: route zh-CN agent, debug, channel-refresh, and exec-approval copy through the locale source while preserving the English <code>Cron Jobs</code> agent tab label and the security-audit command styling. Carries forward #39692 repair context. Thanks @hepeng154833488 and @vincentkoc.</li>
|
||||
<li>Auto-reply: honor explicit <code>silentReply.direct: "allow"</code> for clean empty or reasoning-only direct chat turns while keeping the default direct-chat empty-response guard conservative. Fixes #74409. Thanks @jesuskannolis.</li>
|
||||
<li>OpenAI Codex: send a non-empty Responses input item when a Codex turn only has systemPrompt-backed instructions, avoiding ChatGPT backend 400s from <code>input: []</code>. Fixes #73820. Thanks @woodhouse-bot.</li>
|
||||
<li>Ollama: normalize provider-prefixed tool-call names at the native stream boundary so Kimi/Ollama calls such as <code>functions.exec</code> dispatch as <code>exec</code> instead of missing configured tools. Fixes #74487. Thanks @afurm and @carreipeia.</li>
|
||||
<li>Security/audit: resolve configured model aliases before model-tier and small-parameter checks, so alias-based GPT-5/Codex configs no longer report false weak-model warnings. Fixes #74455. Thanks @blaspat.</li>
|
||||
<li>CLI/agent: isolate Gateway-timeout embedded fallback runs under explicit <code>gateway-fallback-*</code> sessions so accepted Gateway runs cannot race transcript locks or replace the routed conversation session. Fixes #62981. Thanks @HemantSudarshan.</li>
|
||||
<li>CLI/QR/device-pair: reject malformed public setup URLs before issuing mobile pairing bootstrap tokens, while keeping valid bare host:port setup URLs supported. Thanks @Lucenx9.</li>
|
||||
<li>Models/UI: hide unauthenticated providers from the default Web chat, <code>/models</code>, and model setup pickers while keeping explicit full-catalog browse paths through <code>view: "all"</code>, <code>/models <provider> all</code>, and <code>models list --all</code>. Fixes #74423. Thanks @guarismo and @SymbolStar.</li>
|
||||
<li>Ollama: keep explicit local model runs on target-provider runtime hooks when PI discovery is skipped, so one-shot Ollama calls no longer cold-load unrelated provider runtimes before streaming. Fixes #74078. Thanks @sakalaboator.</li>
|
||||
<li>Slack/prompts: rely on Slack <code>interactiveReplies</code> guidance instead of generic <code>inlineButtons</code> config hints so enabled Slack button directives are not contradicted. Fixes #46647. Thanks @jeremykoerber.</li>
|
||||
<li>Slack/reactions: treat duplicate <code>already_reacted</code> responses as idempotent success so repeated agent reaction adds no longer surface as tool failures. Fixes #69005. Thanks @shipitsteven and @martingarramon.</li>
|
||||
<li>Channels/Discord: cool down Cloudflare/Error 1015 HTML 429 REST failures during startup application lookup and gateway metadata fetches, add <code>channels.discord.applicationId</code> as an app-id lookup bypass, sanitize HTML bodies before logging, and honor Retry-After before falling back to a conservative cooldown. Fixes #38853. (#74489) Thanks @djgeorg3 and @Garyko0730.</li>
|
||||
<li>Slack/tools: expose <code>fileId</code> in the shared message tool schema so <code>download-file</code> can receive Slack attachment IDs from inbound placeholders. Fixes #45574. Thanks @chadvegas.</li>
|
||||
<li>Exec: reject invalid per-call <code>host</code> values instead of silently falling back to the default target, so hostname-like values fail before commands run. Fixes #74426. Thanks @scr00ge-00 and @vyctorbrzezowski.</li>
|
||||
<li>Google/Gemini: send non-empty placeholder content when a Gemini run is triggered with empty or filtered user content, avoiding <code>contents is not specified</code> API errors. Thanks @CaoYuhaoCarl.</li>
|
||||
<li>Heartbeat: preserve non-task <code>HEARTBEAT.md</code> context around <code>tasks:</code> blocks and apply <code>agents.defaults.heartbeat</code> to all agents unless per-agent heartbeat entries restrict scope. Thanks @Sekhar03.</li>
|
||||
<li>Markdown: preserve paragraph breaks inside loose list items in shared outbound formatting while keeping tight list spacing stable. Thanks @Lucenx9.</li>
|
||||
<li>Build/Gateway: route restart, shutdown, respawn, diagnostics, command-queue cleanup, and runtime cleanup through one stable gateway lifecycle runtime entry so rebuilt packages do not strand long-running gateways on stale hashed chunks. Carries forward #73964. Thanks @pashpashpash.</li>
|
||||
<li>Memory/wiki: keep broad shared-source and generated related-link blocks from turning every page into a search hit, cap noisy backlinks, support all-term searches such as people-routing queries, and prefer readable page body snippets over generated metadata. Thanks @vincentkoc.</li>
|
||||
<li>Cron/Gateway: abort and bounded-clean up timed-out isolated agent turns before recording the timeout, so stale cron sessions cannot leave Discord or other chat lanes stuck in <code>processing</code> after a timeout. Thanks @vincentkoc.</li>
|
||||
<li>Agents/errors: suppress malformed streaming tool-call JSON fragments before they reach chat surfaces while preserving provider request-validation diagnostics. Fixes #59076; keeps #59080 as duplicate coverage. (#59118) Thanks @singleGanghood.</li>
|
||||
<li>CLI/models: restore provider-filtered <code>models list --all --provider <id></code> rows for providers without manifest/static catalog coverage, including Anthropic and Amazon Bedrock, while keeping the compatibility fallback off expensive availability and resolver paths. Thanks @shakkernerd.</li>
|
||||
<li>CLI/models: keep manifest auth-evidence credentials visible across <code>models status</code>, auth probes, and PI model discovery so workspace-scoped provider auth does not disagree between listing, probing, and execution. Thanks @shakkernerd.</li>
|
||||
<li>CLI/models: move local credential evidence such as Google Vertex ADC into generic plugin manifest setup metadata so the model-list auth index stays declarative without provider-specific runtime branches. Thanks @shakkernerd.</li>
|
||||
<li>CLI/models: compute the <code>models list</code> Auth column through one command-local provider auth index so row rendering no longer repeats auth profile, env, configured-provider, AWS, or synthetic-auth checks per model row. Thanks @shakkernerd.</li>
|
||||
<li>CLI/models: move the OpenAI listable catalog into the plugin manifest so <code>models list --all --provider openai</code> uses the manifest fast path instead of loading provider runtime normalization hooks. Thanks @shakkernerd.</li>
|
||||
<li>CLI/tools: keep the Gateway <code>tools.*</code> RPC namespace out of plugin command discovery and managed proxy startup, so stray commands like <code>openclaw tools effective</code> fail quickly instead of cold-loading plugin metadata. Refs #73477. Thanks @oromeis.</li>
|
||||
<li>CLI/status: keep default text <code>openclaw status --usage</code> on metadata-only channel scans unless <code>--deep</code> or <code>--all</code> is set, and send stray <code>openclaw tools --help</code> through the precomputed root-help fast path so latency-triage commands avoid plugin/runtime cold loads before printing. Refs #73477 and #74220. Thanks @oromeis and @NianJiuZst.</li>
|
||||
<li>Agents/diagnostics: trace embedded-run startup and preparation stage timings before model I/O, and warn only on severe slow stages, so Docker/VPS latency reports can identify whether plugin loading, auth/model resolution, tool inventory, bootstrap, MCP/LSP, resource loading, or stream setup is dominating pre-run latency without noisy normal logs. Refs #73428. Thanks @Dimaoggg, @quangtran88, and @Heyvhuang.</li>
|
||||
<li>Agents/subagents: cache persisted subagent run registry reads by file signature while preserving fresh-parse isolation, so busy gateways stop reparsing unchanged <code>subagents/runs.json</code> on controller/list/status hot paths. Refs #72338. Thanks @argus-as.</li>
|
||||
<li>Gateway/clients: wait for the event loop to become responsive before opening Gateway WebSocket RPC/probe/client connections while charging that readiness wait to caller timeouts, so Windows deferred module-evaluation stalls no longer turn healthy loopback gateways into false handshake timeouts across status, TUI, ACP, MCP, node-host, and plugin client paths. Refs #74279 and #48270. Thanks @wongcode and @joost-heijden.</li>
|
||||
<li>Gateway/Windows: read listener command lines via PowerShell before falling back to <code>wmic</code>, so restart health can recognize OpenClaw listeners on modern Windows installs and avoid long anonymous-port waits. Refs #74280. Thanks @zym951223.</li>
|
||||
<li>Plugins/runtime-deps: record process start-time in bundled dependency install locks and expire recycled-PID locks, so Docker gateway restarts recover from stale <code>.openclaw-runtime-deps.lock</code> directories without waiting through repeated five-minute timeouts. Fixes #74346. (#74361) Thanks @jhsmith409.</li>
|
||||
<li>Plugins/runtime-deps: memoize packaged bundled runtime dist-mirror preparation after the first successful pass while keeping source-checkout mirrors refreshable, so constrained Docker/VPS installs avoid repeated root scans before chat turns. Refs #73428, #73421, #73532, and #73477. Thanks @Dimaoggg, @oromeis, @oadiazp, @jmfraga, @bstanbury, @antoniusfelix, and @jkobject.</li>
|
||||
<li>Channels/Discord: treat bare numeric outbound targets that match the effective Discord DM allowlist as user DMs while preserving account-specific legacy <code>dm.allowFrom</code> precedence over inherited root <code>allowFrom</code>. (#74303) Thanks @Squirbie.</li>
|
||||
<li>Channels/Discord/Slack: share one DM policy/allowlist resolver across runtime, setup, allowlist editing, and doctor repair, so legacy <code>dm.policy</code> / <code>dm.allowFrom</code> compatibility migrates to canonical <code>dmPolicy</code> / <code>allowFrom</code> without divergent access checks. Thanks @Squirbie.</li>
|
||||
<li>Control UI: make the chat sidebar split divider focusable, keyboard-resizable, ARIA-described, and pointer-event based so sidebar resizing works without a mouse. Thanks @BunsDev.</li>
|
||||
<li>Agents/usage: keep PI embedded-run telemetry attributed to the resolved model provider instead of the PI harness label, so OpenRouter and other provider-backed turns report the right provider in session usage and traces. Thanks @vincentkoc.</li>
|
||||
<li>Agents/attribution: send OpenClaw attribution headers on native OpenAI and Codex traffic, including SDK transports, realtime voice and TTS, device-code auth, WHAM usage, and remote embeddings, so PI-origin defaults no longer leak into provider requests. Thanks @vincentkoc.</li>
|
||||
<li>Agents/auth: keep OAuth auth profiles inherited from the main agent read-through instead of copying refresh tokens into secondary agents, and refresh Codex app-server tokens against the owning store so multi-agent swarms avoid reused refresh-token failures. Fixes #74055. Thanks @ClarityInvest.</li>
|
||||
<li>Channels/Telegram: honor <code>ALL_PROXY</code> / <code>all_proxy</code> and service-level <code>OPENCLAW_PROXY_URL</code> when constructing the HTTP/1-only Telegram Bot API transport, so Windows and service installs that rely on those proxy settings no longer fall back to direct egress. Fixes #74014; refs #74086. Thanks @SymbolStar.</li>
|
||||
<li>Channels/Telegram: keep raw host/network-unreachable Bot API connect failures non-fatal and route tagged polling uncaught exceptions through the Telegram restart path, so transient reachability failures no longer kill the Gateway or leave long polling stuck. Fixes #60515; refs #74540. Thanks @HemantSudarshan, @thacid22, and @ewimsatt.</li>
|
||||
<li>Channels/Telegram: continue polling when <code>deleteWebhook</code> hits a transient network failure but <code>getWebhookInfo</code> confirms no webhook is configured, so startup does not retry cleanup forever after the webhook was already removed. Refs #74086; carries forward #47384. Thanks @clovericbot.</li>
|
||||
<li>Channels/Telegram: retry native quote replies without <code>reply_parameters.quote</code> when Telegram returns <code>QUOTE_TEXT_INVALID</code>, so stale or truncated quote excerpts no longer drop the whole reply. Fixes #74581. Thanks @moeedahmed.</li>
|
||||
<li>Channels/Telegram: apply strict safe-send retry to inbound final replies when grammY wraps a pre-connect failure, while leaving ambiguous plain network envelopes single-shot to avoid duplicate visible messages. Fixes #74203. Thanks @nanli2000cn.</li>
|
||||
<li>Channels/Telegram: surface polling liveness warnings in channel status and doctor when a running long-poller has not completed <code>getUpdates</code> after startup grace or its transport activity is stale, so silent polling failures no longer look clean. Refs #74299. Thanks @lolaopenclaw.</li>
|
||||
<li>Channels/Telegram: publish webhook runtime state and warn when <code>setWebhook</code> has not completed after startup grace, so webhook-mode accounts no longer look healthy while registration is still failing or retrying. Refs #74299. Thanks @lolaopenclaw and @martingarramon.</li>
|
||||
<li>Channels/Telegram: bound native command menu <code>deleteMyCommands</code> and <code>setMyCommands</code> Bot API calls and allow the same timeout-triggered transport fallback retry as other startup control calls, so Windows/WSL network stalls cannot leave command sync hanging behind an otherwise running provider. Refs #74086. Thanks @SymbolStar.</li>
|
||||
<li>ACP/commands: accept forwarded ACP timeout config controls in the OpenClaw bridge, treat unsupported discard-close controls as recoverable cleanup, and restore native <code>/verbose full</code> plus no-arg status behavior, so Discord command menus and nested ACP turns no longer fail on supported session controls. Thanks @vincentkoc.</li>
|
||||
<li>Codex harness: interrupt and release native app-server turns that go quiet after an OpenClaw dynamic-tool response without sending <code>turn/completed</code>, so Discord and other chat lanes do not stay stuck in <code>processing</code>. Thanks @vincentkoc.</li>
|
||||
<li>Codex harness: bound OpenClaw dynamic tool responses to 30 seconds and fail closed with an explicit tool result when the app-server bridge would otherwise strand the turn in <code>processing</code>. Thanks @vincentkoc.</li>
|
||||
<li>TUI/status: clear stale <code>streaming</code> footer state when a final event arrives after the active run was already cleared and no tracked runs remain, while preserving concurrent-run ownership and inactive local <code>/btw</code> terminal handling. Fixes #64825; carries forward #64842, #64843, #64847, and #64862. Thanks @briandevans and @Yanhu007.</li>
|
||||
<li>Channels/Discord: fail startup closed when Discord cannot resolve the bot's own identity and keep mention gating active when only configured mention patterns can detect mentions, so the provider no longer continues with a missing bot id. Fixes #42219; carries forward #46856 and #49218. Thanks @education-01 and @BenediktSchackenberg.</li>
|
||||
<li>Channels/Discord: split long CJK replies at punctuation and code-point-safe fallback boundaries so Discord chunking stays readable without corrupting astral characters. Fixes #38597; repairs #71384. Thanks @p3nchan.</li>
|
||||
<li>TUI: keep the streaming watchdog alive across active tool/lifecycle proof-of-life, pause it during disconnects, and reload history after stale reconnect runs so long-running chats stop flipping to false idle or hanging on stale streaming. Fixes #69081. Thanks @EenvoudJasper.</li>
|
||||
<li>Browser/gateway: ignore Playwright dialog-close races from <code>Page.handleJavaScriptDialog</code> so browser automation no longer crashes the Gateway when a dialog disappears before Playwright accepts it. (#40067) Thanks @randyjtw.</li>
|
||||
<li>Cron/Gateway: defer missed isolated agent-turn catch-up out of the channel startup window, so overdue cron work cannot starve Discord or Telegram while providers connect after a restart. Thanks @vincentkoc.</li>
|
||||
<li>Heartbeat/cron: defer heartbeat turns while cron work is active or queued, add opt-in <code>heartbeat.skipWhenBusy</code> for subagent/nested lane pressure, and retry busy skips without advancing the schedule so local Ollama hosts do not run heartbeat and cron prompts concurrently. Fixes #50773. Thanks @scottgl9.</li>
|
||||
<li>Agents/thinking: honor configured model <code>compat.supportedReasoningEfforts</code> entries that include <code>xhigh</code>, so custom OpenAI-compatible provider refs expose and validate <code>/think xhigh</code> consistently across command menus, Gateway sessions, agent CLI, and <code>llm-task</code>. Carries forward #48904. Thanks @Milchstrassse and @wufunc.</li>
|
||||
<li>Vercel AI Gateway: expose provider-owned <code>/think xhigh</code> for trusted OpenAI/Codex upstream refs and Claude adaptive thinking for Anthropic upstream refs, while leaving untrusted namespaced refs on base levels. Carries forward #41561. Thanks @Zcg2021.</li>
|
||||
<li>Plugins/runtime-deps: prune stale <code>openclaw-unknown-*</code> bundled runtime dependency roots during Gateway startup while keeping recent or locked roots, so old staging debris cannot keep growing across restarts. Thanks @vincentkoc.</li>
|
||||
<li>Plugins/runtime-deps: include ten more root-package runtime dependencies (<code>@agentclientprotocol/sdk</code>, <code>@lydell/node-pty</code>, <code>croner</code>, <code>dotenv</code>, <code>jiti</code>, <code>json5</code>, <code>jszip</code>, <code>markdown-it</code>, <code>tar</code>, <code>web-push</code>) in <code>MIRRORED_CORE_RUNTIME_DEP_NAMES</code> so they are mirrored into the runtime-deps tree alongside <code>semver</code> and <code>tslog</code>, preventing <code>Cannot find package 'X'</code> failures from core dist code (for example <code>qmd-manager</code>, <code>cron/schedule</code>, <code>infra/archive</code>, <code>infra/push-web</code>, <code>infra/backup-create</code>, <code>process/supervisor/adapters/pty</code>) when no enabled extension owns the dependency. Adds a static drift guard test that scans <code>src/</code> for value imports of root-package deps and fails CI when one is missing from the mirror allowlist or extension-owned set. Refs #74199. Thanks @maxpuppet.</li>
|
||||
<li>Ollama: compose caller abort signals with guarded-fetch timeouts for native <code>/api/chat</code> streams, so <code>/stop</code> and early cancellation still interrupt local Ollama requests that also carry provider timeout budgets. Refs #74133. Thanks @obviyus.</li>
|
||||
<li>Doctor/TTS: migrate legacy <code>messages.tts.enabled</code>, agent TTS, channel TTS, and voice-call plugin TTS toggles to <code>auto</code> mode during <code>openclaw doctor --fix</code>, matching the documented TTS config contract. Thanks @vincentkoc.</li>
|
||||
<li>CLI/logs: fall back to the configured Gateway file log when implicit loopback Gateway connections close or time out before or during <code>logs.tail</code>, so <code>openclaw logs</code> still works while diagnosing local-model Gateway disconnects. Refs #74078. Thanks @sakalaboator.</li>
|
||||
<li>MCP/plugins: stringify non-array plugin tool results with chat-content coercion instead of default object stringification, so MCP callers receive useful JSON/text content from plugin tools. Thanks @vincentkoc.</li>
|
||||
<li>Active Memory/QMD: make gateway-start QMD refresh opt-in via <code>memory.qmd.update.startup</code>, keep normal memory access lazy, preserve interactive file watching, and align watcher dependency/build ignores with QMD's scanner so cold gateway startup no longer imports or initializes QMD by default. Thanks @codexGW.</li>
|
||||
<li>Channels/Discord: remove Discord-owned queued-run timeout replies through the shared channel lifecycle queue while preserving message ordering and compatibility timeout constants, so long Discord turns stay governed by session/tool/runtime lifecycle instead of channel fallback errors. Thanks @codexGW.</li>
|
||||
<li>Agents/tools: clamp <code>process.poll</code> waits to 30 seconds, advertise that cap in the tool schema, and honor abort signals while waiting, so long command polls cannot pin agent responsiveness after cancellation. Thanks @vincentkoc.</li>
|
||||
<li>Plugin SDK: add tracked Discord component-message helpers and a Telegram account-resolution compatibility facade, so existing plugins using those subpaths resolve while new plugins stay on generic channel SDK contracts. Thanks @vincentkoc.</li>
|
||||
<li>Shared labels: preserve Unicode combining marks and NFC-equivalent accented text in group/channel slug normalization so non-Latin labels no longer lose meaningful characters. Fixes #58932; carries forward #58942 and #58995. Thanks @fengqing-git, @Starhappysh, and @koen666.</li>
|
||||
<li>Channels/Telegram: include probed video width and height when sending regular Telegram videos, so portrait clips render with the correct orientation instead of being stretched by clients. (#18915) Thanks @storyarcade.</li>
|
||||
<li>Docs/Hetzner: clarify that SSH tunnel access requires <code>AllowTcpForwarding local</code> before running <code>ssh -L</code>, so hardened VPS sshd configs do not block loopback Gateway access. Fixes #54557; carries forward #54564; refs #54954. Thanks @satishkc7, @blackstrype, and @Aftabbs.</li>
|
||||
<li>Agents/config: preserve authored <code>agents.defaults.params</code> and per-model <code>agents.defaults.models[].params</code> during narrowed internal config writes, so OpenAI transport overrides such as <code>transport: "sse"</code> and <code>openaiWsWarmup: false</code> are not stripped from <code>openclaw.json</code>. Fixes #73607; refs #73428. Thanks @quangtran88.</li>
|
||||
<li>Agents/model config: resolve per-model extra params through canonical model keys while preserving legacy double-prefixed fallback entries, so provider-prefixed model ids such as <code>openrouter/auto</code> keep their configured runtime params. (#44319) Thanks @HenryXiaoYang.</li>
|
||||
<li>Gateway/shutdown: report structured shutdown warnings and HTTP close timeout warnings through <code>ShutdownResult</code> while preserving lifecycle hook hardening. Carries forward #41296. Thanks @edenfunf.</li>
|
||||
<li>Control UI: keep Agents Overview and config-form select dropdowns on their configured value after options render while preserving inherited agent model placeholders. Fixes #40352; carries forward #52948. Thanks @xiaoquanidea.</li>
|
||||
<li>Agents/exec: launch zsh, bash, and fish host exec shells with startup files suppressed while preserving existing PATH fallbacks, so daemon env is not overridden by shell startup files. Carries forward #40200; fixes #40179. Thanks @NewdlDewdl.</li>
|
||||
<li>Plugins/QA: prebuild the private QA channel runtime before plugin gauntlet source runs so wrapper CPU/RSS measurements are not polluted by private QA dist rebuild work. Thanks @vincentkoc.</li>
|
||||
<li>Plugins/QA: add a Kitchen Sink plugin gauntlet that installs the external package, checks command inventory, MCP tools, channel status, provider turns, gateway RSS, CPU, and fatal log anomalies. Thanks @vincentkoc.</li>
|
||||
<li>Plugins/config: reuse the bundled plugin alias scan within a single config normalization pass, so Kitchen Sink-style plugin configs no longer peg Gateway CPU by repeatedly rescanning bundled metadata before agent turns. Thanks @vincentkoc.</li>
|
||||
<li>Plugins/channels: reject malformed runtime channel registrations that omit required config helpers before they can poison channel status. Thanks @vincentkoc.</li>
|
||||
<li>MCP/plugins: serialize raw plugin tool return values through the plugin-tools MCP bridge so Kitchen Sink-style tools no longer surface <code>undefined</code> content. Thanks @vincentkoc.</li>
|
||||
<li>Gateway/reload: bound default restart deferral and SIGUSR1 restart drain to five minutes while preserving explicit <code>deferralTimeoutMs: 0</code> indefinite waits, so stale active work accounting cannot block config reloads forever. Thanks @vincentkoc.</li>
|
||||
<li>Active Memory: register the prompt-build hook with the configured recall timeout plus setup grace instead of the 150s maximum budget, so default memory recall cannot delay turn startup for multiple minutes. Thanks @vincentkoc.</li>
|
||||
<li>Gateway/readiness: include an <code>eventLoop</code> diagnostic block in local or authenticated <code>/readyz</code> responses with event-loop delay (p99 and max), event-loop utilization, CPU core ratio, and a <code>degraded</code> flag, so operators can see when slow startups or runaway turns stall the event loop. Thanks @vincentkoc.</li>
|
||||
<li>Gateway/agents: schedule accepted agent runs after the accepted RPC frame has a chance to flush, so pre-turn prompt/context work is less likely to starve immediate <code>agent.wait</code> callers. Thanks @vincentkoc.</li>
|
||||
<li>CLI/update: tolerate stale memory-runtime import failures during best-effort CLI process teardown, so <code>openclaw update</code> replacing hashed runtime chunks before the finalizer runs no longer surfaces as exit-time <code>Cannot find module</code> noise. Thanks @vincentkoc.</li>
|
||||
<li>CLI/channels logs: reuse the rolling log-file resolver so <code>openclaw channels logs</code> falls back to the active dated log across date boundaries without reading unrelated custom log files. Fixes #42875; carries forward #42904 and #43043. Thanks @ethanclaw and @wdskuki.</li>
|
||||
<li>CLI/update: skip tracked plugins disabled in config during post-update plugin sync before npm, ClawHub, or marketplace update checks, preserving their install records without failing the update. Fixes #73880. Thanks @islandpreneur007.</li>
|
||||
<li>Control UI: fix Peak Error Hours showing incorrect hourly rates when the browser's timezone observes DST, by storing hourly message counts with UTC date keys and using DST-aware <code>Date.getHours()</code> for local conversion. Also extract <code>accumulateMessageCounts</code> helper to reduce duplicated daily/hourly aggregation logic. (#49396) Thanks @konanok.</li>
|
||||
<li>iMessage: normalize known leading attributedBody corruption markers on sent-message echo text keys so delayed reflected echoes with U+FFFD/U+FFFE/U+FFFF/FEFF prefixes are dropped without collapsing interior text. Fixes #59973; carries forward #59980 and #62191. Thanks @neeravmakwana and @maguilar631697.</li>
|
||||
<li>Security/audit: recognize dangerous node command IDs as valid <code>gateway.nodes.denyCommands</code> entries, so audit only warns on real typos or unsupported patterns. (#56923) Thanks @chziyue.</li>
|
||||
<li>Cron: treat implicit text payloads with agent-turn overrides as agent turns, preserving model overrides for scheduled text prompts instead of pruning them as system events. Fixes #28905. (#64060) Thanks @liaoandi.</li>
|
||||
<li>Telegram/exec approvals: stop treating general Telegram chat allowlists and <code>defaultTo</code> routes as native exec approvers; Telegram now uses explicit <code>execApprovals.approvers</code> or owner identity from <code>commands.ownerAllowFrom</code>, matching the first-pairing owner bootstrap path. Thanks @pashpashpash.</li>
|
||||
<li>Plugins/providers: keep Gateway startup primary-model discovery on metadata-only provider entries and reuse active non-speech capability providers even with explicit plugin entries, avoiding unnecessary provider registry loads during startup and media capability checks. Fixes #73729, #73835, and #73793; carries forward #73853 and #73794. Thanks @sg1416-zg, @brokemac79, and @poolside-ventures.</li>
|
||||
<li>Chat commands: route sensitive group <code>/diagnostics</code> and <code>/export-trajectory</code> approvals and results to a private owner route, preferring same-surface DMs before falling back to the first configured owner route, so Discord group invocations can land in Telegram when that is the primary owner interface. Thanks @pashpashpash.</li>
|
||||
<li>Gateway/hooks: keep successful <code>deliver:false</code> agent hooks silent, log a hook audit record for suppressed success announcements, and suppress fallback summaries after attempted hook delivery while still surfacing failed hook runs. Repairs #55761; builds on #36332 and #49234. Thanks @EffortlessSteven, @cioclawcode, and @BrennerSpear.</li>
|
||||
<li>Plugin SDK/Discord: restore a deprecated <code>openclaw/plugin-sdk/discord</code> compatibility facade and the legacy compat group-policy warning export for the published <code>@openclaw/discord@2026.3.13</code> package, covering its config, account, directory, status, and thread-binding imports while keeping new plugins on generic SDK subpaths. Fixes #73685; supersedes #73703. Thanks @rderickson9 and @SymbolStar.</li>
|
||||
<li>Channels/Discord: suppress duplicate gateway monitors when multiple enabled accounts resolve to the same bot token, preferring config tokens over default env fallback and reporting skipped duplicates as disabled. Supersedes #73608. Thanks @kagura-agent.</li>
|
||||
<li>CLI/health: build channel health summaries from inspected credential metadata plus runtime state, so <code>openclaw health --json</code> reports Discord <code>running</code>, <code>connected</code>, and <code>tokenSource</code> consistently with channel status. Fixes #44354. Thanks @ferenc-acs.</li>
|
||||
<li>Control UI/Talk: decode Google Live binary WebSocket JSON frames and stop queued browser audio on interruption or shutdown, so browser Talk leaves <code>Connecting Talk...</code> and barge-in no longer plays stale audio. Fixes #73601 and #73460; supersedes #73466. Thanks @Spolen23 and @WadydX.</li>
|
||||
<li>Channels/Discord: ignore stale route-shaped conversation bindings after a Discord channel is reconfigured to another agent, while preserving explicit focus and subagent bindings. Fixes #73626. Thanks @ramitrkar-hash.</li>
|
||||
<li>Agents/bootstrap: pass pending BOOTSTRAP.md contents through the first-run user prompt while keeping them out of privileged system context, and show limited bootstrap guidance when workspace file access is unavailable. Fixes #73622. Thanks @mark1010.</li>
|
||||
<li>ACP/tasks: classify parent-owned ACP sessions as background work regardless of persistent runtime mode, and close terminal stale ACP sessions when no active binding remains, so delegated ACP output reports through the parent task notifier instead of acting like a normal foreground chat session. Refs #73609. Thanks @joerod26.</li>
|
||||
<li>Tasks: keep terminal mirrored TaskFlow timestamps pinned to task completion time and let maintenance repair stale mirrors, so ACP terminal delivery updates no longer leave inconsistent flow audits. Refs #73609. Thanks @joerod26.</li>
|
||||
<li>Gateway/sessions: add conservative stuck-session recovery that releases only stale session lanes while active embedded runs, reply operations, and lane tasks remain serialized, so queued follow-ups can drain without aborting legitimate long-running turns. Refs #73581, #73655, #73652, #73705, #73647, #73602, #73592, and #73601. Thanks @WS-Q0758, @bryangauvin, @spenceryang1996-dot, @bmilne1981, @mattmcintyre, @Vksh07, and @Spolen23.</li>
|
||||
<li>Plugins: cache unchanged plugin manifest loads by file signature, reducing repeated JSON/JSON5 parsing and manifest normalization in bursty startup and runtime registry paths. Refs #73532 and #73647; carries forward #73678. Thanks @TheDutchRuler.</li>
|
||||
<li>Plugins/runtime-deps: cache unchanged bundled runtime mirror dist-file materialization decisions and close file-lock handles on owner-write failures, reducing repeated startup chunk scans and avoiding FileHandle-GC recovery stalls. Refs #73532. Thanks @oadiazp and @bstanbury.</li>
|
||||
<li>Plugins/runtime-deps: retry and defer transient cleanup failures for owned runtime staging directories so CLI startup no longer aborts after a successful bundled dependency swap. Refs #73903. Thanks @bobfreeman1989.</li>
|
||||
<li>Plugins/runtime-deps: cache bundled runtime-deps JSON/package files by file signature, reducing repeated staged-runtime metadata reads during bundled channel startup. Refs #73647 and #73705. Thanks @mattmcintyre and @bmilne1981.</li>
|
||||
<li>Plugins/runtime-deps: delegate bundled plugin dependency staging to complete npm/pnpm install plans with durable runtime state, removing retained-manifest and source-checkout cache reconciliation from Gateway startup. Refs #73532. Thanks @oadiazp, @bstanbury, and @jmfraga.</li>
|
||||
<li>Plugins/runtime-deps: replace Gateway-start root chunk dependency inference with explicit mirrored-root dependency metadata, reducing staged runtime scans while preserving lazy per-plugin installs. Refs #73532. Thanks @oadiazp and @bstanbury.</li>
|
||||
<li>Plugins/runtime-deps: run pnpm staged installs outside the repository workspace and disable pnpm release-age gates for exact bundled runtime dependency materialization, so bundled plugin dependency repair writes packages into the generated stage without blocking fresh packaged dependencies. Refs #73532. Thanks @oadiazp and @bstanbury.</li>
|
||||
<li>CLI/TUI: keep <code>chat.history</code> off model-catalog discovery so initial Gateway-backed TUI history loads cannot block behind slow provider/plugin model scans on low-core hosts. Refs #73524. Thanks @harshcatsystems-collab.</li>
|
||||
<li>Channels/WhatsApp: flag recently reconnected linked accounts in channel status even when the socket is currently healthy, so flapping WhatsApp Web sessions no longer look clean after a brief reconnect. Refs #73602. Thanks @Vksh07.</li>
|
||||
<li>Channels/WhatsApp: log shared dispatcher delivery failures with reply kind, message id, chat id, and connection id, so typing-without-send reports can identify whether the WhatsApp send path rejected a generated reply. Refs #74269. Thanks @tomcosta-git.</li>
|
||||
<li>Feishu: suppress distinct late <code>final</code> text deliveries after a streaming card has already closed, while keeping media attachments deliverable, so late-finals no longer reopen duplicate Feishu cards. Fixes #71977. (#72294) Thanks @MonkeyLeeT.</li>
|
||||
<li>Gateway: expose <code>gateway.handshakeTimeoutMs</code> in config, schema, and docs while preserving <code>OPENCLAW_HANDSHAKE_TIMEOUT_MS</code> precedence, so loaded or low-powered hosts can tune local WebSocket pre-auth handshakes without patching dist files. Supersedes #51282; refs #73592 and #73652. Thanks @henry-the-frog.</li>
|
||||
<li>Gateway/TUI/status: align configured and env-based WebSocket handshake budgets across local clients, probes, and fallback RPCs while preserving explicit status timeouts and paired-device auth fallback, so slow local gateways are not marked unreachable by a shorter client watchdog. Refs #73524, #73535, #73592, and #73602. Thanks @harshcatsystems-collab, @DJBlackhawk, and @Vksh07.</li>
|
||||
<li>Gateway/startup: return retryable <code>UNAVAILABLE</code> during the sidecar startup window and keep CLI/TUI/status clients retrying inside their existing timeout budget, so early connects no longer surface as terminal handshake failures. Fixes #73652. Thanks @spenceryang1996-dot.</li>
|
||||
<li>Gateway/proxy: bypass inherited proxy environment for local Gateway control-plane WebSockets to <code>localhost</code> as well as loopback IPs, so Windows/WSL proxy settings cannot intercept local CLI/TUI Gateway connections. Supersedes #73474; refs #73602. Thanks @DhtIsCoding.</li>
|
||||
<li>Doctor/Gateway: use a lightweight <code>status</code> RPC without channel summary work for doctor Gateway liveness, so slow health snapshots do not falsely drive service restart repair. Fixes #64400; supersedes #64511. Thanks @CHE10X and @EronFan.</li>
|
||||
<li>Agents/auth: scope external CLI credential discovery to configured providers during model auth status and startup prewarm, so opencode-only and other single-provider gateways do not block on unrelated Claude CLI Keychain probes. Fixes #73908. Thanks @Ailuras.</li>
|
||||
<li>Agents/model selection: resolve slash-form aliases before provider/model parsing and keep alias-resolved primary models subject to transient provider cooldowns, so cron and persisted sessions do not retry cooled-down raw aliases. Fixes #73573 and #73657. Thanks @akai-shuuichi and @hashslingers.</li>
|
||||
<li>Agents/Claude CLI: reuse already-cached macOS Keychain credentials for no-prompt Claude credential reads, so doctor/runtime checks do not miss fresh interactive Claude auth. Fixes #73682. Thanks @RyanSandoval.</li>
|
||||
<li>Agents/Claude CLI doctor: scope workspace and project-dir checks to agents that actually use the Claude CLI runtime, so non-default Claude agents no longer make the default agent look Claude-backed. Fixes #73903. Thanks @bobfreeman1989.</li>
|
||||
<li>Gateway/sessions: expose effective agent runtime metadata on session rows, <code>sessions.patch</code>, and local <code>openclaw sessions --json</code>, while keeping Claude CLI-backed rows on the canonical model provider so runtime backend and model identity are no longer conflated. Fixes #73090. Thanks @vishutdhar.</li>
|
||||
<li>Gateway/auth status: scope external CLI credential overlays to configured providers, runtimes, or profiles and keep status reads off new Keychain prompts, so single-provider Gateway configs no longer probe unrelated Claude/Codex/MiniMax auth on startup. Fixes #73908. Thanks @Ailuras.</li>
|
||||
<li>Agents/runtime status: expose effective agent runtime metadata in <code>agents.list</code>, Control UI agent panels, and <code>/agents</code>, and avoid rendering stale or cumulative CLI token totals as live context usage. Fixes #73660, #73578, and #45268. Thanks @spartman, @DashLabsDev, and @xyooz.</li>
|
||||
<li>Agents/transcripts: strip empty assistant text blocks while preserving valid text, images, and signatures, so Anthropic-style providers no longer reject sanitized transcript turns. Fixes #73640. Thanks @jowhee327.</li>
|
||||
<li>Gateway/sessions: preserve session keys on hidden lifecycle events so channel-routed runs still persist terminal session state and do not strand session status as running after Codex turn completion. Thanks @cathrynlavery.</li>
|
||||
<li>Providers/Bedrock: omit deprecated <code>temperature</code> for Claude Opus 4.7 Bedrock model ids, named and application inference profiles, including dotted <code>opus-4.7</code> refs, and classify the nested validation response for failover. Fixes #73663. Thanks @bstanbury.</li>
|
||||
<li>Gateway: raise the preauth/connect-challenge timeout to 15s so cold CLI starts on slower hosts have more time to process the WebSocket challenge before the Gateway closes the connection. Fixes #51469; refs #73592 and #62060. Thanks @GothicFox and @jackychen-png.</li>
|
||||
<li>CLI/status: fall back to a bounded local <code>status</code> RPC when loopback detail probes time out or report unknown capability, so reachable local gateways are no longer marked unreachable by slow read diagnostics. Fixes #73535; refs #48360, #62762, #51357, and #42019. Thanks @RacecarGuy, @justinschille, @DJBlackhawk, @tianyaqpzm, and @0xrsydn.</li>
|
||||
<li>CLI/gateway: reuse cached paired-device auth during <code>gateway probe</code> and report post-connect diagnostic failures as degraded reachability, so healthy local gateways are no longer marked unreachable after loopback auth or read timeouts. Fixes #48360. Thanks @RacecarGuy.</li>
|
||||
<li>Channels/Discord: give Discord Gateway WebSocket handshakes a 30s timeout so stalled TLS/network transitions emit an error and Carbon can continue its reconnect loop instead of leaving the bot silent until restart. Refs #50046. Thanks @codexGW.</li>
|
||||
<li>Mattermost/WebSocket: send protocol ping/pong keepalives and terminate stale sessions when pongs stop arriving, so silent TCP drops reconnect instead of leaving monitoring idle. Fixes #41837; carries forward #57621; refs #50138, #44160, and #51104. Thanks @JasonWang1124.</li>
|
||||
<li>Channels/Telegram: suppress standalone failed edit/write warning payloads when a user-facing assistant error reply already covers the turn, while keeping unresolved mutating failures visible behind success-looking or suppressed-error replies. Fixes #39631; refs #73750; carries forward #39636 and #39717; leaves #39406 for configurable delivery policy. Thanks @Bartok9 and @Bortlesboat.</li>
|
||||
<li>Control UI/agents: persist the Set Default action through <code>agents.list[].default</code> instead of writing the unsupported <code>agents.defaultId</code> field, so saved default-agent changes survive config validation. Fixes #65565; carries forward #72585. Thanks @luyao618.</li>
|
||||
<li>NVIDIA/NIM: persist the <code>NVIDIA_API_KEY</code> provider marker and mark bundled NVIDIA Chat Completions models as string-content compatible, so NIM models load from <code>models.json</code> and OpenAI-compatible subagent calls send plain text content. Fixes #73013 and #50107; refs #73014. Thanks @bautrey, @iot2edge, @ifearghal, and @futhgar.</li>
|
||||
<li>Channels/Discord: let text-only configs drop the <code>GuildVoiceStates</code> gateway intent and expose a bounded <code>/gateway/bot</code> metadata timeout with rate-limited fallback logs, reducing idle CPU and warning floods. Fixes #73709 and #73585. Thanks @sanchezm86 and @trac3r00.</li>
|
||||
<li>Agents/sessions: mark same-turn <code>sessions_send</code> and A2A reply prompts with an inter-session <code>isUser=false</code> envelope before they reach the model, so foreign session output no longer lands as bare active user text. Fixes #73702; refs #73698, #73609, #73595, and #73622. Thanks @alvelda.</li>
|
||||
<li>Channels/Telegram: fail closed when account-level public DM settings conflict with a restrictive top-level <code>allowFrom</code>, and require an effective wildcard before <code>dmPolicy="open"</code> behaves as public access. Fixes #73756; refs #73698. Thanks @Hilo-Hilo and @xace1825.</li>
|
||||
<li>Channels/security: move open-DM allowlist semantics into the shared policy helpers and align Discord, Slack, Mattermost, Matrix, Feishu, LINE, IRC, Google Chat, Zalo, Zalo User, QQ Bot, and Synology Chat so <code>dmPolicy="open"</code> is public only with an effective wildcard and otherwise still respects sender allowlists. Refs #73756 and #73698. Thanks @Hilo-Hilo and @xace1825.</li>
|
||||
<li>ACP/tasks: sweep orphaned parent-owned ACP sessions whose task records are gone, preserving bound persistent sessions but clearing unbound stale ACPX metadata so old child sessions cannot silently respawn into chat. Fixes #73609. Thanks @joerod26.</li>
|
||||
<li>Outbound/security: strip known internal runtime scaffolding such as <code><system-reminder></code> and <code><previous_response></code> at the final channel delivery boundary and keep Discord output on targeted tag stripping, so degraded harness replies cannot leak those tags to users. Fixes #73595. Thanks @gabrielexito-stack and @martingarramon.</li>
|
||||
<li>Security/Telegram: load Telegram security adapters in read-only audit/doctor, audit malformed Telegram DM <code>allowFrom</code> entries even when groups are disabled, and keep allowlist DM audits from counting stale pairing-store senders, so public/shared-DM risk checks stay accurate. Refs #73698. Thanks @xace1825.</li>
|
||||
<li>Plugins: remove hidden manifest, provider-owner, bootstrap, and channel metadata caches so plugin installs, manifest edits, and bundled-root changes are visible on the next metadata read while keeping runtime/module loader caches for actual plugin code. Thanks @shakkernerd.</li>
|
||||
<li>CLI/plugins: use plugin metadata snapshots for install slot selection and add opt-in plugin lifecycle timing traces, so plugin install avoids runtime-loading the plugin registry for metadata-only decisions. Thanks @shakkernerd.</li>
|
||||
<li>fix(plugins): restrict bundled plugin dir resolution to trusted package roots. (#73275) Thanks @pgondhi987.</li>
|
||||
<li>fix(security): prevent workspace PATH injection via service env and trash helpers. (#73264) Thanks @pgondhi987.</li>
|
||||
<li>Active Memory: allow <code>allowedChatTypes</code> to include explicit portal/webchat sessions and classify <code>agent:...:explicit:...</code> session keys before opaque session ids can shadow the chat type. Fixes #65775. (#66285) Thanks @Lidang-Jiang.</li>
|
||||
<li>Active Memory: allow the hidden recall sub-agent to use both <code>memory_recall</code> and the legacy <code>memory_search</code>/<code>memory_get</code> memory tool contract, so bundled <code>memory-lancedb</code> recall works without breaking the default <code>memory-core</code> path. Fixes #73502. (#73584) Thanks @Takhoffman.</li>
|
||||
<li>fix(device-pairing): validate callerScopes against resolved token scopes on repair [AI]. (#72925) Thanks @pgondhi987.</li>
|
||||
<li>Active Memory docs: document the <code>cacheTtlMs</code> 1000-120000 ms range and 15000 ms default so setup snippets do not lead users past the schema limit. Fixes #65708. (#65737) Thanks @WuKongAI-CMU.</li>
|
||||
<li>fix(agents): canonicalize provider aliases in byProvider tool policy lookup [AI]. (#72917) Thanks @pgondhi987.</li>
|
||||
<li>fix(security): block npm_execpath injection from workspace .env [AI-assisted]. (#73262) Thanks @pgondhi987.</li>
|
||||
<li>Tools/web_fetch: decode response bodies from raw bytes using declared HTTP, XML, or HTML meta charsets before extraction, so Shift_JIS and other legacy-charset pages no longer return mojibake. Fixes #72916. Thanks @amknight.</li>
|
||||
<li>Active Memory: skip payload-less <code>memory_search</code> transcript tool results when building debug telemetry, so newer empty entries no longer hide the latest useful debug payload. (#68773) Thanks @SimbaKingjoe.</li>
|
||||
<li>Active Memory: keep recall setup time from consuming the configured model timeout while giving the hook runner an explicit bounded budget for the plugin, so slow embedded-run setup no longer causes immediate recall timeouts. Fixes #72606. (#72620) Thanks @hyspacex.</li>
|
||||
<li>Channels/Discord: bound message read/search REST calls, route those actions through Gateway execution, and fall back to <code>CommandTargetSessionKey</code> for inbound hook session keys so Discord reads do not hang and hooks still fire when <code>SessionKey</code> is empty. Fixes #73431. (#73521) Thanks @amknight.</li>
|
||||
<li>Plugins/media: auto-enable provider plugins referenced by <code>agents.defaults.imageGenerationModel</code>, <code>videoGenerationModel</code>, and <code>musicGenerationModel</code> primary/fallback refs, so configured Google and MiniMax media providers do not stay disabled behind a restrictive plugin allowlist. Thanks @vincentkoc.</li>
|
||||
<li>Memory-core/dreaming: retry managed dreaming cron registration after startup when the cron service is not reachable yet, so the scheduled Memory Dreaming Promotion sweep recovers without waiting for heartbeat traffic. Fixes #72841. Thanks @amknight.</li>
|
||||
<li>Acpx/runtime: validate the runtime session mode at the <code>AcpxRuntime.ensureSession</code> wrapper boundary so callers that pass anything other than <code>persistent</code> or <code>oneshot</code> get a clear <code>ACP_INVALID_RUNTIME_OPTION</code> error instead of silently round-tripping through the encoded handle as a default <code>persistent</code> mode and later throwing <code>SessionResumeRequiredError</code>. Investigation context: #73071. (#73548) Thanks @amknight.</li>
|
||||
<li>CLI/infer: keep web-search fallback on missing provider API keys, preserve structured validation errors from the selected provider, and let per-request image describe prompts override configured media-entry prompts. (#63263) Thanks @Spolen23.</li>
|
||||
<li>Chat commands: include configured model-catalog reasoning metadata when building <code>/think</code> argument menus so Ollama Cloud and other provider-owned reasoning models show supported levels instead of only <code>off</code>. Fixes #73515; supersedes #73568. Thanks @danielzinhu99 and @neeravmakwana.</li>
|
||||
<li>Channels/Telegram: suppress generic tool-progress chatter when preview streaming is off, so non-streaming Telegram turns only deliver final replies while approvals, media, and errors still route normally. Refs #72363 and #72482. Thanks @neeravmakwana and @SweetSophia.</li>
|
||||
<li>CLI/model probes: add repeatable image <code>--file</code> inputs to <code>infer model run</code> for local and gateway multimodal model smokes, so vision models such as Ollama Qwen VL and Gemini can be tested through the raw model-probe surface. Fixes #63700. Thanks @cedricjanssens.</li>
|
||||
<li>CLI/model probes: request trusted operator scope for <code>infer model run --gateway --model <provider/model></code> so Gateway raw model smokes can use one-off provider/model overrides instead of being rejected before provider auth resolution. Fixes #73759. Thanks @chrislro.</li>
|
||||
<li>CLI/image describe: pass <code>--prompt</code> and <code>--timeout-ms</code> through <code>infer image describe</code> and <code>describe-many</code>, so custom vision instructions and slow local model budgets reach media-understanding providers such as Ollama, OpenAI, Google, and OpenRouter. Refs #63700. Thanks @cedricjanssens.</li>
|
||||
<li>Model selection: include the rejected provider/model ref and allowlist recovery hint when a stored session override is cleared, so local model selections such as Gemma GGUF variants do not fall back to the default with a generic message. Refs #71069. Thanks @CyberRaccoonTeam.</li>
|
||||
<li>OpenAI-compatible providers: drop malformed event-only or blank-data SSE frames before the OpenAI SDK stream parser sees them, so proxies that split <code>event:</code> from <code>data:</code> no longer crash streaming runs with <code>Unexpected end of JSON input</code>. Fixes #52802. Thanks @LyHug.</li>
|
||||
<li>Gateway/OpenAI-compatible streaming: strip <code><final></code> tags split across streamed model deltas before they reach SSE clients, so <code>/v1/chat/completions</code> no longer emits tag remnants or drops content when final-answer wrappers cross chunk boundaries. Fixes #63325. Thanks @tzwickl.</li>
|
||||
<li>Ollama: resolve explicitly selected signed-in <code>:cloud</code> models through <code>/api/show</code> when <code>/api/tags</code> omits them, so working models such as <code>gemini-3-flash-preview:cloud</code> and <code>deepseek-v4-pro:cloud</code> do not fail dynamic model resolution before the native <code>/api/chat</code> transport runs. Fixes #73909. Thanks @chtse53.</li>
|
||||
<li>Discord/exec approvals: keep the local <code>/approve</code> prompt when no native Discord approval runtime is active, and send a manual fallback notice when native approval delivery reaches no targets, so failed DM cards no longer leave approval turns silent or dependent on model-written shell commands. Fixes #73954; carries forward #74027. Thanks @guarismo and @brokemac79.</li>
|
||||
<li>Local model prompt caching: keep stable Project Context above volatile channel/session prompt guidance and stop embedding current channel names in the message tool description, so Ollama, MLX, llama.cpp, and other prefix-cache backends avoid avoidable full prompt reprocessing across channel turns. Fixes #40256; supersedes #40296. Thanks @rhclaw and @sriram369.</li>
|
||||
<li>Gateway/OpenAI-compatible API: guard provider policy lookup against runtime providers with non-array <code>models</code> values, so <code>/v1/chat/completions</code> no longer fails with <code>provider?.models?.some is not a function</code>. Fixes #66744; carries forward #66761. Thanks @MightyMoud, @MukundaKatta.</li>
|
||||
<li>WhatsApp/Web: pass explicit Baileys socket timings into every WhatsApp Web socket and expose <code>web.whatsapp.*</code> keepalive, connect, and query timeout settings so unstable networks can avoid repeated 408 disconnect and opening-handshake timeout loops. Fixes #56365. (#73580) Thanks @velvet-shark.</li>
|
||||
<li>WhatsApp/Web: recover recently active listeners when a post-408 reconnect keeps receiving transport frames but stops delivering app messages, while keeping group metadata fallback off Baileys sends. Fixes #63855 and #66920; refs #7433, #67986, #70856, #60007, and #72621. Thanks @legonhilltech-jpg, @octopuslabs-fl, @Kanorin-chan, and @stuswan.</li>
|
||||
<li>Channels/Telegram: persist native command metadata on target sessions so topic, helper, and ACP-bound slash commands keep their session metadata attached to the routed conversation. (#57548) Thanks @GaosCode.</li>
|
||||
<li>Channels/native commands: keep validated native slash command replies visible in group chats while preserving explicit owner allowlists for command authorization. (#73672) Thanks @obviyus.</li>
|
||||
<li>Pairing/doctor: bootstrap <code>commands.ownerAllowFrom</code> from the first approved DM pairing when no command owner exists, and have doctor explain missing owners so privileged slash commands are not accidentally unusable after onboarding. Thanks @pashpashpash.</li>
|
||||
<li>Telegram/exec: infer native exec approvers from <code>commands.ownerAllowFrom</code> and auto-enable the Telegram approval client when an owner is resolvable, so owner-only commands such as <code>/diagnostics</code> can be approved in Telegram without duplicate per-channel approver config. Thanks @pashpashpash.</li>
|
||||
<li>Auto-reply/session: carry the tail of user/assistant turns into the freshly-rotated transcript on silent in-reply session resets (compaction failure, role-ordering conflict) so direct-chat continuity survives the rebind. Fixes #70853. (#70898) Thanks @neeravmakwana.</li>
|
||||
<li>Skills: load grouped skill directories such as <code>skills/<group>/<skill>/SKILL.md</code> from configured skill roots while keeping grouped discovery capped for large directories. Fixes #56915. (#72534) Thanks @ottodeng, @MoerAI, and @i010542.</li>
|
||||
<li>Config: skip malformed non-string <code>env.vars</code> entries before env-reference checks, so config loading no longer crashes on JSON values like numbers or booleans. (#42402) Thanks @MiltonHeYan.</li>
|
||||
<li>Docker Compose: default missing config and workspace bind mounts to <code>${HOME:-/tmp}/.openclaw</code> so manual compose runs do not create invalid empty-source volume specs. (#64485) Thanks @jlapenna.</li>
|
||||
<li>Agents/context engines: preserve the child agent's configured <code>agentDir</code> when subagent cleanup re-resolves a context engine, so <code>onSubagentEnded</code> hooks keep operating on the correct per-agent state. (#67243) Thanks @jarimustonen.</li>
|
||||
<li>Channels/WhatsApp: restrict pairing verification replies to real inbound user content, preventing unsolicited prompts from receipts, typing indicators, presence updates, and other non-message Baileys upserts. Fixes #73797. (#73823) Thanks @hclsys.</li>
|
||||
<li>Configure/Ollama: show the configured Ollama model allowlist after Cloud only or Cloud + Local setup and skip slow per-model cloud metadata fetches. (#73995) Thanks @obviyus.</li>
|
||||
<li>Channels/WhatsApp: detect explicit group <code>@mentions</code> again when the bot's own E.164 is in <code>allowFrom</code>, so shared-number setups no longer skip group pings that directly mention the bot. Fixes #49317. (#73453) Thanks @juan-flores077.</li>
|
||||
<li>WhatsApp/reliability: publish real transport-liveness into WhatsApp channel status and force earlier reconnects on silent transport stalls, so quiet healthy sessions stay connected while wedged sockets recover before the later remote 408 path. (#72656) Thanks @Sathvik-1007.</li>
|
||||
<li>Core/channels: tighten selected runtime, media, and plugin edge-case handling while preserving existing behavior. Thanks @jesse-merhi.</li>
|
||||
<li>Channels/WhatsApp: strip leaked plural tool-call XML wrappers on every WhatsApp-visible outbound path and keep channel error payloads out of WhatsApp chats. (#71830) Thanks @rubencu.</li>
|
||||
<li>Agents/embedded-runner: inject the resolved OAuth bearer (and forward the run abort signal) on the boundary-aware embedded stream fallback so models that route through <code>openai-codex-responses</code> and other boundary-aware transports stop failing with <code>401 Unauthorized: Missing bearer or basic authentication in header</code>. Fixes #73559. (#73588) Thanks @openperf.</li>
|
||||
<li>Telegram/gateway: bound outbound Bot API calls and cache bundled plugin alias lookup so slow Telegram sends or WSL2 filesystem scans no longer wedge gateway replies. (#74210) Thanks @obviyus.</li>
|
||||
<li>Configure/GitHub Copilot: reuse existing Copilot auth during configure and show the provider's manifest model catalog in the model picker. (#74276) Thanks @obviyus.</li>
|
||||
<li>Configure/models: keep the model picker scoped to the selected manifest provider and enable its bundled plugin before catalog lookup, so choosing GitHub Copilot no longer falls back to Ollama or skips the catalog. (#74322) Thanks @obviyus.</li>
|
||||
<li>Auto-reply/subagents: reject <code>/focus</code> from leaf subagents and scope fallback target resolution to the requesting subagent's children, so subagents cannot bind conversations outside their control boundary. (#73613) Thanks @drobison00.</li>
|
||||
<li>Gateway/startup: skip inherited workspace startup memory for sandboxed spawned sessions without real-workspace write access, so <code>/new</code> no longer preloads host workspace memory into isolated child runs. (#73611) Thanks @drobison00.</li>
|
||||
<li>Agents/tool policy: validate caller group IDs against session or spawned context before applying group-scoped tool policies or persisting gateway group metadata, so forged group IDs cannot unlock more permissive tools. (#73720) Thanks @mmaps.</li>
|
||||
<li>Commands: keep channel-prefixed owner allowlist entries scoped to matching providers so webchat command contexts cannot inherit external channel owners. Thanks @zsxsoft.</li>
|
||||
<li>Auth/device pairing: bound bootstrap handoff token issuance, redemption, and approved pairing baselines to the documented per-role scope allowlist, so bootstrap approvals cannot persistently grant <code>operator.admin</code>, <code>operator.pairing</code>, or <code>node.exec</code> scopes. Thanks @eleqtrizit.</li>
|
||||
<li>Providers/GitHub Copilot: support the GUI/RPC wizard device-code auth flow so onboarding from non-TTY clients (gateway RPC bridge, GUI wizards) completes instead of returning empty profiles. Dangerous-state handling now distinguishes <code>access_denied</code> and <code>expired_token</code> from transport errors. (#73290) Thanks @indierawk2k2.</li>
|
||||
<li>Installer/Linux: warn before switching an unwritable npm global prefix to <code>~/.npm-global</code>, then tell users to run future global updates with <code>npm i -g openclaw@latest</code> without <code>sudo</code> so npm keeps using the redirected user prefix. Fixes #44365; carries forward #50479. Thanks @Sayeem3051.</li>
|
||||
<li>Gateway/plugins: enable the native <code>require()</code> fast path on Windows for bundled plugin modules so plugin loading uses <code>require()</code> instead of Jiti's transform pipeline, reducing startup from ~39s to ~2s on typical 6-plugin setups. Fixes #68656. (#74173) Thanks @galiniliev.</li>
|
||||
<li>macOS app: detect stale Gateway TLS certificate pins, automatically repair trusted Tailscale Serve rotations, and surface paired-but-disconnected Mac companion nodes so partial Gateway connections no longer look healthy. Thanks @guti.</li>
|
||||
</ul>
|
||||
<p><a href="https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md">View full changelog</a></p>
|
||||
]]></description>
|
||||
<enclosure url="https://github.com/openclaw/openclaw/releases/download/v2026.4.29/OpenClaw-2026.4.29.zip" length="50896802" type="application/octet-stream" sparkle:edSignature="YfQ25zMGgDv8XvHbdlL/s0SMJXyu763l5ppnfjiKOjSyxZY9sfoLaoXthcctFQDXA8isR1EEb/EEausu+XkFCA=="/>
|
||||
</item>
|
||||
<item>
|
||||
<title>2026.4.27</title>
|
||||
<pubDate>Wed, 29 Apr 2026 23:53:26 +0000</pubDate>
|
||||
@@ -529,409 +892,5 @@
|
||||
]]></description>
|
||||
<enclosure url="https://github.com/openclaw/openclaw/releases/download/v2026.4.26/OpenClaw-2026.4.26.zip" length="48222029" type="application/octet-stream" sparkle:edSignature="6wgFZUyyU09Y6nvD9T1Ufq7Plo0Wzfg+L9r80DCaNMMuwebcKWAsMVSP3RvhRhTxVMax8toUDYg3gb/vOiE5BA=="/>
|
||||
</item>
|
||||
<item>
|
||||
<title>2026.4.25</title>
|
||||
<pubDate>Mon, 27 Apr 2026 13:34:25 +0000</pubDate>
|
||||
<link>https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml</link>
|
||||
<sparkle:version>2026042590</sparkle:version>
|
||||
<sparkle:shortVersionString>2026.4.25</sparkle:shortVersionString>
|
||||
<sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion>
|
||||
<description><![CDATA[<h2>OpenClaw 2026.4.25</h2>
|
||||
<h3>Highlights</h3>
|
||||
<ul>
|
||||
<li>Voice replies get a full TTS upgrade: <code>/tts latest</code>, chat-scoped auto-TTS controls, personas, per-agent/per-account overrides, and new Azure Speech, Xiaomi, Local CLI, Inworld, Volcengine, and ElevenLabs v3 provider coverage. Thanks @leonchui, @zoujiejun, @solar2ain, @cshape, @xuruiray, @itsuzef, and @barronlroth.</li>
|
||||
<li>Plugin startup and install paths move to the cold persisted registry, cutting broad manifest scans while making plugin update, repair, provider discovery, and install metadata more deterministic. Thanks @vincentkoc and @shakkernerd.</li>
|
||||
<li>OpenTelemetry coverage expands across model calls, token usage, tool loops, harness runs, exec processes, outbound delivery, context assembly, and memory pressure with bounded low-cardinality attributes. Thanks @vincentkoc, @jlapenna, @Lidang-Jiang, and @oc-factus.</li>
|
||||
<li>Browser automation gets safer tab URLs, iframe-aware role snapshots, CDP readiness tuning, headless one-shot launch, and deeper browser doctor probes for slow hosts. Thanks @beat843796 and @BenediktSchackenberg.</li>
|
||||
<li>Control UI and setup flows add PWA/Web Push support, Crestodian first-run repair, TUI setup, context mode selection, and a shorter startup greeting. Thanks @eduardocruz, @SebTardif, and @kevinlin-openai.</li>
|
||||
<li>Install/update hardening covers Windows, macOS, Linux, Docker, bundled plugin runtime deps, Node service restarts, LaunchAgent token rotation, and mixed-version gateway verification. Thanks @Kobevictor, @igormf, @abhinas90, @jsompis, @Solvely-Colin, and @gucasbrg.</li>
|
||||
</ul>
|
||||
<h3>Changes</h3>
|
||||
<ul>
|
||||
<li>TTS/WhatsApp: add <code>/tts latest</code> read-aloud support with duplicate suppression and <code>/tts chat on|off|default</code> session-scoped auto-TTS overrides, completing the on-demand voice-note UX for current-chat replies. Fixes #66032.</li>
|
||||
<li>TTS/channels: resolve channel and account TTS overrides generically, enabling Feishu and QQBot accounts to deep-merge <code>channels.<channel>.accounts.<id>.tts</code> over global and per-agent TTS config. Thanks @sahilsatralkar.</li>
|
||||
<li>TTS/agents: allow <code>agents.list[].tts</code> to override global <code>messages.tts</code> for per-agent voices, and make <code>/tts audio</code>, <code>/tts status</code>, and the <code>tts</code> agent tool honor the active voice/provider override while keeping shared provider credentials and preferences in the existing TTS config surface.</li>
|
||||
<li>Providers/Azure Speech: add Azure Speech as a bundled TTS provider with Speech-resource auth, voice listing, SSML escaping, native Ogg/Opus voice-note output, and telephony output. (#51776) Thanks @leonchui.</li>
|
||||
<li>Google Meet: add calendar-backed attendance export workflows, export manifests, dry-run previews, and tool parity for meeting records.</li>
|
||||
<li>Control UI: add PWA install support and Web Push notifications for Gateway chat. (#44590) Thanks @eduardocruz.</li>
|
||||
<li>Browser automation: add safe tab URLs in agent responses plus a CDP-native role snapshot fallback with iframe-aware refs, cursor-clickable detection, target attach preparation, and <code>openclaw browser doctor --deep</code> live snapshot probing.</li>
|
||||
<li>CLI/image generation: expose generic <code>--background</code> on <code>openclaw infer image generate</code> and <code>openclaw infer image edit</code>, keep <code>--openai-background</code> as an OpenAI alias, and let fal image generation honor <code>--output-format png|jpeg</code>.</li>
|
||||
<li>Browser/config: allow local managed Chrome launch discovery and post-launch CDP readiness timeouts to be raised for slower hosts such as Raspberry Pi. Fixes #66803. Thanks @beat843796.</li>
|
||||
<li>Discord: allow <code>channels.discord.voice.model</code> to override the LLM used for voice channel responses while keeping STT and TTS on their existing media settings. (#64368) Thanks @mrdavey.</li>
|
||||
<li>Browser/CLI: add <code>openclaw browser start --headless</code> as a one-shot local managed browser launch override without rewriting persisted browser config. Thanks @BenediktSchackenberg.</li>
|
||||
<li>CLI/Crestodian/TUI: add the first-run setup helper, local planner fallback, full-TUI interactive Crestodian, startup progress indicators, context mode selector, and a shorter startup greeting. (#71720, #71760) Thanks @SebTardif and @kevinlin-openai.</li>
|
||||
<li>Plugins: migrate the local plugin registry automatically during package install/update, keeping install metadata in the plugin index while indexing existing plugin manifests for the new cold registry path. Thanks @vincentkoc and @shakkernerd.</li>
|
||||
<li>Plugins/doctor: make <code>openclaw doctor --fix</code> refresh the plugin index and cold registry index when needed without treating plugin install records as authored config. Thanks @vincentkoc and @shakkernerd.</li>
|
||||
<li>Plugins/hooks: add before-agent-finalize hooks, cron <code>jobId</code> hook context, bounded native permission fingerprints, and Codex MCP hook relay support. (#71765, #71758, #71707) Thanks @vincentkoc and @pashpashpash.</li>
|
||||
<li>Plugins/tokenjuice: bump the bundled tokenjuice runtime to 0.6.3. Thanks @vincentkoc.</li>
|
||||
<li>Diagnostics/OTEL: align model-call GenAI span attributes with OpenTelemetry stability opt-in semantics, keeping legacy <code>gen_ai.system</code> by default while emitting <code>gen_ai.provider.name</code> under <code>OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_latest_experimental</code>. Thanks @vincentkoc.</li>
|
||||
<li>Diagnostics/OTEL: support signal-specific OTLP endpoint overrides for traces, metrics, and logs via config or standard OTEL environment variables. Thanks @vincentkoc.</li>
|
||||
<li>Diagnostics/OTEL: emit bounded telemetry exporter health diagnostics for startup and log-export failures without exporting raw error text. Thanks @vincentkoc.</li>
|
||||
<li>Diagnostics/OTEL: export agent harness lifecycle telemetry as bounded <code>openclaw.harness.run</code> spans and <code>openclaw.harness.duration_ms</code> metrics so QA-lab, Codex, and future harnesses share one trace shape. Thanks @vincentkoc.</li>
|
||||
<li>Diagnostics/trace: propagate W3C <code>traceparent</code> headers from trusted model-call trace context to provider transports while replacing caller-supplied traceparent values. Thanks @vincentkoc.</li>
|
||||
<li>Diagnostics/Prometheus: add a bundled <code>diagnostics-prometheus</code> plugin with a protected gateway scrape route for low-cardinality diagnostics metrics. Thanks @vincentkoc.</li>
|
||||
<li>Plugins/CLI: add <code>openclaw plugins registry</code> for explicit persisted-registry inspection and <code>--refresh</code> repair without making normal startup rescan plugin locations. Thanks @vincentkoc.</li>
|
||||
<li>Plugins/CLI: make <code>openclaw plugins list</code> read the cold persisted registry snapshot by default, leaving module-aware diagnostics to <code>plugins doctor</code> and <code>plugins inspect</code>. Thanks @vincentkoc.</li>
|
||||
<li>Plugins/startup: move gateway startup plugin planning onto the versioned cold registry index, with postinstall repair for older registry files that predate startup metadata. Thanks @vincentkoc.</li>
|
||||
<li>Plugins/startup: normalize startup and provider plugin enablement through registry aliases so boot paths do not need the legacy manifest alias scan. Thanks @vincentkoc.</li>
|
||||
<li>Providers/plugins: resolve provider ownership, provider discovery scopes, and catalog-hook provider ids from the cold plugin registry instead of rescanning manifests on those paths. Thanks @vincentkoc.</li>
|
||||
<li>Plugins/registry: keep installed plugin index records focused on install/state/load paths and resolve plugin capabilities from manifests scoped to indexed plugins. Thanks @shakkernerd.</li>
|
||||
<li>Plugins/registry: route cold manifest and capability lookups through the installed plugin index so setup, channels, config, secrets, doctor, and provider metadata paths avoid broad plugin-root scans before runtime execution. Thanks @shakkernerd.</li>
|
||||
<li>CLI/models: speed up <code>models list --all --provider <id></code> for static manifest-backed providers by loading catalog rows through the installed plugin index instead of broad manifest scans or runtime suppression hooks. Thanks @shakkernerd.</li>
|
||||
<li>CLI/models: use OpenClaw Provider Index preview rows as the final cold fallback for installable providers, while keeping user config, installed manifests, and refreshed cache rows above provider-index metadata. Thanks @vincentkoc.</li>
|
||||
<li>Providers/plugins: keep onboarding and auth-choice setup lists on cold manifest/install metadata and add Provider Index install metadata for not-yet-installed provider plugins. Thanks @vincentkoc.</li>
|
||||
<li>Providers/plugins: keep provider setup guidance and configure auth imports on cold manifest metadata, with a regression guard against static provider-runtime imports on setup/configure list paths. Thanks @vincentkoc.</li>
|
||||
<li>CLI/capabilities: keep capability command registration from importing the models auth runtime until <code>model auth login</code> actually runs. Thanks @vincentkoc.</li>
|
||||
<li>CLI/configure: keep web-search configure prompts on cold plugin registry metadata until the user chooses managed search setup. Thanks @vincentkoc.</li>
|
||||
<li>Plugins/chat commands: refresh the persisted plugin registry after <code>/plugins enable</code> and <code>/plugins disable</code>, matching the CLI mutation path. Thanks @vincentkoc.</li>
|
||||
<li>Plugins/compat: mark <code>OPENCLAW_DISABLE_PERSISTED_PLUGIN_REGISTRY</code> as a deprecated break-glass switch and point operators at registry repair instead. Thanks @vincentkoc.</li>
|
||||
<li>Plugins/compat: expand the central compatibility registry with dated owners, replacements, and maximum three-month removal targets for legacy SDK, manifest, setup, registry-migration, and agent-runtime surfaces. Thanks @vincentkoc.</li>
|
||||
<li>Plugins/registry: ignore stale persisted registry reads when plugin policy no longer matches current config, and stamp generated registry files with a do-not-edit warning. Thanks @vincentkoc.</li>
|
||||
<li>Config/plugins: keep plugin command-alias validation on cold manifest metadata instead of importing the runtime alias resolver. Thanks @vincentkoc.</li>
|
||||
<li>Security/plugins: keep web-search credential presence checks on cold config, env, and manifest metadata instead of importing web-search provider runtime. Thanks @vincentkoc.</li>
|
||||
<li>Diagnostics/OTEL: surface provider request identifiers as bounded hashes on model-call diagnostics and span events, without exporting raw request IDs or metric labels. Thanks @Lidang-Jiang and @vincentkoc.</li>
|
||||
<li>Plugins/diagnostics: add metadata-only <code>model_call_started</code> and <code>model_call_ended</code> hooks for provider/model call telemetry without exposing prompts, responses, headers, request bodies, or raw provider request IDs. Thanks @vincentkoc.</li>
|
||||
<li>Diagnostics/OTEL: emit bounded context assembly diagnostics and export <code>openclaw.context.assembled</code> spans with prompt/history sizes but no prompt, history, response, or session-key content. Thanks @vincentkoc.</li>
|
||||
<li>Diagnostics/OTEL: export existing tool-loop diagnostics as <code>openclaw.tool.loop</code> counters and spans without loop messages, session identifiers, params, or tool output. Thanks @vincentkoc.</li>
|
||||
<li>Diagnostics/OTEL: export diagnostic memory samples and pressure as bounded memory histograms, counters, and pressure spans to help spot leak regressions without session or payload data. Thanks @vincentkoc.</li>
|
||||
<li>Diagnostics/OTEL: add the GenAI <code>gen_ai.client.token.usage</code> histogram for input/output model usage while keeping session identifiers and aggregate cache counters out of the semantic metric. Thanks @vincentkoc.</li>
|
||||
<li>Diagnostics/OTEL: add a bounded <code>openclaw.agent</code> label to OpenClaw token metrics so per-agent Grafana dashboards can group usage without exporting session identifiers. Thanks @oc-factus.</li>
|
||||
<li>Plugins/install: consolidate managed plugin install metadata into the state-managed plugin index at <code>plugins/installs.json</code>, replacing the temporary <code>plugins/installed-index.json</code> path and removing <code>plugins.installs</code> as an authored config surface. Thanks @vincentkoc and @shakkernerd.</li>
|
||||
<li>Diagnostics/OTEL: add the GenAI <code>gen_ai.client.operation.duration</code> histogram for model-call latency in seconds with bounded provider/model/API and error attributes. Thanks @vincentkoc.</li>
|
||||
<li>Diagnostics/OTEL: add GenAI usage token attributes to model-usage spans, including cache read/write input token counts without session identifiers or prompt/response content. Thanks @vincentkoc.</li>
|
||||
<li>Diagnostics/OTEL: include bounded GenAI operation, provider, and request-model attributes on model-usage spans so token usage remains self-describing without diagnostic identifiers. Thanks @vincentkoc.</li>
|
||||
<li>Diagnostics/OTEL: keep model-usage span GenAI provider attributes aligned with the existing semantic-convention opt-in policy, using legacy <code>gen_ai.system</code> unless latest experimental GenAI conventions are enabled. Thanks @vincentkoc.</li>
|
||||
<li>Diagnostics/OTEL: keep <code>gen_ai.request.model</code> present on GenAI token usage metrics with a bounded <code>unknown</code> fallback when model usage events do not include a model. Thanks @vincentkoc.</li>
|
||||
<li>Docs/OTEL: document the GenAI token and model-call duration metrics, model-usage span attributes, and <code>OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_latest_experimental</code> provider-attribute behavior. Thanks @vincentkoc.</li>
|
||||
<li>Docs: refresh the MCP, model provider, doctor, troubleshooting, BlueBubbles, media generation, TTS, subagents, skills, cron/tasks, exec approvals, and voice-call guides with structured Steps, Tabs, and Accordion content.</li>
|
||||
<li>Diagnostics/trace: add an internal traceparent propagation helper that only formats trusted dispatcher metadata, keeping plugin-emitted diagnostic traces out of outbound propagation by default. Thanks @vincentkoc.</li>
|
||||
<li>Diagnostics/OTEL: add bounded outbound message delivery lifecycle diagnostics and export them as low-cardinality delivery spans/metrics without message body, recipient, room, or media-path data. (#71471) Thanks @vincentkoc and @jlapenna.</li>
|
||||
<li>Diagnostics/OTEL: emit bounded exec-process diagnostics and export them as <code>openclaw.exec</code> spans without exposing command text, working directories, or container identifiers. (#71451) Thanks @vincentkoc and @jlapenna.</li>
|
||||
<li>Diagnostics/OTEL: support <code>OPENCLAW_OTEL_PRELOADED=1</code> so the plugin can reuse an already-registered OpenTelemetry SDK while keeping OpenClaw diagnostic listeners wired. (#71450) Thanks @vincentkoc and @jlapenna.</li>
|
||||
<li>Providers/Xiaomi: add MiMo TTS as a bundled speech provider with MP3/WAV output and voice-note Opus transcoding. Fixes #52376. (#55614) Thanks @zoujiejun.</li>
|
||||
<li>Providers/ElevenLabs: include <code>eleven_v3</code> in the bundled TTS model catalog so model selection surfaces can offer ElevenLabs v3. (#68321) Thanks @itsuzef.</li>
|
||||
<li>Providers/Local CLI TTS: add a bundled local command speech provider with file/stdout input, voice-note Opus conversion, and telephony PCM output. (#56239) Thanks @solar2ain.</li>
|
||||
<li>Providers/Inworld: add Inworld as a bundled speech provider with streaming TTS synthesis, voice listing, voice-note output, and PCM telephony output. (#55972) Thanks @cshape.</li>
|
||||
<li>Providers/Volcengine: add Volcengine/BytePlus Seed Speech as a bundled TTS provider with API-key auth, native Ogg/Opus voice-note output, and MP3 audio-file output. (#55641) Thanks @xuruiray.</li>
|
||||
<li>Android/Talk Mode: expose Talk Mode in the Voice tab with runtime-owned voice capture modes and microphone foreground-service escalation. Thanks @alex-latitude.</li>
|
||||
<li>Providers/LiteLLM: register <code>litellm</code> as an image-generation provider so <code>image_generate model=litellm/...</code> calls and <code>agents.defaults.imageGenerationModel.fallbacks</code> entries resolve through the LiteLLM proxy. Thanks @zqchris.</li>
|
||||
<li>Providers/fal: add Seedance 2.0 reference-to-video models with multi-image, video, and audio reference input mapping plus model-specific capability limits for <code>video_generate</code>. Thanks @shivanker.</li>
|
||||
<li>Codex harness: require Codex app-server <code>0.125.0</code> or newer and cover native MCP <code>PreToolUse</code>, <code>PostToolUse</code>, and <code>PermissionRequest</code> payloads through the OpenClaw hook relay.</li>
|
||||
<li>Agents/Codex: teach prompts and <code>agents_list</code> to surface native Codex app-server availability so agents prefer <code>/codex ...</code> over Codex ACP unless ACP/acpx is explicit. Thanks @vincentkoc.</li>
|
||||
<li>ACPX/Droid: add Factory Droid to the live ACP bind Docker matrix, including <code>.factory</code> settings staging, <code>FACTORY_API_KEY</code> forwarding, and the single-agent <code>test:docker:live-acp-bind:droid</code> recipe.</li>
|
||||
<li>TTS/personas: add provider-aware TTS personas with deterministic provider binding merges, <code>/tts persona</code> controls, gateway/CLI persona state, Google Gemini <code>audio-profile-v1</code> prompt wrapping, and OpenAI instruction mapping. (#70748) Thanks @barronlroth.</li>
|
||||
<li>Voice Wake: add trigger-based routing so macOS voice wake phrases can select a configured agent or session target, with Gateway routing APIs and node update events. (#30354) Thanks @longbiaochen.</li>
|
||||
</ul>
|
||||
<h3>Fixes</h3>
|
||||
<ul>
|
||||
<li>Auto-reply: poison inbound message dedupe after replay-unsafe provider/runtime failures so retries stay safe before visible progress but cannot duplicate messages after block output, tool side effects, or session progress. Fixes #69303; keeps #58549 and #64606 as duplicate validation. Thanks @martingarramon, @NikolaFC, and @zeroth-blip.</li>
|
||||
<li>Logging/sessions: apply configured redaction patterns to persisted session transcript text and accept escaped character classes in safe custom redaction regexes, so transcript JSONL no longer keeps matching sensitive text in the clear. Fixes #42982. Thanks @panpan0000.</li>
|
||||
<li>Agents/OpenAI: keep Responses web search compatible with minimal thinking by raising <code>web_search</code> requests to the lowest supported reasoning effort instead of sending a rejected minimal payload.</li>
|
||||
<li>Agents/tools: honor the <code>bundle-mcp</code> allowlist token when deciding whether bundled MCP tools are available, so restricted tool policies can still enable bundled MCP without exposing unrelated tools.</li>
|
||||
<li>Agents/model fallback: jump directly to a known later live-session model redirect instead of walking unrelated fallback candidates, while preserving the already-landed live-session/fallback loop guard. Fixes #57471; related loop family already closed via #58496. Thanks @yuxiaoyang2007-prog.</li>
|
||||
<li>Skills/memory: restore Chokidar v5 hot reloads by watching concrete skill and memory roots with filters, including SKILL.md removals and deleted skill folders without broad workspace recursion. Fixes #27404, #33585, and #41606. Thanks @shelvenzhou, @08820048, and @rocke2020.</li>
|
||||
<li>Discord/gateway: count failed health-monitor restart attempts toward cooldown and hourly caps, and evict stale account lifecycle state during channel reloads so repeated Discord gateway recovery cannot loop on old status. Fixes #38596. (#40413) Thanks @jellyAI-dev and @vashquez.</li>
|
||||
<li>Plugins/CLI: let flag-driven <code>openclaw channels add</code> install the selected channel plugin from its default source without opening an interactive prompt, fixing published npm Telegram setup in stdin-closed automation.</li>
|
||||
<li>Plugins/startup: load the default <code>memory-core</code> slot during Gateway startup when permitted so active-memory recall can call <code>memory_search</code> and <code>memory_get</code> without requiring an explicit <code>plugins.slots.memory</code> entry, while preserving <code>plugins.slots.memory: "none"</code>.</li>
|
||||
<li>Plugins/install: materialize plugin-owned root chunks in external bundled-runtime mirrors so staged plugin dependencies resolve under native ESM in packaged installs. Fixes #72058; supersedes #72084. Thanks @amnesia106 and @drvoss.</li>
|
||||
<li>Plugins/CLI: prefer native require for compiled bundled plugin JavaScript before jiti so read-only config, status, device, and node commands avoid unnecessary transform overhead on slow hosts. Fixes #62842. Thanks @Effet.</li>
|
||||
<li>Plugins/compat: inventory doctor-side deprecation migrations separately from runtime plugin compatibility so release sweeps preserve needed repairs while enforcing dated removal windows. Thanks @vincentkoc.</li>
|
||||
<li>Plugins/compat: add missing dated compatibility records for legacy extension-api, memory registration, provider hook/type aliases, runtime aliases, channel SDK helpers, and approval/test utility shims. Thanks @vincentkoc.</li>
|
||||
<li>Plugins/CLI: refresh the persisted registry after managed plugin files are removed so ClawHub uninstall cannot leave stale <code>plugins list</code> entries.</li>
|
||||
<li>Plugins/CLI: make plugin install and uninstall config writes conflict-aware, clear stale denylist entries on explicit reinstall/removal, and delete managed plugin files only after config/index commit succeeds.</li>
|
||||
<li>Plugins: fail <code>plugins update</code> when tracked plugin or hook updates error, keep bundled runtime-dependency repair behind restrictive allowlists, and reject package installs with unloadable extension entries.</li>
|
||||
<li>Gateway/chat: keep duplicate attachment-backed <code>chat.send</code> retries with the same idempotency key on the documented in-flight path so aborts still target the real active run. Fixes #70139. Thanks @Feelw00.</li>
|
||||
<li>Plugins: share package entrypoint resolution between install and discovery, reject mismatched <code>runtimeExtensions</code>, and cache bundled runtime-dependency manifest reads during scans.</li>
|
||||
<li>WhatsApp/Web: keep quiet but healthy linked-device sessions connected by basing the watchdog on WhatsApp Web transport activity, while retaining a longer app-silence cap so frame activity cannot mask a stuck session forever. Fixes #70678; carries forward the focused #71466 approach and keeps #63939 as related configurable-timeout follow-up. Thanks @vincentkoc and @oromeis.</li>
|
||||
<li>Onboarding/setup: keep first-run config reads, plugin compatibility notices, and post-model sanity checks on cold metadata paths unless the user chooses to browse all models, avoiding full plugin/runtime catalog work between prompts. Thanks @shakkernerd.</li>
|
||||
<li>Onboarding/auth: run manifest-owned provider auth choices through scoped setup providers so selecting OpenAI Codex browser/device auth no longer loads every provider runtime before OAuth starts. Thanks @shakkernerd.</li>
|
||||
<li>Onboarding/auth: keep the post-auth default-model policy lookup on manifest/setup metadata so the next prompt appears without loading broad provider runtime. Thanks @shakkernerd.</li>
|
||||
<li>Onboarding/models: keep skip-auth and provider-scoped model picker prompts off the full global model catalog path, and cache provider catalog hook resolution so setup no longer stalls after auth on large plugin registries. Thanks @shakkernerd.</li>
|
||||
<li>Gateway/Bonjour: suppress known @homebridge/ciao cancellation and network assertion failures through scoped process handlers so malformed mDNS packets or restricted VPS networking disable/restart Bonjour instead of crashing the gateway. Fixes #67578. Thanks @zenassist26-create.</li>
|
||||
<li>Discord: keep late clicks on already-resolved exec approval buttons quiet when elevated mode auto-resolved the request, while still surfacing real approval submission failures. Fixes #66906. Thanks @rlerikse.</li>
|
||||
<li>Agents/subagents: deliver completed yielded-subagent results back to no-thread requester routes via direct fallback when the dormant parent announce turn produces no visible reply, and add QA-lab coverage for the regression. Thanks @vincentkoc.</li>
|
||||
<li>Gateway/Tailscale: let Tailscale-authenticated Control UI operator sessions with browser device identity skip the device-pairing round trip while still rejecting device-less and node-role connections. Refs #71986. Thanks @jokedul.</li>
|
||||
<li>Doctor: honor <code>OPENCLAW_SERVICE_REPAIR_POLICY=external</code> by reporting gateway service health while skipping service install/start/restart/bootstrap, supervisor rewrites, and legacy service cleanup for externally managed environments. Thanks @shakkernerd.</li>
|
||||
<li>CLI/update: run package post-update doctor with <code>--fix</code> so package updates repair config migrations before restart. Thanks @shakkernerd.</li>
|
||||
<li>CLI/update: retry failed npm global updates with <code>--omit=optional</code> and ignore the superseded first failure when the fallback succeeds. Thanks @shakkernerd.</li>
|
||||
<li>Plugins/uninstall: migrate and reset <code>plugins.slots.contextEngine</code> alongside memory slots when plugin ids change or selected plugins are removed. Thanks @shakkernerd.</li>
|
||||
<li>Agents/Discord: keep raw <code>Agent failed before reply</code> runner failures out of Discord group/channel chats and show detailed runner errors in direct chats only when <code>/verbose</code> is enabled.</li>
|
||||
<li>UI/Windows: quote resolved pnpm <code>.cmd</code> launcher paths before spawning UI install/build/test commands so Node installs under <code>C:\Program Files</code> no longer fail as <code>C:\Program</code>. Fixes #45275. Thanks @Kobevictor, @stoppieboy, and @iubns.</li>
|
||||
<li>Codex/agent: translate <code>--thinking minimal</code> to <code>low</code> for modern Codex models (gpt-5.5, gpt-5.4, gpt-5.4-mini, gpt-5.2) at request build time so the first turn is accepted instead of paying a wasted call + retry-with-low fallback. Older Codex models still receive <code>minimal</code> directly. Fixes #71946. Thanks @hclsys.</li>
|
||||
<li>Plugins/uninstall: remove tracked plugin files from their recorded managed extensions root even when the current state directory points somewhere else, so <code>openclaw plugins uninstall --force</code> does not leave the plugin discoverable. Thanks @shakkernerd.</li>
|
||||
<li>Agents/runtime: add <code>agentRuntime.id</code> as the canonical config key, migrate legacy runtime-policy configs with <code>openclaw doctor --fix</code>, route canonical Anthropic models through <code>claude-cli</code> without passing CLI backend aliases to embedded harness selection, and load CLI backend owner plugins before channel startup. Fixes #71957. Thanks @WolvenRA.</li>
|
||||
<li>CLI/update: guard Windows scheduled-task stops by state and timeout so auto-update restart cannot hang indefinitely on <code>schtasks /End</code> before stale-listener cleanup. Fixes #69970. Thanks @yangswld and @sherlock-huang.</li>
|
||||
<li>Windows install/Lobster: execute <code>pnpm.exe</code> directly when <code>npm_execpath</code> points at the native pnpm binary, add an installed-package fallback for the Lobster embedded runtime, and include the Lobster runner regression test in Windows CI. Fixes #69456. Thanks @igormf.</li>
|
||||
<li>Gateway/install: refresh loaded gateway service installs when the current service embeds stale gateway auth instead of returning already-installed, avoiding LaunchAgent token-mismatch loops after token rotation. Fixes #70752. Thanks @hyspacex.</li>
|
||||
<li>Update: ignore bundled plugin <code>.openclaw-install-stage</code> directories during global install verification and packaged dist pruning so leftover runtime-dep staging files do not turn successful updates into <code>unexpected packaged dist file</code> failures. Fixes #71752. Thanks @waynegault.</li>
|
||||
<li>CLI/update: fail package updates when post-update plugin sync fails and refresh legacy npm plugin install records before trusting unchanged artifacts, preventing successful updates from restarting with stale or failed plugin state. Thanks @vincentkoc and @shakkernerd.</li>
|
||||
<li>Release/update: reject pre-populated bundled plugin <code>.openclaw-install-stage</code> directories, including mixed-case path variants, before package inventory generation so release tarballs cannot ship poisoned runtime-dependency staging debris. Fixes #71752. Thanks @hclsys.</li>
|
||||
<li>Node runtime: keep node-host retry timers alive across Gateway restarts and exit on terminal credential pauses so supervised nodes do not become silent zombies. Fixes #69800. Thanks @meroli28.</li>
|
||||
<li>Gateway/plugins: stop persisted WhatsApp auth state from activating bundled channel runtime-dependency repair during startup when <code>channels.whatsapp</code> is absent, avoiding npm/git stalls on packaged Linux installs. Fixes #71994. Thanks @xiao398008.</li>
|
||||
<li>Gateway/device tokens: enforce caller-scope containment inside token rotation and revocation so pairing-only sessions cannot mutate higher-scope operator tokens. Fixes #71990. Thanks @coygeek.</li>
|
||||
<li>Plugins/channels: keep security checks, thread-binding placement, provider summaries, health formatting, and message action labels on read-only or already-loaded channel metadata instead of importing full channel runtime. Thanks @shakkernerd.</li>
|
||||
<li>Plugins/status: keep config-only channel labels and status security summaries from importing plugin runtime modules just to render metadata. Thanks @shakkernerd.</li>
|
||||
<li>Sessions/channels: stop group-session metadata from loading bundled channel runtime just to classify <code>#channel</code> subjects, using only already-loaded channel capabilities on that path. Thanks @shakkernerd.</li>
|
||||
<li>Plugins/channels: keep native command and native skill <code>auto</code> defaults on static channel metadata so config, audit, and command-list checks do not load channel runtime just to read those defaults. Thanks @shakkernerd.</li>
|
||||
<li>CLI/channels: keep channel remove selection and all-channel capabilities summaries on read-only plugin metadata, loading channel runtime only for the selected mutation path. Thanks @shakkernerd.</li>
|
||||
<li>CLI/models: keep Provider Index preview rows out of <code>models list --all --provider <id></code> when the owning provider plugin is disabled, preserving config authority for cold catalog fallbacks. Thanks @shakkernerd.</li>
|
||||
<li>CLI/model runs: keep <code>openclaw infer model run</code> on explicit OpenRouter models from loading the full provider catalog or inheriting chat-agent silent-reply policy, restoring non-empty one-shot probe output. Fixes #68791. Thanks @limpredator.</li>
|
||||
<li>Installer/macOS: rerun Homebrew install steps without the gum spinner when raw-mode ioctl failures occur, and avoid claiming <code>node@24</code> was installed when the Homebrew keg binary is missing. Fixes #70411. Thanks @1fanwang and @dad-io.</li>
|
||||
<li>Installer: load nvm before Node.js detection so <code>curl | bash</code> installs respect nvm-managed Node instead of stale system Node. Fixes #49556. Thanks @heavenlxj.</li>
|
||||
<li>Installer/Windows: route PowerShell install failures through a top-level handler so <code>iwr ... | iex</code> returns control to the current shell while direct script-file runs still exit non-zero. Fixes #38054. Thanks @PwrSrg.</li>
|
||||
<li>CLI/Volta: respawn raw <code>openclaw</code> CLI runs through the named <code>node</code> shim when the current Node executable resolves to <code>volta-shim</code>, avoiding direct shim execution failures in non-interactive shells. Fixes #68672. Thanks @sanchezm86.</li>
|
||||
<li>Installer: warn when multiple npm global roots contain OpenClaw installs, showing active Node/npm/openclaw plus each install path and version so stale version-manager installs are visible. Fixes #40839. Thanks @zhixianio.</li>
|
||||
<li>Cron/tasks: recover completed cron task ledger records from durable run logs and job state before marking them <code>lost</code>, reducing false <code>backing session missing</code> audit errors for isolated cron runs and keeping offline CLI audit from treating its empty local cron active-job set as authoritative. Fixes #71963.</li>
|
||||
<li>Docker: copy patched dependency files into runtime images so downstream <code>pnpm install</code> layers keep working. Fixes #69224. Thanks @gucasbrg.</li>
|
||||
<li>Package: include patched dependency files in the published npm package so downstream installs can resolve <code>patchedDependencies</code>. (#69224) Thanks @gucasbrg and @vincentkoc.</li>
|
||||
<li>Plugins/channels: treat malformed bundled channel plugin loaders that return <code>undefined</code> as unavailable instead of crashing config and help paths. Fixes #69044. Thanks @frankhli843 and @vincentkoc.</li>
|
||||
<li>Scripts/watch: show corrupted dependency package-config recovery guidance when <code>gateway:watch</code> fails during watcher startup, without double-logging unrelated import failures. (#58780) Thanks @roytong9 and @vincentkoc.</li>
|
||||
<li>Signal: read signal-cli RPC, health checks, and SSE events through Node's HTTP client so Node 24/25 fetch regressions do not break Signal sends or inbound events. Fixes #51716 and #53040. Thanks @Barukimang, @minupla, and @vincentkoc.</li>
|
||||
<li>Skills/Docker: run npm-backed skill dependency installs with an OpenClaw-managed user prefix so non-root Docker images do not write to <code>/usr/local</code>. Fixes #59601. Thanks @chanjarster and @vincentkoc.</li>
|
||||
<li>Agents/runtime: submit heartbeat, cron, and exec wakeups as transient runtime context instead of visible user prompts, keeping synthetic system work out of chat transcripts. Fixes #66496 and #66814. Thanks @jeades and @mandomaker.</li>
|
||||
<li>Telegram: include native quote excerpts automatically for threaded replies and reply tags when the original Telegram text is available, without adding another config knob. Fixes #6975. Thanks @rex05ai.</li>
|
||||
<li>Node/Linux: make <code>openclaw node install</code> enable and restart the <code>openclaw-node</code> systemd unit instead of the gateway unit on node-only VMs. Fixes #68287. Thanks @dlebee-agent.</li>
|
||||
<li>Browser/CDP: retry transient raw-CDP WebSocket handshake failures before any browser command is sent, and reconnect stale persistent Playwright CDP sessions for safe tab-list reads without replaying mutating browser actions. Fixes #67728.</li>
|
||||
<li>Gateway/Linux: retry <code>systemctl --user enable</code> after a second daemon reload when the freshly written gateway unit is not visible yet on migrated systemd installs. Fixes #65184. Thanks @liushuaiiu.</li>
|
||||
<li>Telegram: preserve exact selected quote text when sending native quote replies, and retry with legacy replies if Telegram rejects quote parameters. (#71952) Thanks @rubencu.</li>
|
||||
<li>Plugins/CLI: preserve manifest name, description, format, and source metadata in cold <code>openclaw plugins list</code> output without importing plugin runtime. Thanks @shakkernerd.</li>
|
||||
<li>Security/audit: read channel exposure and plugin allowlist ownership from read-only plugin index metadata so cold audits do not depend on loaded channel runtime. Thanks @shakkernerd.</li>
|
||||
<li>Plugins/chat: keep <code>/plugins list</code>, <code>/plugins enable</code>, and <code>/plugins disable</code> on the persisted plugin index path so chat plugin management does not load diagnostic/runtime plugin registries before execution. Thanks @shakkernerd.</li>
|
||||
<li>Plugins/doctor: read workspace plugin status and legacy web-search ownership through installed-index manifest metadata instead of broad manifest registry scans. Thanks @shakkernerd.</li>
|
||||
<li>CLI/agents: read channel provider status from read-only plugin index metadata for text <code>agents list</code> output instead of the loaded channel registry. Thanks @shakkernerd.</li>
|
||||
<li>Logging: redact configured secret patterns at console and file-log sink exits so credentials that reach the logger are masked before terminal display or JSONL persistence. Fixes #67953. Thanks @Ziy1-Tan.</li>
|
||||
<li>Gateway/services: refuse process and service mutations from an older OpenClaw binary when the config was last written by a newer version, preventing split-brain installs from stopping or rewriting newer gateway services. Fixes #57079.</li>
|
||||
<li>Gateway: reserve <code>/healthz</code> and <code>/readyz</code> ahead of plugin, canvas, and Control UI HTTP stages so liveness/readiness probes still answer when a later route handler stalls. Fixes #69674. Thanks @Xike-Creek.</li>
|
||||
<li>Logging: load <code>logging.file</code> and redaction settings directly from the active OpenClaw config path in bundled runtimes, so packaged gateways stop falling back to <code>/tmp/openclaw</code>. Fixes #59370, #67168, and #61295. Thanks @KeaneYan, @Pan9hu, and @zsjlovelike.</li>
|
||||
<li>Logging: rotate file logs at <code>logging.maxFileBytes</code>, keep bounded numbered archives, and make long-lived rolling loggers follow the current-day file instead of suppressing diagnostics or writing stale dated files. Fixes #58583 and #62381. Thanks @jpeghead and @zhaoleink.</li>
|
||||
<li>Agents/groups: treat clean empty assistant stops as silent <code>NO_REPLY</code> only for always-on groups where silent replies are allowed, while keeping direct and mention-gated sessions on the incomplete-turn retry path. Thanks @MagnaAI.</li>
|
||||
<li>macOS/Node: keep native remote app nodes from advertising <code>browser.proxy</code>, start browser-capable CLI node services through the restored <code>openclaw node start</code> command, and show an actionable browser-control error when the local control service is missing. Fixes #66637.</li>
|
||||
<li>Gateway/update: fail package updates when the restarted managed gateway reports the wrong version, including fallback restarts and JSON mode, avoiding false-success mixed-version restarts after macOS LaunchAgent updates. Fixes #71835. Thanks @abhinas90 and @jsompis.</li>
|
||||
<li>Gateway/update: warn before package updates and bundled plugin runtime-dependency repairs when the target volume appears low on disk space, without blocking installs on best-effort filesystem checks. Fixes #71835. Thanks @abhinas90 and @jsompis.</li>
|
||||
<li>Plugins/runtime deps: surface activated plugin load failures in health and fail package-update restart verification or doctor repair when bundled runtime deps still cannot load, avoiding false-success repairs. (#71883) Thanks @Solvely-Colin.</li>
|
||||
<li>Gateway/Linux: include fnm <code>aliases/default/bin</code> in generated service PATHs and let doctor accept either modern fnm aliases or the legacy <code>current/bin</code> symlink, avoiding false PATH repair prompts. Fixes #68169. Thanks @richard-scott.</li>
|
||||
<li>Installer/Linux: run apt installs with noninteractive dpkg and needrestart settings so fresh Ubuntu 24.04 <code>curl | bash</code> installs do not hang while installing Node.js, Git, or build tools. Fixes #41146. Thanks @iht76, @alexcarv318, @cs3gallery, @firofame, and @cgdusek.</li>
|
||||
<li>Providers/Bedrock: defer the AWS SDK import until Bedrock discovery actually runs so plugin registration and setup stay lightweight on cold start. Fixes #71690. Thanks @jarvis-ai-gregmoser.</li>
|
||||
<li>Installer/macOS: stop immediately when Homebrew <code>node@24</code> installation fails and avoid printing PATH advice for missing Homebrew Node installs. Fixes #70411. Thanks @1fanwang.</li>
|
||||
<li>WhatsApp: remove ack reactions after a visible reply when <code>messages.removeAckAfterReply</code> is enabled, matching other reaction-capable channels. Fixes #26183. Thanks @MrUnforsaken.</li>
|
||||
<li>Providers/Z.AI: map OpenClaw thinking controls to Z.AI's <code>thinking</code> payload and add opt-in preserved thinking replay via <code>params.preserveThinking</code>, so GLM 5.x can keep prior <code>reasoning_content</code> when requested. Fixes #58680. Thanks @xuanmingguo.</li>
|
||||
<li>Channels/status: keep read-only channel lists on manifest and package metadata by default, loading setup runtime only for explicit fallback callers. Thanks @shakkernerd.</li>
|
||||
<li>Plugins: scope setup and web-provider metadata manifest reads to explicit plugin ids when callers already know the owning plugin set. Thanks @vincentkoc.</li>
|
||||
<li>Plugins/onboarding: defer onboarding install-record index writes until the guarded config commit so setup failures cannot leave the plugin index ahead of <code>openclaw.json</code>. Thanks @shakkernerd.</li>
|
||||
<li>Plugins/registry: resolve web provider ownership from the installed plugin index instead of broad manifest scans on secret, tool, and pricing paths. Thanks @shakkernerd.</li>
|
||||
<li>Config/providers: accept <code>video</code> and <code>audio</code> in configured model <code>input</code> values and preserve them in provider catalog entries. Fixes #20721. Thanks @alvinttang.</li>
|
||||
<li>Models/auth: honor the parent <code>--agent</code> flag for auth write commands (<code>add</code>, <code>login</code>, <code>setup-token</code>, <code>paste-token</code>, and the GitHub Copilot shortcut) so OAuth/API-key/token results are written to the requested agent store instead of the default agent. Fixes #71864. (#71933) Thanks @balric-seo.</li>
|
||||
<li>TTS: strip model-emitted TTS directives from streamed block text before channel delivery, including directives split across adjacent blocks, while preserving the accumulated raw reply for final-mode synthesis. Fixes #38937.</li>
|
||||
<li>TTS: keep explicit <code>provider=...</code> directive keys scoped to that provider and warn on unsupported keys instead of letting another speech provider consume overlapping keys. Fixes #60131.</li>
|
||||
<li>TTS/Feishu: normalize final-mode streamed TTS-only audio before delivery so generated voice-note files use the same safe media path and native voice routing as normal final replies. Fixes #71920.</li>
|
||||
<li>Feishu: transcribe inbound voice-note audio with the shared media audio path before agent dispatch and keep raw Feishu <code>file_key</code> payloads out of message text. Fixes #67120 and #61876.</li>
|
||||
<li>Tasks: terminalize async Gateway agent task records from the Gateway run result while preserving aborted, failed, and cancelled outcomes instead of leaving completed runs stuck as active or lost. (#71905) Thanks @likewen-tech.</li>
|
||||
<li>WhatsApp: let authorized group voice-note transcripts satisfy mention gating before reply dispatch, while keeping unmentioned transcripts in pending group history. Fixes #44908.</li>
|
||||
<li>Media understanding: carry channel voice-note preflight state into attachment selection so WhatsApp, Feishu, Telegram, and Discord do not transcribe the same inbound audio twice. Fixes #70580.</li>
|
||||
<li>TTS/BlueBubbles: deliver compatible auto-TTS audio as iMessage voice memo bubbles instead of plain MP3/CAF file attachments. Fixes #16848.</li>
|
||||
<li>TTS: resolve voice-note and voice-memo routing from channel plugin capabilities instead of speech-core-owned channel id lists.</li>
|
||||
<li>ACP: send subagent and async-task completion wakes to external ACP harnesses as plain prompts instead of OpenClaw internal runtime-context envelopes, while keeping those envelopes out of ACP transcripts.</li>
|
||||
<li>TTS/status: show configured TTS model, voice, and sanitized custom endpoint in <code>/status</code>, preserve OpenAI-compatible TTS instructions on custom endpoints, and retry empty Microsoft/Edge TTS output once. Addresses #46602, #47232, and #43936. Thanks @leekuangtao, @Huntterxx, and @rex993.</li>
|
||||
<li>Agents/Gateway: steer agent-driven config edits and restarts through the owner-only <code>gateway</code> tool, document <code>config.schema.lookup</code> as the field-doc source, and warn against using <code>gateway stop && gateway start</code> as a restart substitute on macOS. Fixes #71929. Thanks @ygc3817922006-sketch.</li>
|
||||
<li>Media understanding/audio: inject a deterministic transcript placeholder for too-small voice notes so agents do not hallucinate transcription or provider failures. Fixes #48944. Thanks @eulicesl.</li>
|
||||
<li>Providers/vLLM: send Nemotron 3 chat-template kwargs when thinking is off and honor configured <code>params.chat_template_kwargs</code> for OpenAI-compatible completions, so vLLM/Nemotron replies stay visible instead of becoming thinking-only. Fixes #71891. Thanks @jmystaki-create and @dennis-lynch.</li>
|
||||
<li>Channels/replies: strip copied inbound metadata blocks from user-facing assistant replies and model replay history, so Discord/vLLM sessions do not leak <code>Conversation info</code> / <code>UNTRUSTED ... message body</code> envelopes after a model echoes them. Fixes #71847. Thanks @jmystaki-create.</li>
|
||||
<li>Subagents/memory: keep inter-session completion wakes out of memory and dreaming session exports, and strip internal runtime-context blocks from realtime Control UI chat events.</li>
|
||||
<li>Agents/Claude: treat zero-token empty <code>stop</code> turns as failed provider output, retry once, repair replay, and allow configured model fallback instead of preserving them as successful silent replies. Fixes #71880. Thanks @MagnaAI.</li>
|
||||
<li>Tasks: normalize task lifecycle timestamps at create, update, and restore time, and report retained lost tasks as audit warnings until their cleanup window expires. (#71871) Thanks @likewen-tech.</li>
|
||||
<li>Diagnostics/OTEL: treat normal early model stream cleanup as a completed model call instead of exporting a misleading <code>StreamAbandoned</code> error span. Thanks @vincentkoc.</li>
|
||||
<li>Gateway/pairing: stop corrupt or unreadable device/node pairing stores from being treated as empty state, preserving <code>paired.json</code> for repair instead of overwriting approved pairings. Fixes #71873. Thanks @iret77.</li>
|
||||
<li>ACP: keep <code>/acp</code> management commands, plus local <code>/status</code> and <code>/unfocus</code>, on the Gateway path inside ACP-bound threads so they are not consumed as ACP prompt text. Fixes #66298. Thanks @kindomLee.</li>
|
||||
<li>ACPX: stop probing ACP agents during normal Gateway startup; the embedded backend now registers without spawning Codex/ACP child processes unless <code>OPENCLAW_ACPX_RUNTIME_STARTUP_PROBE=1</code> is explicitly set.</li>
|
||||
<li>CLI/image edit: accept <code>--size</code>, <code>--aspect-ratio</code>, and <code>--resolution</code> on <code>openclaw infer image edit</code> and report all supported edit flags from <code>capability inspect image.edit</code>. Thanks @Pinghuachiu.</li>
|
||||
<li>ACP: wait for the configured runtime backend to become healthy before startup identity reconciliation, avoiding transient acpx warnings during Gateway boot. Fixes #40566.</li>
|
||||
<li>Channels/ACP bindings: time out configured binding readiness checks instead of letting Discord preflight hang forever when an ACP target never settles. Fixes #68776.</li>
|
||||
<li>Control UI: hide the chat loading skeleton during background history reloads when existing messages or active stream content are already visible, avoiding reload flashes on high-latency local gateways. Fixes #71844. Thanks @WolvenRA.</li>
|
||||
<li>Control UI: keep locally optimistic chat messages visible when a history reload temporarily returns empty, avoiding lost first-turn messages on high-latency gateways. Fixes #71878. Thanks @WolvenRA.</li>
|
||||
<li>Control UI: keep chat history limits based on visible messages after filtering heartbeat and control-only transcript rows, so recent hidden entries no longer make older visible replies disappear. Thanks @WolvenRA.</li>
|
||||
<li>Agents/images: scrub old <code>[media attached: ...]</code>, <code>[Image: source: ...]</code>, and <code>media://inbound/...</code> markers from pruned model replay context so stale media refs are not rehydrated as fresh prompt images. Fixes #71868. Thanks @jmeadlock.</li>
|
||||
<li>Docker/Bonjour: disable Bonjour/mDNS advertising by default for bundled Compose gateways on bridge networking, while keeping host/macvlan opt-in with <code>OPENCLAW_DISABLE_BONJOUR=0</code>. Fixes #71879. Thanks @gbballpack.</li>
|
||||
<li>CLI/status: label the OpenClaw Serve/Funnel setting as <code>Tailscale exposure</code> and show daemon state separately when available, so <code>gateway.tailscale.mode: "off"</code> no longer reads like the Tailscale daemon is stopped. Fixes #71790. Thanks @pesvobodak.</li>
|
||||
<li>Plugins/Bonjour: stop ciao mDNS watchdog failures from looping forever when the advertiser stays stuck in <code>probing</code> or <code>announcing</code>; Bonjour now disables itself for the current Gateway process after repeated failed restarts while the Gateway keeps running. Fixes #69011. Thanks @siddharthaagarwalofficial-ux, @FiredMosquito831, and @spikefcz.</li>
|
||||
<li>Gateway/Fly.io: seed Control UI allowed origins from the actual runtime bind and port so CLI-driven non-loopback starts do not crash before config exists. Fixes #71823.</li>
|
||||
<li>macOS/remote SSH: keep discovered gateway hosts in <code>gateway.remote.sshTarget</code> while pinning SSH transport URLs to the local loopback tunnel, so browser automation does not regress into blocked non-loopback <code>ws://</code> endpoints. Fixes #67336.</li>
|
||||
<li>Gateway/proxy: bootstrap env proxy dispatching from direct Gateway startup so provider and plugin network requests honor <code>HTTPS_PROXY</code>/<code>HTTP_PROXY</code> before the first embedded agent attempt runs. (#71833) Thanks @mjamiv.</li>
|
||||
<li>Plugins/runtime deps: verify clean npm installs actually place requested bundled runtime packages in the managed install root, reporting exact missing specs instead of a false successful repair. (#71883) Thanks @Solvely-Colin.</li>
|
||||
<li>Plugins/discovery: ignore stale <code>plugins.load.paths</code> aliases that point back at packaged bundled plugin directories and have doctor remove them, keeping bundled plugins on the runtime-deps staging path.</li>
|
||||
<li>Models/LM Studio: preserve <code>@iq*</code> quant suffixes in model refs and provider matching so <code>/model lmstudio/...@iq3_xxs</code> keeps the exact LM Studio variant. Fixes #71474. (#71486) Thanks @Bartok9, @XinwuC, and @Sanjays2402.</li>
|
||||
<li>Matrix/cron: preserve the live Matrix delivery target when creating implicit announce reminder jobs so mixed-case room IDs are not reconstructed from lowercased session keys. Fixes #71798.</li>
|
||||
<li>Feishu: accept Schema 2.0 card action callbacks that report <code>context.open_chat_id</code> instead of legacy <code>context.chat_id</code>, so button callbacks no longer drop as malformed. Fixes #71670. Thanks @eddy1068.</li>
|
||||
<li>Feishu: keep synthetic card-action and bot-menu ids out of platform reply targets, using the real card callback message id when Feishu provides one and plain-sending otherwise. Fixes #71673. Thanks @eddy1068.</li>
|
||||
<li>Plugins/QQ Bot: prefer an installed QQ Bot plugin that declares it replaces the bundled <code>qqbot</code> channel, preventing duplicate <code>qqbot_channel_api</code> and <code>qqbot_remind</code> tool registration noise. Fixes #63102.</li>
|
||||
<li>Browser automation: keep stable tab ids and labels attached when Chromium replaces the raw target after form submissions or other action-triggered navigations, and return the replacement <code>targetId</code> from <code>/act</code> when the match is provable. Fixes #46137.</li>
|
||||
<li>QQ Bot: make <code>qqbot_remind</code> schedule, list, and remove Gateway cron jobs directly for owner-authorized senders instead of returning <code>cronParams</code> and relying on a follow-up generic <code>cron</code> tool call. Fixes #70865. (#70937) Thanks @GaosCode.</li>
|
||||
<li>Agents/ACP: hide <code>sessions_spawn</code> ACP runtime options unless an ACP backend is loaded, and make <code>/acp doctor</code> call out <code>plugins.allow</code> blocking bundled <code>acpx</code>. Thanks @vincentkoc.</li>
|
||||
<li>Agents/Codex: keep ACP prompt/skill routing hidden unless an ACP runtime backend is available, and warn in doctor when enabled Codex plugin configs still route <code>openai-codex/*</code> models through PI. Thanks @vincentkoc.</li>
|
||||
<li>Media delivery: avoid sending generated image attachments twice when the assistant reply already includes explicit <code>MEDIA:</code> lines for the same turn, and reject unsafe remote <code>MEDIA:</code> URLs before delivery. Thanks @pashpashpash.</li>
|
||||
<li>Codex harness: ignore retryable app-server error notifications after Codex recovers, and preserve the real nested error message for terminal app-server failures instead of replacing it with a generic failure. Thanks @pashpashpash.</li>
|
||||
<li>Agents/Codex: prepare native Codex sub-agent session metadata without a nested Gateway session patch and add a focused Docker smoke for the app-server sub-agent path. Thanks @vincentkoc.</li>
|
||||
<li>Agents/subagents: keep queued subagent announces session-only when the requester has no external channel target, avoiding ambiguous multi-channel delivery failures. Fixes #59201. Thanks @larrylhollan.</li>
|
||||
<li>Image understanding: preserve configured provider-prefixed vision model metadata when callers request the model without the provider prefix, so custom image models keep their <code>input: ["text", "image"]</code> capability. Fixes #33185. Thanks @Kobe9312 and @vincentkoc.</li>
|
||||
<li>Plugins/install: restore the previous plugin index records if a concurrent config write conflict interrupts install, update, or uninstall metadata commits. Thanks @shakkernerd.</li>
|
||||
<li>Plugins/install: reject native plugin archives that do not include a valid <code>openclaw.plugin.json</code>, preventing manifestless archives from writing install records that later show missing-manifest diagnostics. Thanks @shakkernerd.</li>
|
||||
<li>Plugins/uninstall: remove tracked managed plugin install directories even when the persisted install path differs from the default id-derived target, while still refusing deletes outside the managed extensions root. Thanks @shakkernerd.</li>
|
||||
<li>Plugins/update: restore previous plugin index records if core update or channel setup hits a concurrent config write conflict after plugin metadata changes. Thanks @shakkernerd.</li>
|
||||
<li>Plugins/onboarding: defer channel/provider plugin install records until the owning config write commits, keeping setup failures from advancing the plugin index ahead of <code>openclaw.json</code>. Thanks @shakkernerd.</li>
|
||||
<li>Plugins/config: route configure and agent setup writes with pending plugin install records through the plugin index commit helper so provider onboarding metadata is not stripped by plain config writes. Thanks @shakkernerd.</li>
|
||||
<li>Plugins/channels: merge pending channel plugin install records with the existing plugin index before config writes, preserving unrelated tracked installs during channel setup, resolve, remove, and capability repair flows. Thanks @shakkernerd.</li>
|
||||
<li>Plugins/config: defer shipped <code>plugins.installs</code> index migration during config writes until the guarded config commit window and roll it back if the config write fails before commit. Thanks @shakkernerd.</li>
|
||||
<li>Sessions: keep embedded runtime context out of the visible user prompt by sending it as a hidden next-turn custom message, and teach doctor to repair affected 2026.4.24 transcripts with duplicated prompt-rewrite branches. Fixes #71761.</li>
|
||||
<li>Gateway/subagents: keep direct-loopback backend RPCs authenticated with the shared gateway token/password off stale CLI paired-device scope baselines, so internal calls no longer hit <code>scope-upgrade</code> pairing prompts while remote, browser, node, device-token, and explicit-device paths still require normal pairing approval. Fixes #63548.</li>
|
||||
<li>Providers/Azure OpenAI: give deployment-scoped image generation requests a longer 600s default timeout so slow <code>gpt-image-2</code> generations can complete without a per-call <code>timeoutMs</code>. Fixes #71705. Thanks @voytas75.</li>
|
||||
<li>Gateway/plugins: link source-checkout bundled runtime dependency caches instead of recursively copying <code>node_modules</code> on the gateway main thread, preventing local status, node, and skill probes from timing out during startup cache restores.</li>
|
||||
<li>Skills/remote nodes: only expose remote macOS skill bins for connected nodes, clear stale bin matches when node probes fail, and include probe command, timeout, bin count, and connection state in timeout logs.</li>
|
||||
<li>Skills/remote nodes: recognize <code>system.which</code> object-map responses when probing connected macOS nodes, so Linux gateways can expose macOS-only skills such as Apple Notes when the required binaries are installed remotely. Fixes #71877. Thanks @miguelarios.</li>
|
||||
<li>CLI/gateway: keep diagnostic probes from creating first-time read-only device pairings, while still reusing cached device tokens for detailed read probes. Fixes #71766. Thanks @SunboZ.</li>
|
||||
<li>CLI/plugins: keep <code>message</code> startup, <code>channels logs</code>, <code>agents delete</code>, and <code>agents set-identity</code> off broad plugin preloading; message delivery still loads plugins when the action actually runs.</li>
|
||||
<li>Image understanding: resolve configured image models such as local LM Studio vision entries before reporting <code>Unknown model</code> when the discovery registry has not registered that provider. Fixes #66486. Thanks @zhanggpcsu.</li>
|
||||
<li>QQ Bot: ignore self-echoed bot messages using the outbound ref-index marker, preventing mirrored replies from re-entering the agent loop while still allowing users to quote bot replies. Fixes #71912. Thanks @wangyc6003.</li>
|
||||
<li>Sessions: separate reset freshness from session-store <code>updatedAt</code>, so heartbeat, cron, exec, and gateway bookkeeping no longer prevent configured daily/idle resets from rolling long-running channel sessions. Fixes #68315, #63732, #63820, and #69083. Thanks @maxatv, @longhairedsi, @bradfreels, and @akessel56.</li>
|
||||
<li>Sessions: clear queued system-event notices during <code>/new</code>, <code>/reset</code>, gateway <code>sessions.reset</code>, and daily/idle rollover so stale background updates cannot leak into the first prompt of the fresh session. Fixes #66864. Thanks @opeyio, @Magicray1217, and @cedillarack.</li>
|
||||
<li>CLI/agents: keep <code>agents bind</code>, <code>agents unbind</code>, and <code>agents bindings</code> on setup-safe channel metadata paths so they do not preload bundled plugin runtimes or stage runtime dependencies. Fixes #71743.</li>
|
||||
<li>Plugins/registry: preserve explicit disabled plugin records during registry migration without persisting every unused bundled plugin discovered on disk. Thanks @shakkernerd.</li>
|
||||
<li>Windows/native: keep CLI startup and bundled provider plugin loading off Windows ESM raw-path failure paths, fixing native onboarding/install smoke on Node 24.</li>
|
||||
<li>Plugins/doctor: read bundled channel doctor capabilities through the same packaged plugin directory resolver used by plugin loading, so published installs keep Matrix DM allowlist repairs on <code>channels.matrix.dm.*</code> instead of writing invalid top-level <code>dmPolicy</code> keys. Fixes #71757.</li>
|
||||
<li>Plugins/Windows: keep bundled plugin Jiti loaders off the native import path on Windows so channel plugins such as Telegram no longer crash with <code>ERR_UNSUPPORTED_ESM_URL_SCHEME</code> on <code>C:\...</code> paths. Fixes #71749. Thanks @smeyer9.</li>
|
||||
<li>Providers/Ollama: use Ollama's current <code>/api/web_search</code> endpoint and honor <code>https://ollama.com</code> model-provider base URLs for Ollama Web Search. Fixes #71741. Thanks @madhvidua.</li>
|
||||
<li>Memory/Ollama: serialize Ollama memory embedding batches and add an inline batch timeout override, with longer defaults for local/self-hosted embedding providers.</li>
|
||||
<li>Sessions/usage: exclude compaction checkpoint transcript snapshots from usage totals and session discovery, while keeping old checkpoint files removable.</li>
|
||||
<li>CLI/agents: keep <code>openclaw agents list --json</code> on the config-only path by default, avoiding bundled plugin loading unless callers request <code>--bindings</code>. Fixes #71739. Thanks @kaloster.</li>
|
||||
<li>Plugins/install: force plugin dependency installs to stay project-local even when inherited npm config requests global installs, so successful installs still materialize the plugin's staged <code>node_modules</code>.</li>
|
||||
<li>Providers/Google: transcode Gemini TTS PCM to Opus for voice-note targets so WhatsApp and other native voice-note replies can play as voice messages.</li>
|
||||
<li>TTS/WhatsApp: mark non-Opus provider output as voice-note intent so channel delivery transcodes MP3/WebM replies to Ogg/Opus PTT audio.</li>
|
||||
<li>Plugins/runtime deps: reuse existing external bundled-plugin stage roots when mirrored plugin roots are inspected again, avoiding second-generation <code>openclaw-unknown-*</code> stages and repeated first-turn restaging. Fixes #71599.</li>
|
||||
<li>iOS/macOS Talk Mode: allow <code>talk.speechLocale</code> to set the speech recognition locale for non-English voice conversations. Fixes #44688.</li>
|
||||
<li>Plugins/providers: honor explicit plugin candidate lists instead of reading a persisted registry snapshot from local state, keeping candidate-scoped provider discovery hermetic.</li>
|
||||
<li>Plugins/doctor: keep bundled plugin runtime-dependency repairs inside the managed OpenClaw stage even when user npm prefix/global config points npm at <code>$HOME/node_modules</code>. Fixes #71730.</li>
|
||||
<li>ACP/sessions_spawn: reject normal OpenClaw config agent ids when callers explicitly request <code>runtime="acp"</code>, while allowing agents configured with <code>runtime.type="acp"</code> to resolve to their ACP harness id. Fixes #63914.</li>
|
||||
<li>ACP/sessions_spawn: apply <code>runTimeoutSeconds</code> to ACP child turns and dispatch those turns on the background subagent lane, so quota-stalled ACP harnesses do not occupy the main agent lane indefinitely. Fixes #68823.</li>
|
||||
<li>ACP/oneshot: reconcile runtime session identity before closing completed oneshot ACP runs, so finished <code>sessions.json</code> entries do not stay stuck with <code>acp.identity.state="pending"</code>.</li>
|
||||
<li>ACPX: bundle <code>acpx@0.6.1</code> so unsupported generic model overrides fail clearly instead of silently falling back to the target adapter default.</li>
|
||||
<li>ACP/models: document that non-Codex ACP model overrides require adapter support for ACP <code>models</code> plus <code>session/set_model</code>, so unsupported harnesses fail clearly instead of silently falling back to their defaults.</li>
|
||||
<li>Plugins/Voice Call: treat missing provider credentials as setup-incomplete during Gateway startup and log the missing keys as a warning instead of a runtime startup error, while keeping explicit command/tool errors when used.</li>
|
||||
<li>Android/Talk Mode: prevent duplicate TTS playback when fast or repeated final chat events arrive while Talk Mode is waiting for its own response. Fixes #46546.</li>
|
||||
<li>Tooling/check:changed: pass parent heavy-check lock markers to lint lanes so <code>pnpm check:changed</code> no longer waits on its own <code>lint:extensions</code> child.</li>
|
||||
<li>CLI/completion: dedupe provider auth flags before registering <code>openclaw onboard</code> options, so completion-cache refresh during update no longer fails when stale core fallback flags overlap plugin manifest flags. Fixes #71667.</li>
|
||||
<li>Diagnostics/trace: report live context usage from the current prompt snapshot instead of provider turn totals, avoiding false near-full context spikes on cached or tool-heavy runs.</li>
|
||||
<li>Providers/Google: honor <code>models.providers.google.request.allowPrivateNetwork</code> for Gemini TTS and telephony TTS, matching Google image generation and media understanding. (#71723) Thanks @ro-hansolo.</li>
|
||||
<li>Providers/MiniMax: register <code>minimax-portal</code> for music and video generation, preserving OAuth auth and regional MiniMax base URLs across the shared <code>music_generate</code> and <code>video_generate</code> tools. (#63241) Thanks @tars90percent.</li>
|
||||
<li>Providers/onboarding: keep Runway and Alibaba Model Studio out of the text-inference setup picker by scoping their video-generation auth choices to the media setup flow. (#65856) Thanks @Jah-yee.</li>
|
||||
<li>Plugins/Bonjour: stop the gateway from crash-looping on <code>CIAO PROBING CANCELLED</code> when the mDNS watchdog cancels a stuck probe. Restores the rejection-handler wiring dropped during the bonjour plugin migration and shares unhandled-rejection state across module instances so plugin-staged copies of <code>openclaw/plugin-sdk/runtime</code> register into the same handler set the host consults. Especially affects Docker on macOS, where mDNS probing reliably hits the watchdog. Thanks @troyhitch.</li>
|
||||
<li>Google Meet: report pinned Chrome nodes as offline or missing capabilities in setup/join diagnostics, keep inaccessible nodes out of auto-selection, and preflight local BlackHole/SoX requirements before agents try local Chrome.</li>
|
||||
<li>Providers/MiniMax: route <code>image-01</code> requests to the dedicated image generation endpoint while preserving CN endpoint selection. Fixes #61149. Thanks @mushuiyu886.</li>
|
||||
<li>Plugins/startup: remove ownerless bundled runtime-dependency install locks after a short grace window and include lock owner details when startup times out waiting for a plugin runtime-deps lock.</li>
|
||||
<li>Plugins/install: anchor bundled runtime-dependency npm installs with an OpenClaw-owned package manifest so Linux updates cannot accidentally write to a parent <code>$HOME/node_modules</code> tree. Fixes #71730.</li>
|
||||
<li>Plugins/install: pass onboarding plugin config into plugin index writes so local plugin installs outside default discovery roots keep their install records. Thanks @shakkernerd.</li>
|
||||
<li>Plugins/install: migrate shipped <code>plugins.installs</code> config records into the plugin index while stripping them from runtime config and future writes. Thanks @shakkernerd.</li>
|
||||
<li>Plugins/install: durably remove shipped <code>plugins.installs</code> from <code>openclaw.json</code> after its records are copied into the plugin index, while rolling back the index write if config cleanup fails. Thanks @shakkernerd.</li>
|
||||
<li>Plugins/install: keep migrated plugin install records in the plugin index even when the plugin manifest is missing or invalid, so update, uninstall, inspect, and audit can still recover broken installs. Thanks @shakkernerd.</li>
|
||||
<li>Plugins/security: keep plugin audit JSON check ids stable while reporting plugin index install-record findings with updated wording. Thanks @shakkernerd.</li>
|
||||
<li>CLI/config: reject direct <code>plugins.installs</code> edits with guidance to use <code>openclaw plugins install</code>, <code>openclaw plugins update</code>, or <code>openclaw plugins uninstall</code> instead. Thanks @shakkernerd.</li>
|
||||
<li>Live tests/voice: accept common STT variants for OpenClaw and ElevenLabs brand names so provider smoke tests fail on real regressions rather than equivalent transcripts.</li>
|
||||
<li>Agents/replies: forward sanitized underlying agent failure details on external channels instead of replacing unknown failures with a generic retry message.</li>
|
||||
<li>CLI/MCP: translate OpenClaw <code>mcp.servers.*.transport</code> entries into Claude/Gemini CLI <code>type</code> fields so streamable HTTP MCP servers load in CLI backend sessions. (#71724) Thanks @Blockchain-Oracle.</li>
|
||||
<li>Browser/CDP: honor configured remote and <code>attachOnly</code> CDP HTTP/WebSocket timeouts when opening tabs through raw CDP or <code>/json/new</code> fallback. (#54238) Thanks @FuncWei.</li>
|
||||
<li>WhatsApp/TTS: send visible text separately from PTT voice-note audio instead of relying on hidden voice-note captions. Fixes #51081.</li>
|
||||
<li>Browser/client: avoid telling agents to restart OpenClaw for dispatcher timeouts on external browser profiles such as <code>attachOnly</code>, remote CDP, and existing-session. (#40815) Thanks @0xsline.</li>
|
||||
<li>Agents/TTS: preserve <code>[[audio_as_voice]]</code> directives on trusted text tool-result <code>MEDIA:</code> payloads so generated audio still delivers as a voice note. (#46535) Thanks @azade-c.</li>
|
||||
<li>Agents/TTS: keep queued tool media when an assistant ends with <code>NO_REPLY</code> on non-block delivery paths, so media-only generated audio replies still send. (#60025) Thanks @bradlind1.</li>
|
||||
<li>Telegram/STT: frame inbound voice-note transcripts as machine-generated, untrusted text in agent context while preserving raw transcript mention detection. Closes #33360. Thanks @smartchainark.</li>
|
||||
<li>Subagents/browser: show an actionable <code>/tools</code> notice when browser automation is configured but filtered out by the active tool profile, and document that coding-profile agents should use <code>tools.alsoAllow: ["browser"]</code> rather than subagent allowlists alone.</li>
|
||||
<li>Control UI/Quick Settings: persist the assistant avatar override to browser local storage (mirroring the user avatar) so uploaded image data URLs no longer fail config validation with "Too big: expected string to have <=200 characters". Also lift the gateway-side <code>ui.assistant.avatar</code> length cap to match the user avatar size budget for non-UI clients writing the field directly. Thanks @BunsDev.</li>
|
||||
<li>Plugin SDK: share diagnostic event subscriptions across duplicate source/dist module graphs so legacy root SDK imports still receive runtime diagnostic events.</li>
|
||||
<li>Agents/Bedrock: prevent empty assistant stream-error turns from poisoning Converse replay by persisting, repairing, and replaying a non-empty fallback block. Fixes #71572. (#71627) Thanks @openperf.</li>
|
||||
<li>Agents/Anthropic/Bedrock: strip thinking blocks with missing, empty, or blank replay signatures before provider conversion, falling back to non-empty omitted-reasoning text when needed so corrupted signed-thinking history no longer poisons subsequent turns. Fixes #45010. (#70054) Thanks @castaples.</li>
|
||||
<li>Agents/Anthropic/Bedrock: preserve stripped thinking-only assistant replay turns with non-empty omitted-reasoning text so provider adapters keep strict user/assistant turn shape. Thanks @wujiaming88.</li>
|
||||
<li>ACP/Codex: pass <code>sessions_spawn(runtime="acp")</code> model and thinking overrides into Codex ACP startup, normalize <code>openai-codex/*</code> refs and slash reasoning suffixes, and recognize managed Codex ACP wrapper commands without blocking current <code>gpt-5.5</code> sessions. Fixes #40393. (#71643) Thanks @91wan.</li>
|
||||
<li>Browser/CDP: make readiness diagnostics use the same discovery-first fallback as reachability for bare <code>ws://</code> Browserless and Browserbase CDP URLs. Fixes #69532.</li>
|
||||
<li>Browser/CDP: explain that loopback Browserless or other externally managed CDP services need <code>attachOnly: true</code> and matching Browserless <code>EXTERNAL</code> endpoint when reporting local port ownership conflicts, and fall back to the configured bare WebSocket root when a discovered Browserless endpoint rejects CDP. Fixes #49815.</li>
|
||||
<li>Gateway/reload: preserve indefinite <code>gateway.reload.deferralTimeoutMs: 0</code> semantics for channel hot reload deferrals so active agent runs are not interrupted by a forced channel restart. (#71637) Thanks @Poo-Squirry.</li>
|
||||
<li>Agents/tool results: cap persisted Pi tool-result details and strip hidden diagnostics before provider conversion, preventing large debug payloads from bloating session transcripts. (#71637) Thanks @Poo-Squirry.</li>
|
||||
<li>ACP/OpenCode: update the bundled acpx runtime to 0.6.0 and cover the OpenCode ACP bind path in Docker live tests.</li>
|
||||
<li>Providers/OpenCode Go: add DeepSeek V4 Pro and DeepSeek V4 Flash to the Go catalog while the bundled Pi registry catches up. Fixes #71587.</li>
|
||||
<li>Providers/OpenCode Go: route DeepSeek V4 Pro/Flash through the OpenAI-compatible Go endpoint and suppress invalid <code>reasoning_effort: "off"</code> payloads, fixing tool-enabled requests for <code>opencode-go/deepseek-v4-flash</code>. Fixes #71683.</li>
|
||||
<li>Plugins/model defaults: run Skill Workshop review, Active Memory recall, and session-memory slug generation on the configured agent default model instead of the hardcoded OpenAI SDK fallback when hook context lacks model metadata. Fixes #71659.</li>
|
||||
<li>Providers/Venice: fill the required DeepSeek V4 <code>reasoning_content</code> placeholder for <code>venice/deepseek-v4-pro</code> and <code>venice/deepseek-v4-flash</code> replay turns without sending native DeepSeek <code>thinking</code> controls that Venice rejects. Fixes #71628.</li>
|
||||
<li>Browser/existing-session: support per-profile Chrome MCP command/args, map <code>cdpUrl</code> to <code>--browserUrl</code> or <code>--wsEndpoint</code>, and avoid combining endpoint flags with <code>--userDataDir</code>. Fixes #47879, #48037, and #62706. Thanks @puneet1409, @zhehao, and @madkow1001.</li>
|
||||
<li>Media/plugins: bound MIME sniffing and ZIP archive preflight before handing untrusted files to <code>file-type</code> or <code>jszip</code>, reducing parser CPU and memory exposure for attachments and ClawHub plugin archives. Thanks @vincentkoc.</li>
|
||||
<li>Memory-host SDK: use trusted env-proxy mode for remote embedding and batch HTTP calls only when Undici will proxy that target, preserving SSRF DNS pinning for <code>ALL_PROXY</code>-only and <code>NO_PROXY</code> bypass cases. Fixes #52162. (#71506) Thanks @DhtIsCoding.</li>
|
||||
<li>Gateway/dashboard: render Control UI and WebSocket links with <code>https://</code>/<code>wss://</code> when <code>gateway.tls.enabled=true</code>, including <code>openclaw gateway status</code>. Fixes #71494. (#71499) Thanks @deepkilo.</li>
|
||||
<li>Agents/OpenAI-compatible: default proxy/local completions tool requests to <code>tool_choice: "auto"</code> when tools are present, so providers enter native tool-calling mode instead of replying with plain-text tool directives. (#71472) Thanks @Speed-maker.</li>
|
||||
<li>OpenAI image generation: use <code>gpt-5.5</code> for the Codex OAuth responses transport instead of the retired <code>gpt-5.4</code> model, fixing 500s from ChatGPT Codex image generation. Fixes #71513. Thanks @baolongl.</li>
|
||||
<li>OpenAI image generation: route transparent-background default-model requests to <code>gpt-image-1.5</code>, document the expected <code>image_generate</code> call shape, and keep Azure/custom OpenAI-compatible deployment names untouched.</li>
|
||||
<li>Google video generation: download direct MLDev Veo <code>video.uri</code> results instead of passing them through the Files API path, fixing 404s after successful generation/polling. Fixes #71200. Thanks @panhaishan.</li>
|
||||
<li>Google video generation: fall back to the REST <code>predictLongRunning</code> Veo endpoint for text-only SDK 404s while keeping reference image/video generation on the SDK path. Fixes #62309 and #63008. (#62343) Thanks @leoleedev.</li>
|
||||
<li>MiniMax music generation: switch the bundled default model from the unsupported <code>music-2.5+</code> id to the current <code>music-2.6</code> API model. Fixes #64870 and addresses the music default from #62315. Thanks @noahclanman and @edwardzheng1.</li>
|
||||
<li>Cron: record jobs interrupted by a gateway restart as failed at their original <code>runningAtMs</code>, skip unsafe startup replay, and disable interrupted one-shot jobs so they show a visible failure instead of silently disappearing or duplicating work. Fixes #59056, #61343, #63657, and #59301. Thanks @ponchoooPenguin, @daemic24, @myradon, and @hikiwibot.</li>
|
||||
<li>Cron tool: recover flat top-level schedule shorthand such as <code>cron</code>, <code>tz</code>, and <code>staggerMs</code> before gateway validation, so model-generated cron add/update calls preserve cron jitter settings. Thanks @tyxben.</li>
|
||||
<li>Cron: hydrate flat legacy job rows with top-level <code>cron</code>, <code>tz</code>, <code>session</code>, and <code>message</code> fields into canonical schedule, target, and payload objects before startup recomputes run times. Fixes #43351.</li>
|
||||
<li>Agents/replies: let pending group chat history trigger bare mentioned turns without treating metadata-only inbound context as user input. Fixes #71489. (#71520) Thanks @SymbolStar.</li>
|
||||
<li>Google media generation: strip a configured trailing <code>/v1beta</code> from Google music/video provider base URLs before calling the Google GenAI SDK, preventing doubled <code>/v1beta/v1beta</code> paths. Fixes #63240. (#63258) Thanks @Hybirdss.</li>
|
||||
<li>Discord: restore direct-message voice-note preflight transcription and classify URL-only Ogg/Opus voice attachments as audio while skipping partial attachments without usable URLs. Fixes #61314 and #64803.</li>
|
||||
<li>Plugins/build: copy bundled plugin skill trees into <code>dist-runtime</code>, broaden Windows symlink-copy fallbacks, and fingerprint runtime dependencies from <code>lstat</code> so symlink-like directory entries cannot crash staging.</li>
|
||||
<li>Google Chat: preserve reply text when a typing indicator message is deleted or can no longer be updated, so media captions and first text chunks are resent instead of silently disappearing. (#71498) Thanks @colin-lgtm.</li>
|
||||
<li>Cron: tolerate malformed legacy job rows in startup, main-session system-event payloads, and human-readable <code>cron list</code> output so missing <code>state</code>, <code>payload.text</code>, or display fields no longer crash the scheduler or CLI. Fixes #66016, #65916, #64137, #57872, #59968, #63813, #52804, and #43163. (#71509) Thanks @vincentkoc.</li>
|
||||
<li>CLI/models: make <code>openclaw models scan</code> fall back to public OpenRouter free-model metadata when no <code>OPENROUTER_API_KEY</code> is configured, avoid config secret resolution for explicit <code>--no-probe</code> scans, and apply the scan timeout to the OpenRouter catalog request.</li>
|
||||
<li>Feishu: keep streaming cards to one live card per turn, flush throttled card edits after meaningful text boundaries, and skip exact block/partial repeats so tool-heavy replies do not duplicate card output. Thanks @allan0509.</li>
|
||||
<li>Feishu: finish the streaming-card duplicate closeout by stripping leaked reasoning tags, preserving cross-block partial snapshots, enabling topic-thread streaming cards, omitting the generic <code>main</code> card header, surfacing transient tool/compaction status, and cleaning streaming state after close failures. Thanks @sesame437, @Vicky-v7, @maoku-family, @Pengxiao-Wang, and @Maple778.</li>
|
||||
<li>Telegram: recover incomplete partial-stream previews by falling back to a final send when an ambiguous final edit failure would otherwise retain a strict prefix of the answer. Fixes #71525. (#71554) Thanks @sahilsatralkar.</li>
|
||||
<li>Control UI/chat: collapse assistant token/model context details behind an explicit Context disclosure and show full dates in message footers, making historical transcript timing clear without noisy default metadata. (#71337) Thanks @BunsDev.</li>
|
||||
<li>OpenAI/Codex OAuth: explain <code>unsupported_country_region_territory</code> token-exchange failures with a proxy/region hint instead of surfacing a generic OAuth error. Fixes #51175. (#71501) Thanks @vincentkoc and @wulala-xjj.</li>
|
||||
<li>Browser/Linux: fall back to headless mode for local managed profiles on hosts without a display server, while preserving explicit per-profile headed overrides and reporting the headless source. (#60953) Thanks @rrpsantos.</li>
|
||||
<li>Telegram: remove the startup persisted-offset <code>getUpdates</code> preflight so polling restarts do not self-conflict before the runner starts. Fixes #69304. (#69779) Thanks @chinar-amrutkar.</li>
|
||||
<li>Telegram: keep the polling stall watchdog active even when grammY reports the runner as not running while its task is still pending, so a rebuilt transport cannot leave <code>getUpdates</code> silent until a manual gateway restart. Fixes #69064. Thanks @LDLoeb.</li>
|
||||
<li>Subagents: fall back to direct completion delivery when the parent announce turn finishes without a visible payload, so child results still reach channel-backed requester sessions.</li>
|
||||
<li>Subagents: tell parent agents to use <code>sessions_yield</code> while waiting for child completion events, preventing GPT-5 fast runs from ending silently after spawning workers.</li>
|
||||
<li>Browser/Playwright: ignore benign already-handled route races during guarded navigation so browser-page tasks no longer fail when Playwright tears down a route mid-flight. (#68708) Thanks @Steady-ai.</li>
|
||||
<li>Browser/CLI: lazy-load browser command groups and plugin runtime services so <code>openclaw browser --help</code> can render without loading the full browser automation stack. Fixes #65400. (#65460, #66640) Thanks @pandego and @Tianworld.</li>
|
||||
<li>Browser/CLI: serve precomputed <code>openclaw browser --help</code> text from CLI startup metadata, avoiding the full plugin/config startup path for the common help invocation.</li>
|
||||
<li>Browser/downloads: seed managed Chrome profiles with OpenClaw download prefs and capture unmanaged click-triggered downloads under the guarded downloads directory, while explicit download waiters still own their target file. (#64558) Thanks @Pearcekieser.</li>
|
||||
<li>Browser/Chrome: stop passing redundant <code>--disable-setuid-sandbox</code> when <code>browser.noSandbox</code> is enabled; <code>--no-sandbox</code> remains the effective sandbox opt-out. (#67939) Thanks @sebykrueger.</li>
|
||||
<li>Browser/client: stop telling agents to permanently avoid the browser after transient timeout or cancellation failures; keep the no-retry hint for persistent unavailable/rate-limit cases. (#46505) Thanks @jriff.</li>
|
||||
<li>Browser/aria snapshots: bind <code>format=aria</code> <code>axN</code> refs to live DOM nodes through backend DOM ids when Playwright is available, so follow-up browser actions can use those refs without timing out. (#62434) Thanks @MrKipler.</li>
|
||||
<li>Telegram: prevent duplicate in-process long pollers for the same bot token and add clearer <code>getUpdates</code> conflict diagnostics for external duplicate pollers. Fixes #56230. Thanks @Co-Messi.</li>
|
||||
<li>Browser/Linux: detect Chromium-based installs under <code>/opt/google</code>, <code>/opt/brave.com</code>, <code>/usr/lib/chromium</code>, and <code>/usr/lib/chromium-browser</code> before asking users to set <code>browser.executablePath</code>. (#48563) Thanks @lupuletic.</li>
|
||||
<li>Sessions/browser: close tracked browser tabs when idle, daily, <code>/new</code>, or <code>/reset</code> session rollover archives the previous transcript, preventing tabs from leaking past the old session. Thanks @jakozloski.</li>
|
||||
<li>Sessions/forking: fall back to transcript-estimated parent token counts when cached totals are stale or missing, so oversized thread forks start fresh instead of cloning the full parent transcript. Thanks @jalehman.</li>
|
||||
<li>OpenAI/Codex: send Codex Responses system prompts through top-level <code>instructions</code> while preserving the existing native Codex payload controls.</li>
|
||||
<li>MCP/CLI: retire bundled MCP runtimes at the end of one-shot <code>openclaw agent</code> and <code>openclaw infer model run</code> gateway/local executions, so repeated scripted runs do not accumulate stdio MCP child processes. Fixes #71457. Thanks @spartoviMD.</li>
|
||||
<li>OpenAI/Codex image generation: canonicalize legacy <code>openai-codex.baseUrl</code> values such as <code>https://chatgpt.com/backend-api</code> to the Codex Responses backend before calling <code>gpt-image-2</code>, matching the chat transport. Fixes #71460. Thanks @GodsBoy.</li>
|
||||
<li>Control UI: make <code>/usage</code> use the fresh context snapshot for context percentage, and include cache-write tokens in the Usage overview cache-hit denominator. Fixes #47885. Thanks @imwyvern and @Ante042.</li>
|
||||
<li>GitHub Copilot: preserve encrypted Responses reasoning item IDs during replay so Copilot can validate encrypted reasoning payloads across requests. (#71448) Thanks @a410979729-sys.</li>
|
||||
<li>GitHub Copilot: never rewrite connection-bound reasoning item IDs regardless of whether <code>encrypted_content</code> is present, fixing a 400 "Encrypted content item_id did not match" error with <code>gpt-5.3-codex</code> and future Codex models that fall through to the forward-compat catch-all with <code>reasoning: false</code>. Also recognize Codex-named models as reasoning-capable so they inherit the correct capability flags. Refs #68735. Thanks @InvalidPandaa.</li>
|
||||
<li>Agents/replies: recover final-answer text when streamed assistant chunks contain only whitespace, preventing completed turns from surfacing as empty-payload errors. Fixes #71454. (#71467) Thanks @Sanjays2402.</li>
|
||||
<li>Feishu/TTS: transcode voice-intent MP3 and other audio replies to Ogg/Opus before sending native Feishu audio bubbles, while keeping ordinary MP3 attachments as files. Fixes #61249 and #37868. Thanks @sg1416-zg and @ycjlb2023-peteryi.</li>
|
||||
<li>WhatsApp/TTS: transcode MP3/WebM audio, including Microsoft Edge TTS output, to Ogg/Opus before sending PTT voice notes.</li>
|
||||
<li>QQBot/TTS: honor plain <code>audioAsVoice</code> replies by synthesizing TTS to native QQ voice messages, and mark inbound voice-only messages as audio media without exposing raw voice paths to generic media context.</li>
|
||||
<li>Providers/SenseAudio: add bundled SenseAudio batch audio transcription through <code>tools.media.audio</code> with <code>SENSEAUDIO_API_KEY</code> auth. (#66943) Thanks @Fl0rencess720.</li>
|
||||
<li>Providers/MiniMax: let TTS use MiniMax portal OAuth and Token Plan credentials before falling back to <code>MINIMAX_API_KEY</code>, and include current TTS HD model ids. Fixes #55017. Thanks @zx15210404690-hash.</li>
|
||||
<li>Telegram/webhook: acknowledge validated webhook updates before running bot middleware, keeping slow agent turns from tripping Telegram delivery retries while preserving per-chat processing lanes. Fixes #71392. Thanks @joelforsberg46-source.</li>
|
||||
<li>MCP/config reload: hot-apply <code>mcp.*</code> changes by disposing cached session MCP runtimes, and dispose bundled MCP runtimes during gateway shutdown so removed <code>mcp.servers</code> entries reap child processes promptly. Fixes #60656. Thanks @xieyuanqing.</li>
|
||||
<li>Active Memory: keep silent recall sub-agent billing/auth failures out of shared auth-profile cooldown state, so a Claude CLI extra-usage rejection cannot disable normal Claude-backed turns. Fixes #71284. (#71539) Thanks @vishutdhar and @obviyus.</li>
|
||||
<li>Auth/Claude CLI: sync refreshed Claude CLI OAuth credentials into the managed auth profile so long-running Claude CLI runs stop falling back to stale OpenClaw snapshots. (#70902) Thanks @starvex.</li>
|
||||
<li>Sessions: make <code>sessions_spawn(mode="session")</code> errors name usable alternatives when the current channel cannot bind subagent threads. Fixes #67400. (#67790) Thanks @stainlu.</li>
|
||||
<li>Agents/Claude CLI: pass the OpenClaw system prompt through Claude's prompt-file flag so Windows runs avoid argv length failures without changing system prompt semantics. Fixes #69158. (#69211) Thanks @skylee-01, @cassioanorte, @Syu0, and @Stache73.</li>
|
||||
<li>Agents/CLI sessions: bind <code>google-gemini-cli</code> session auth-epoch to the Google account identity in <code>~/.gemini/oauth_creds.json</code>, so Gemini-backed agents resume their conversation after gateway restart instead of minting a fresh session, and stale bindings are invalidated when the authenticated Google account changes. Fixes #70973. (#71076) Thanks @openperf.</li>
|
||||
<li>Slack: stop treating user mentions in assistant-authored message edit blocks as sender attribution, preventing edited bot messages from spoofing a mentioned DM user. (#71700) Thanks @vincentkoc.</li>
|
||||
<li>Codex: consume unauthorized bound conversation inbound claims before they can fall through to other claim handlers or enqueue Codex turns. (#71702) Thanks @vincentkoc.</li>
|
||||
<li>Codex media understanding: require approval-checked app-server image turns while explicitly declining tool, file, permission, and elicitation approval requests for the bounded image worker. (#71703) Thanks @vincentkoc.</li>
|
||||
<li>Agents/Claude CLI: allow large live <code>stream-json</code> JSONL lines up to the existing per-turn raw limit, preventing large Telegram, WebChat, MCP, and image turns from aborting on the old stdout buffer cap. Fixes #71793, #71080, and #70766. (#71897) Thanks @chacher86, @shivamgrover21, and @tpjordan.</li>
|
||||
<li>Agents/Claude CLI: unwrap nested Claude result envelopes in CLI JSON output so delegated agent responses surface as final text instead of raw result JSON. (#66819) Thanks @mraleko.</li>
|
||||
<li>Agents/Claude CLI: apply the configured 1M context window override to eligible Claude CLI Opus and Sonnet models when <code>context1m</code> is enabled. (#70863) Thanks @bidadh.</li>
|
||||
<li>Models/status: report fresh Claude CLI native auth instead of stale stored <code>anthropic:claude-cli</code> profile expiry when local credentials are current. Fixes #71256. (#71332) Thanks @matthiasjanke and @neeravmakwana.</li>
|
||||
<li>CLI backends: compact OpenClaw transcripts after over-budget CLI turns and reseed fresh CLI sessions from the compacted transcript instead of stale external resume state. Fixes #68329. (#71916) Thanks @obviyus.</li>
|
||||
<li>Telegram: keep default tool progress messages visible when answer preview streaming is disabled. (#71825) Thanks @VACInc.</li>
|
||||
<li>Configure/models: clear deselected model fallbacks when updating the model picker allowlist, including provider-scoped setup flows. (#71596) Thanks @rubencu.</li>
|
||||
<li>Agents/streaming: strip namespaced <code><antml:thinking></code> reasoning tags from streamed assistant replies before user-visible text is emitted. (#69288) Thanks @xialonglee.</li>
|
||||
</ul>
|
||||
<p><a href="https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md">View full changelog</a></p>
|
||||
]]></description>
|
||||
<enclosure url="https://github.com/openclaw/openclaw/releases/download/v2026.4.25/OpenClaw-2026.4.25.zip" length="48125363" type="application/octet-stream" sparkle:edSignature="RnQ01wCFgupauUdwOFan+XPGZhBJi/w3sgJYA5EaasbeGrduDHBGw1e9Zj2Lqb4ud8e6Q+tRcJVfxh5KKSEIDg=="/>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
||||
@@ -65,8 +65,8 @@ android {
|
||||
applicationId = "ai.openclaw.app"
|
||||
minSdk = 31
|
||||
targetSdk = 36
|
||||
versionCode = 2026042700
|
||||
versionName = "2026.4.27"
|
||||
versionCode = 2026043000
|
||||
versionName = "2026.4.30"
|
||||
ndk {
|
||||
// Support all major ABIs — native libs are tiny (~47 KB per ABI)
|
||||
abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# OpenClaw iOS Changelog
|
||||
|
||||
## 2026.4.30 - 2026-04-30
|
||||
|
||||
Maintenance update for the current OpenClaw development release.
|
||||
|
||||
## 2026.4.27 - 2026-04-27
|
||||
|
||||
Maintenance update for the current OpenClaw development release.
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// Source of truth: apps/ios/version.json
|
||||
// Generated by scripts/ios-sync-versioning.ts.
|
||||
|
||||
OPENCLAW_IOS_VERSION = 2026.4.27
|
||||
OPENCLAW_MARKETING_VERSION = 2026.4.27
|
||||
OPENCLAW_IOS_VERSION = 2026.4.30
|
||||
OPENCLAW_MARKETING_VERSION = 2026.4.30
|
||||
OPENCLAW_BUILD_VERSION = 1
|
||||
|
||||
#include? "../build/Version.xcconfig"
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"version": "2026.4.27"
|
||||
"version": "2026.4.30"
|
||||
}
|
||||
|
||||
@@ -184,7 +184,9 @@ final class CanvasManager {
|
||||
|
||||
private func maybeAutoNavigateToA2UI(controller: CanvasWindowController, a2uiUrl: String?) {
|
||||
guard let a2uiUrl else { return }
|
||||
let shouldNavigate = controller.shouldAutoNavigateToA2UI(lastAutoTarget: self.lastAutoA2UIUrl)
|
||||
let shouldNavigate = controller.shouldAutoNavigateToA2UI(
|
||||
lastAutoTarget: self.lastAutoA2UIUrl,
|
||||
candidateTarget: a2uiUrl)
|
||||
guard shouldNavigate else {
|
||||
Self.logger.debug("canvas auto-nav skipped; target unchanged")
|
||||
return
|
||||
|
||||
@@ -319,12 +319,14 @@ final class CanvasWindowController: NSWindowController, WKNavigationDelegate, NS
|
||||
self.sessionDir.path
|
||||
}
|
||||
|
||||
func shouldAutoNavigateToA2UI(lastAutoTarget: String?) -> Bool {
|
||||
let trimmed = (self.currentTarget ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if trimmed.isEmpty || trimmed == "/" { return true }
|
||||
func shouldAutoNavigateToA2UI(lastAutoTarget: String?, candidateTarget: String) -> Bool {
|
||||
let current = (self.currentTarget ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let candidate = candidateTarget.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if current.isEmpty || current == "/" { return true }
|
||||
if !candidate.isEmpty, current == candidate { return false }
|
||||
if let lastAuto = lastAutoTarget?.trimmingCharacters(in: .whitespacesAndNewlines),
|
||||
!lastAuto.isEmpty,
|
||||
trimmed == lastAuto
|
||||
current == lastAuto
|
||||
{
|
||||
return true
|
||||
}
|
||||
|
||||
39
apps/macos/Sources/OpenClaw/ContextRootMenuLabelView.swift
Normal file
39
apps/macos/Sources/OpenClaw/ContextRootMenuLabelView.swift
Normal file
@@ -0,0 +1,39 @@
|
||||
import SwiftUI
|
||||
|
||||
struct ContextRootMenuLabelView: View {
|
||||
let subtitle: String
|
||||
let width: CGFloat
|
||||
@Environment(\.menuItemHighlighted) private var isHighlighted
|
||||
|
||||
private var palette: MenuItemHighlightColors.Palette {
|
||||
MenuItemHighlightColors.palette(self.isHighlighted)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
HStack(alignment: .firstTextBaseline, spacing: 8) {
|
||||
Text("Context")
|
||||
.font(.callout.weight(.semibold))
|
||||
.foregroundStyle(self.palette.primary)
|
||||
.lineLimit(1)
|
||||
.layoutPriority(1)
|
||||
|
||||
Spacer(minLength: 8)
|
||||
|
||||
Text(self.subtitle)
|
||||
.font(.caption.monospacedDigit())
|
||||
.foregroundStyle(self.palette.secondary)
|
||||
.lineLimit(1)
|
||||
.truncationMode(.tail)
|
||||
.layoutPriority(2)
|
||||
|
||||
Image(systemName: "chevron.right")
|
||||
.font(.caption.weight(.semibold))
|
||||
.foregroundStyle(self.palette.secondary)
|
||||
.padding(.leading, 2)
|
||||
}
|
||||
.padding(.vertical, 8)
|
||||
.padding(.leading, 22)
|
||||
.padding(.trailing, 14)
|
||||
.frame(width: max(1, self.width), alignment: .leading)
|
||||
}
|
||||
}
|
||||
@@ -253,12 +253,11 @@ enum ExecApprovalsPromptPresenter {
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private static func buildAccessoryView(_ request: ExecApprovalPromptRequest) -> NSView {
|
||||
static func buildAccessoryView(_ request: ExecApprovalPromptRequest) -> NSView {
|
||||
let stack = NSStackView()
|
||||
stack.orientation = .vertical
|
||||
stack.spacing = 8
|
||||
stack.alignment = .leading
|
||||
stack.translatesAutoresizingMaskIntoConstraints = false
|
||||
stack.widthAnchor.constraint(greaterThanOrEqualToConstant: 380).isActive = true
|
||||
|
||||
let commandTitle = NSTextField(labelWithString: "Command")
|
||||
@@ -337,6 +336,10 @@ enum ExecApprovalsPromptPresenter {
|
||||
footer.font = NSFont.systemFont(ofSize: NSFont.smallSystemFontSize)
|
||||
stack.addArrangedSubview(footer)
|
||||
|
||||
// NSAlert reserves accessory space from the view frame, not from Auto Layout constraints.
|
||||
// Give the top-level accessory an explicit frame so its subviews do not paint over the
|
||||
// alert title, message, and buttons while the frame remains zero-sized.
|
||||
stack.frame = NSRect(origin: .zero, size: stack.fittingSize)
|
||||
return stack
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import AppKit
|
||||
import AVFoundation
|
||||
import Foundation
|
||||
import Observation
|
||||
import OpenClawKit
|
||||
import SwiftUI
|
||||
|
||||
/// Menu contents for the OpenClaw menu bar extra.
|
||||
@@ -14,6 +15,7 @@ struct MenuContent: View {
|
||||
private let heartbeatStore = HeartbeatStore.shared
|
||||
private let controlChannel = ControlChannel.shared
|
||||
private let activityStore = WorkActivityStore.shared
|
||||
private let nodesStore = NodesStore.shared
|
||||
@Bindable private var pairingPrompter = NodePairingApprovalPrompter.shared
|
||||
@Bindable private var devicePairingPrompter = DevicePairingApprovalPrompter.shared
|
||||
@Environment(\.openSettings) private var openSettings
|
||||
@@ -44,6 +46,9 @@ struct MenuContent: View {
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text(self.connectionLabel)
|
||||
self.statusLine(label: self.healthStatus.label, color: self.healthStatus.color)
|
||||
if let macNodeStatus = self.macNodeStatus {
|
||||
self.statusLine(label: macNodeStatus.label, color: macNodeStatus.color)
|
||||
}
|
||||
if self.pairingPrompter.pendingCount > 0 {
|
||||
let repairCount = self.pairingPrompter.pendingRepairCount
|
||||
let repairSuffix = repairCount > 0 ? " · \(repairCount) repair" : ""
|
||||
@@ -351,6 +356,31 @@ struct MenuContent: View {
|
||||
}
|
||||
}
|
||||
|
||||
private var macNodeStatus: (label: String, color: Color)? {
|
||||
guard self.state.connectionMode != .unconfigured else { return nil }
|
||||
guard case .connected = self.controlChannel.state else { return nil }
|
||||
|
||||
let deviceId = DeviceIdentityStore.loadOrCreate().deviceId
|
||||
if let entry = self.nodesStore.nodes.first(where: { $0.nodeId == deviceId }) {
|
||||
guard entry.isConnected else {
|
||||
return ("Mac capabilities offline", .orange)
|
||||
}
|
||||
let commands = Set(entry.commands ?? [])
|
||||
let missingRequiredCommands = [
|
||||
OpenClawSystemCommand.notify.rawValue,
|
||||
OpenClawSystemCommand.run.rawValue,
|
||||
OpenClawSystemCommand.which.rawValue,
|
||||
].filter { !commands.contains($0) }
|
||||
if !missingRequiredCommands.isEmpty {
|
||||
return ("Mac capabilities incomplete", .orange)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
guard !self.nodesStore.isLoading, !self.nodesStore.nodes.isEmpty else { return nil }
|
||||
return ("Mac capabilities offline", .orange)
|
||||
}
|
||||
|
||||
private var healthStatus: (label: String, color: Color) {
|
||||
if let activity = self.activityStore.current {
|
||||
let color: Color = activity.role == .main ? .accentColor : .gray
|
||||
|
||||
@@ -176,99 +176,31 @@ extension MenuSessionsInjector {
|
||||
let channelState = ControlChannel.shared.state
|
||||
|
||||
var cursor = insertIndex
|
||||
var headerView: NSView?
|
||||
|
||||
if let snapshot = self.cachedSnapshot {
|
||||
let now = Date()
|
||||
let mainKey = self.mainSessionKey
|
||||
let rows = snapshot.rows.filter { row in
|
||||
if row.key == "main", mainKey != "main" { return false }
|
||||
if row.key == mainKey { return true }
|
||||
guard let updatedAt = row.updatedAt else { return false }
|
||||
return now.timeIntervalSince(updatedAt) <= self.activeWindowSeconds
|
||||
}.sorted { lhs, rhs in
|
||||
if lhs.key == mainKey { return true }
|
||||
if rhs.key == mainKey { return false }
|
||||
return (lhs.updatedAt ?? .distantPast) > (rhs.updatedAt ?? .distantPast)
|
||||
}
|
||||
if !rows.isEmpty {
|
||||
let previewKeys = rows.prefix(20).map(\.key)
|
||||
let task = Task {
|
||||
await SessionMenuPreviewLoader.prewarm(sessionKeys: previewKeys, maxItems: 10)
|
||||
}
|
||||
self.previewTasks.append(task)
|
||||
}
|
||||
|
||||
let headerItem = NSMenuItem()
|
||||
headerItem.tag = self.tag
|
||||
headerItem.isEnabled = false
|
||||
let statusText = self
|
||||
.cachedErrorText ?? (isConnected ? nil : self.controlChannelStatusText(for: channelState))
|
||||
let hosted = self.makeHostedView(
|
||||
rootView: AnyView(MenuSessionsHeaderView(
|
||||
count: rows.count,
|
||||
statusText: statusText)),
|
||||
width: width,
|
||||
highlighted: false)
|
||||
headerItem.view = hosted
|
||||
headerView = hosted
|
||||
menu.insertItem(headerItem, at: cursor)
|
||||
cursor += 1
|
||||
|
||||
if rows.isEmpty {
|
||||
menu.insertItem(
|
||||
self.makeMessageItem(text: "No active sessions", symbolName: "minus", width: width),
|
||||
at: cursor)
|
||||
cursor += 1
|
||||
} else {
|
||||
for row in rows {
|
||||
let item = NSMenuItem()
|
||||
item.tag = self.tag
|
||||
item.isEnabled = true
|
||||
item.submenu = self.buildSubmenu(for: row, storePath: snapshot.storePath)
|
||||
item.view = self.makeHostedView(
|
||||
rootView: AnyView(SessionMenuLabelView(row: row, width: width)),
|
||||
width: width,
|
||||
highlighted: true)
|
||||
menu.insertItem(item, at: cursor)
|
||||
cursor += 1
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let headerItem = NSMenuItem()
|
||||
headerItem.tag = self.tag
|
||||
headerItem.isEnabled = false
|
||||
let statusText = isConnected
|
||||
? (self.cachedErrorText ?? "Loading sessions…")
|
||||
: self.controlChannelStatusText(for: channelState)
|
||||
let hosted = self.makeHostedView(
|
||||
rootView: AnyView(MenuSessionsHeaderView(
|
||||
count: 0,
|
||||
statusText: statusText)),
|
||||
width: width,
|
||||
highlighted: false)
|
||||
headerItem.view = hosted
|
||||
headerView = hosted
|
||||
menu.insertItem(headerItem, at: cursor)
|
||||
cursor += 1
|
||||
|
||||
if !isConnected {
|
||||
menu.insertItem(
|
||||
self.makeMessageItem(
|
||||
text: "Connect the gateway to see sessions",
|
||||
symbolName: "bolt.slash",
|
||||
width: width),
|
||||
at: cursor)
|
||||
cursor += 1
|
||||
}
|
||||
}
|
||||
let item = NSMenuItem(title: "Context", action: nil, keyEquivalent: "")
|
||||
item.tag = self.tag
|
||||
item.isEnabled = true
|
||||
item.submenu = self.buildContextSubmenu(
|
||||
width: width,
|
||||
isConnected: isConnected,
|
||||
channelState: channelState)
|
||||
let hosted = self.makeHostedView(
|
||||
rootView: AnyView(ContextRootMenuLabelView(
|
||||
subtitle: self.contextRootSubtitle(
|
||||
isConnected: isConnected,
|
||||
channelState: channelState),
|
||||
width: width)),
|
||||
width: width,
|
||||
highlighted: true)
|
||||
item.view = hosted
|
||||
menu.insertItem(item, at: cursor)
|
||||
cursor += 1
|
||||
|
||||
cursor = self.insertUsageSection(into: menu, at: cursor, width: width)
|
||||
cursor = self.insertCostUsageSection(into: menu, at: cursor, width: width)
|
||||
|
||||
DispatchQueue.main.async { [weak self, weak headerView] in
|
||||
guard let self, let headerView else { return }
|
||||
self.captureMenuWidthIfAvailable(from: headerView)
|
||||
DispatchQueue.main.async { [weak self, weak hosted] in
|
||||
guard let self, let hosted else { return }
|
||||
self.captureMenuWidthIfAvailable(from: hosted)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -346,6 +278,125 @@ extension MenuSessionsInjector {
|
||||
_ = cursor
|
||||
}
|
||||
|
||||
private func buildContextSubmenu(
|
||||
width: CGFloat,
|
||||
isConnected: Bool,
|
||||
channelState: ControlChannel.ConnectionState) -> NSMenu
|
||||
{
|
||||
let menu = NSMenu()
|
||||
let width = max(300, width)
|
||||
var cursor = 0
|
||||
|
||||
if let snapshot = self.cachedSnapshot {
|
||||
let rows = self.activeRows(from: snapshot)
|
||||
if !rows.isEmpty {
|
||||
let previewKeys = rows.prefix(20).map(\.key)
|
||||
let task = Task {
|
||||
await SessionMenuPreviewLoader.prewarm(sessionKeys: previewKeys, maxItems: 10)
|
||||
}
|
||||
self.previewTasks.append(task)
|
||||
}
|
||||
|
||||
let headerItem = NSMenuItem()
|
||||
headerItem.tag = self.tag
|
||||
headerItem.isEnabled = false
|
||||
let statusText = self.cachedErrorText
|
||||
?? (isConnected ? nil : self.controlChannelStatusText(for: channelState))
|
||||
headerItem.view = self.makeHostedView(
|
||||
rootView: AnyView(MenuSessionsHeaderView(
|
||||
count: rows.count,
|
||||
statusText: statusText)),
|
||||
width: width,
|
||||
highlighted: false)
|
||||
menu.insertItem(headerItem, at: cursor)
|
||||
cursor += 1
|
||||
|
||||
if rows.isEmpty {
|
||||
menu.insertItem(
|
||||
self.makeMessageItem(text: "No active sessions", symbolName: "minus", width: width),
|
||||
at: cursor)
|
||||
cursor += 1
|
||||
} else {
|
||||
for row in rows {
|
||||
let item = NSMenuItem()
|
||||
item.tag = self.tag
|
||||
item.isEnabled = true
|
||||
item.representedObject = row.key
|
||||
item.submenu = self.buildSubmenu(for: row, storePath: snapshot.storePath)
|
||||
item.view = self.makeHostedView(
|
||||
rootView: AnyView(SessionMenuLabelView(row: row, width: width)),
|
||||
width: width,
|
||||
highlighted: true)
|
||||
menu.insertItem(item, at: cursor)
|
||||
cursor += 1
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let headerItem = NSMenuItem()
|
||||
headerItem.tag = self.tag
|
||||
headerItem.isEnabled = false
|
||||
let statusText = isConnected
|
||||
? (self.cachedErrorText ?? "Loading sessions…")
|
||||
: self.controlChannelStatusText(for: channelState)
|
||||
headerItem.view = self.makeHostedView(
|
||||
rootView: AnyView(MenuSessionsHeaderView(
|
||||
count: 0,
|
||||
statusText: statusText)),
|
||||
width: width,
|
||||
highlighted: false)
|
||||
menu.insertItem(headerItem, at: cursor)
|
||||
cursor += 1
|
||||
|
||||
if !isConnected {
|
||||
menu.insertItem(
|
||||
self.makeMessageItem(
|
||||
text: "Connect the gateway to see sessions",
|
||||
symbolName: "bolt.slash",
|
||||
width: width),
|
||||
at: cursor)
|
||||
cursor += 1
|
||||
}
|
||||
}
|
||||
|
||||
_ = cursor
|
||||
return menu
|
||||
}
|
||||
|
||||
private func contextRootSubtitle(
|
||||
isConnected: Bool,
|
||||
channelState: ControlChannel.ConnectionState) -> String
|
||||
{
|
||||
if let snapshot = self.cachedSnapshot {
|
||||
return self.sessionsSubtitle(count: self.activeRows(from: snapshot).count)
|
||||
}
|
||||
|
||||
if isConnected {
|
||||
return self.cachedErrorText ?? "Loading…"
|
||||
}
|
||||
|
||||
return self.controlChannelStatusText(for: channelState)
|
||||
}
|
||||
|
||||
private func activeRows(from snapshot: SessionStoreSnapshot) -> [SessionRow] {
|
||||
let now = Date()
|
||||
let mainKey = self.mainSessionKey
|
||||
return snapshot.rows.filter { row in
|
||||
if row.key == "main", mainKey != "main" { return false }
|
||||
if row.key == mainKey { return true }
|
||||
guard let updatedAt = row.updatedAt else { return false }
|
||||
return now.timeIntervalSince(updatedAt) <= self.activeWindowSeconds
|
||||
}.sorted { lhs, rhs in
|
||||
if lhs.key == mainKey { return true }
|
||||
if rhs.key == mainKey { return false }
|
||||
return (lhs.updatedAt ?? .distantPast) > (rhs.updatedAt ?? .distantPast)
|
||||
}
|
||||
}
|
||||
|
||||
private func sessionsSubtitle(count: Int) -> String {
|
||||
if count == 1 { return "1 session · 24h" }
|
||||
return "\(count) sessions · 24h"
|
||||
}
|
||||
|
||||
private func insertUsageSection(into menu: NSMenu, at cursor: Int, width: CGFloat) -> Int {
|
||||
let rows = self.usageRows
|
||||
if rows.isEmpty {
|
||||
@@ -1156,7 +1207,7 @@ extension MenuSessionsInjector {
|
||||
}
|
||||
|
||||
private func sortedNodeEntries() -> [NodeInfo] {
|
||||
let entries = self.nodesStore.nodes.filter(\.isConnected)
|
||||
let entries = self.nodesStore.nodes.filter { $0.isConnected || $0.isPaired }
|
||||
return entries.sorted { lhs, rhs in
|
||||
if lhs.isConnected != rhs.isConnected { return lhs.isConnected }
|
||||
if lhs.isPaired != rhs.isPaired { return lhs.isPaired }
|
||||
@@ -1239,5 +1290,9 @@ extension MenuSessionsInjector {
|
||||
func testingFindNodesInsertIndex(in menu: NSMenu) -> Int? {
|
||||
self.findNodesInsertIndex(in: menu)
|
||||
}
|
||||
|
||||
func testingSortedNodeEntries() -> [NodeInfo] {
|
||||
self.sortedNodeEntries()
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -10,6 +10,7 @@ final class MacNodeModeCoordinator {
|
||||
private var task: Task<Void, Never>?
|
||||
private let runtime = MacNodeRuntime()
|
||||
private let session = GatewayNodeSession()
|
||||
private var autoRepairedTLSFingerprintsByStoreKey: [String: String] = [:]
|
||||
|
||||
func start() {
|
||||
guard self.task == nil else { return }
|
||||
@@ -58,8 +59,10 @@ final class MacNodeModeCoordinator {
|
||||
try? await Task.sleep(nanoseconds: 200_000_000)
|
||||
}
|
||||
|
||||
var attemptedURL: URL?
|
||||
do {
|
||||
let config = try await GatewayEndpointStore.shared.requireConfig()
|
||||
attemptedURL = config.url
|
||||
let caps = self.currentCaps()
|
||||
let commands = self.currentCommands(caps: caps)
|
||||
let permissions = await self.currentPermissions()
|
||||
@@ -109,6 +112,10 @@ final class MacNodeModeCoordinator {
|
||||
retryDelay = 1_000_000_000
|
||||
try? await Task.sleep(nanoseconds: 1_000_000_000)
|
||||
} catch {
|
||||
if await self.autoRepairStaleTLSPinIfNeeded(error: error, url: attemptedURL) {
|
||||
retryDelay = 1_000_000_000
|
||||
continue
|
||||
}
|
||||
self.logger.error("mac node gateway connect failed: \(error.localizedDescription, privacy: .public)")
|
||||
try? await Task.sleep(nanoseconds: min(retryDelay, 10_000_000_000))
|
||||
retryDelay = min(retryDelay * 2, 10_000_000_000)
|
||||
@@ -188,11 +195,49 @@ final class MacNodeModeCoordinator {
|
||||
Self.resolvedCommands(caps: caps)
|
||||
}
|
||||
|
||||
nonisolated static func tlsPinStoreKey(for url: URL) -> String {
|
||||
let host = url.host?.trimmingCharacters(in: .whitespacesAndNewlines).nonEmpty ?? "gateway"
|
||||
let port = url.port ?? 443
|
||||
return "\(host):\(port)"
|
||||
}
|
||||
|
||||
nonisolated static func shouldAutoRepairStaleTLSPin(url: URL, failure: GatewayTLSValidationFailure) -> Bool {
|
||||
guard failure.kind == .pinMismatch else { return false }
|
||||
guard url.scheme?.lowercased() == "wss" else { return false }
|
||||
guard failure.storeKey == nil || failure.storeKey == self.tlsPinStoreKey(for: url) else { return false }
|
||||
guard let host = url.host?.trimmingCharacters(in: .whitespacesAndNewlines).lowercased(), !host.isEmpty
|
||||
else { return false }
|
||||
|
||||
if LoopbackHost.isLoopback(host) {
|
||||
return failure.systemTrustOk
|
||||
}
|
||||
|
||||
// Tailscale Serve uses publicly trusted, rotating certificates for *.ts.net names.
|
||||
// A stale legacy leaf pin should not leave the companion app half-connected forever.
|
||||
if host == "ts.net" || host.hasSuffix(".ts.net") {
|
||||
return failure.systemTrustOk
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private func autoRepairStaleTLSPinIfNeeded(error: Error, url: URL?) async -> Bool {
|
||||
guard let tlsError = error as? GatewayTLSValidationError, let url else { return false }
|
||||
guard Self.shouldAutoRepairStaleTLSPin(url: url, failure: tlsError.failure) else { return false }
|
||||
let storeKey = tlsError.failure.storeKey ?? Self.tlsPinStoreKey(for: url)
|
||||
guard let observedFingerprint = tlsError.failure.observedFingerprint else { return false }
|
||||
guard self.autoRepairedTLSFingerprintsByStoreKey[storeKey] != observedFingerprint else { return false }
|
||||
|
||||
guard GatewayTLSStore.replaceFingerprint(observedFingerprint, stableID: storeKey) else { return false }
|
||||
self.autoRepairedTLSFingerprintsByStoreKey[storeKey] = observedFingerprint
|
||||
self.logger.info("replaced stale gateway TLS pin storeKey=\(storeKey, privacy: .public)")
|
||||
await self.session.disconnect()
|
||||
return true
|
||||
}
|
||||
|
||||
private func buildSessionBox(url: URL) -> WebSocketSessionBox? {
|
||||
guard url.scheme?.lowercased() == "wss" else { return nil }
|
||||
let host = url.host ?? "gateway"
|
||||
let port = url.port ?? 443
|
||||
let stableID = "\(host):\(port)"
|
||||
let stableID = Self.tlsPinStoreKey(for: url)
|
||||
let stored = GatewayTLSStore.loadFingerprint(stableID: stableID)
|
||||
let params = GatewayTLSParams(
|
||||
required: true,
|
||||
|
||||
@@ -44,10 +44,12 @@ struct NodeMenuEntryFormatter {
|
||||
}
|
||||
|
||||
static func roleText(_ entry: NodeInfo) -> String {
|
||||
if entry.isConnected { return "connected" }
|
||||
if self.isGateway(entry) { return "disconnected" }
|
||||
if entry.isPaired { return "paired" }
|
||||
return "unpaired"
|
||||
if self.isGateway(entry) {
|
||||
return entry.isConnected ? "connected" : "disconnected"
|
||||
}
|
||||
let pairing = entry.isPaired ? "paired" : "unpaired"
|
||||
let connection = entry.isConnected ? "connected" : "disconnected"
|
||||
return "\(pairing) · \(connection)"
|
||||
}
|
||||
|
||||
static func detailLeft(_ entry: NodeInfo) -> String {
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2026.4.27</string>
|
||||
<string>2026.4.30</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>2026042700</string>
|
||||
<string>2026043000</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>OpenClaw</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
|
||||
@@ -473,6 +473,7 @@ public struct SendParams: Codable, Sendable {
|
||||
public let message: String?
|
||||
public let mediaurl: String?
|
||||
public let mediaurls: [String]?
|
||||
public let asvoice: Bool?
|
||||
public let gifplayback: Bool?
|
||||
public let channel: String?
|
||||
public let accountid: String?
|
||||
@@ -487,6 +488,7 @@ public struct SendParams: Codable, Sendable {
|
||||
message: String?,
|
||||
mediaurl: String?,
|
||||
mediaurls: [String]?,
|
||||
asvoice: Bool?,
|
||||
gifplayback: Bool?,
|
||||
channel: String?,
|
||||
accountid: String?,
|
||||
@@ -500,6 +502,7 @@ public struct SendParams: Codable, Sendable {
|
||||
self.message = message
|
||||
self.mediaurl = mediaurl
|
||||
self.mediaurls = mediaurls
|
||||
self.asvoice = asvoice
|
||||
self.gifplayback = gifplayback
|
||||
self.channel = channel
|
||||
self.accountid = accountid
|
||||
@@ -515,6 +518,7 @@ public struct SendParams: Codable, Sendable {
|
||||
case message
|
||||
case mediaurl = "mediaUrl"
|
||||
case mediaurls = "mediaUrls"
|
||||
case asvoice = "asVoice"
|
||||
case gifplayback = "gifPlayback"
|
||||
case channel
|
||||
case accountid = "accountId"
|
||||
@@ -3208,6 +3212,188 @@ public struct AgentsFilesSetResult: Codable, Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
public struct ArtifactSummary: Codable, Sendable {
|
||||
public let id: String
|
||||
public let type: String
|
||||
public let title: String
|
||||
public let mimetype: String?
|
||||
public let sizebytes: Int?
|
||||
public let sessionkey: String?
|
||||
public let runid: String?
|
||||
public let taskid: String?
|
||||
public let messageseq: Int?
|
||||
public let source: String?
|
||||
public let download: [String: AnyCodable]
|
||||
|
||||
public init(
|
||||
id: String,
|
||||
type: String,
|
||||
title: String,
|
||||
mimetype: String?,
|
||||
sizebytes: Int?,
|
||||
sessionkey: String?,
|
||||
runid: String?,
|
||||
taskid: String?,
|
||||
messageseq: Int?,
|
||||
source: String?,
|
||||
download: [String: AnyCodable])
|
||||
{
|
||||
self.id = id
|
||||
self.type = type
|
||||
self.title = title
|
||||
self.mimetype = mimetype
|
||||
self.sizebytes = sizebytes
|
||||
self.sessionkey = sessionkey
|
||||
self.runid = runid
|
||||
self.taskid = taskid
|
||||
self.messageseq = messageseq
|
||||
self.source = source
|
||||
self.download = download
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case id
|
||||
case type
|
||||
case title
|
||||
case mimetype = "mimeType"
|
||||
case sizebytes = "sizeBytes"
|
||||
case sessionkey = "sessionKey"
|
||||
case runid = "runId"
|
||||
case taskid = "taskId"
|
||||
case messageseq = "messageSeq"
|
||||
case source
|
||||
case download
|
||||
}
|
||||
}
|
||||
|
||||
public struct ArtifactsListParams: Codable, Sendable {
|
||||
public let sessionkey: String?
|
||||
public let runid: String?
|
||||
public let taskid: String?
|
||||
|
||||
public init(
|
||||
sessionkey: String?,
|
||||
runid: String?,
|
||||
taskid: String?)
|
||||
{
|
||||
self.sessionkey = sessionkey
|
||||
self.runid = runid
|
||||
self.taskid = taskid
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case sessionkey = "sessionKey"
|
||||
case runid = "runId"
|
||||
case taskid = "taskId"
|
||||
}
|
||||
}
|
||||
|
||||
public struct ArtifactsListResult: Codable, Sendable {
|
||||
public let artifacts: [ArtifactSummary]
|
||||
|
||||
public init(
|
||||
artifacts: [ArtifactSummary])
|
||||
{
|
||||
self.artifacts = artifacts
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case artifacts
|
||||
}
|
||||
}
|
||||
|
||||
public struct ArtifactsGetParams: Codable, Sendable {
|
||||
public let sessionkey: String?
|
||||
public let runid: String?
|
||||
public let taskid: String?
|
||||
public let artifactid: String
|
||||
|
||||
public init(
|
||||
sessionkey: String?,
|
||||
runid: String?,
|
||||
taskid: String?,
|
||||
artifactid: String)
|
||||
{
|
||||
self.sessionkey = sessionkey
|
||||
self.runid = runid
|
||||
self.taskid = taskid
|
||||
self.artifactid = artifactid
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case sessionkey = "sessionKey"
|
||||
case runid = "runId"
|
||||
case taskid = "taskId"
|
||||
case artifactid = "artifactId"
|
||||
}
|
||||
}
|
||||
|
||||
public struct ArtifactsGetResult: Codable, Sendable {
|
||||
public let artifact: ArtifactSummary
|
||||
|
||||
public init(
|
||||
artifact: ArtifactSummary)
|
||||
{
|
||||
self.artifact = artifact
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case artifact
|
||||
}
|
||||
}
|
||||
|
||||
public struct ArtifactsDownloadParams: Codable, Sendable {
|
||||
public let sessionkey: String?
|
||||
public let runid: String?
|
||||
public let taskid: String?
|
||||
public let artifactid: String
|
||||
|
||||
public init(
|
||||
sessionkey: String?,
|
||||
runid: String?,
|
||||
taskid: String?,
|
||||
artifactid: String)
|
||||
{
|
||||
self.sessionkey = sessionkey
|
||||
self.runid = runid
|
||||
self.taskid = taskid
|
||||
self.artifactid = artifactid
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case sessionkey = "sessionKey"
|
||||
case runid = "runId"
|
||||
case taskid = "taskId"
|
||||
case artifactid = "artifactId"
|
||||
}
|
||||
}
|
||||
|
||||
public struct ArtifactsDownloadResult: Codable, Sendable {
|
||||
public let artifact: ArtifactSummary
|
||||
public let encoding: String?
|
||||
public let data: String?
|
||||
public let url: String?
|
||||
|
||||
public init(
|
||||
artifact: ArtifactSummary,
|
||||
encoding: String?,
|
||||
data: String?,
|
||||
url: String?)
|
||||
{
|
||||
self.artifact = artifact
|
||||
self.encoding = encoding
|
||||
self.data = data
|
||||
self.url = url
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case artifact
|
||||
case encoding
|
||||
case data
|
||||
case url
|
||||
}
|
||||
}
|
||||
|
||||
public struct AgentsListParams: Codable, Sendable {}
|
||||
|
||||
public struct AgentsListResult: Codable, Sendable {
|
||||
@@ -3644,6 +3830,100 @@ public struct ToolsEffectiveResult: Codable, Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
public struct ToolsInvokeParams: Codable, Sendable {
|
||||
public let name: String
|
||||
public let args: [String: AnyCodable]?
|
||||
public let sessionkey: String?
|
||||
public let agentid: String?
|
||||
public let confirm: Bool?
|
||||
public let idempotencykey: String?
|
||||
|
||||
public init(
|
||||
name: String,
|
||||
args: [String: AnyCodable]?,
|
||||
sessionkey: String?,
|
||||
agentid: String?,
|
||||
confirm: Bool?,
|
||||
idempotencykey: String?)
|
||||
{
|
||||
self.name = name
|
||||
self.args = args
|
||||
self.sessionkey = sessionkey
|
||||
self.agentid = agentid
|
||||
self.confirm = confirm
|
||||
self.idempotencykey = idempotencykey
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case name
|
||||
case args
|
||||
case sessionkey = "sessionKey"
|
||||
case agentid = "agentId"
|
||||
case confirm
|
||||
case idempotencykey = "idempotencyKey"
|
||||
}
|
||||
}
|
||||
|
||||
public struct ToolsInvokeError: Codable, Sendable {
|
||||
public let code: String
|
||||
public let message: String
|
||||
public let details: AnyCodable?
|
||||
|
||||
public init(
|
||||
code: String,
|
||||
message: String,
|
||||
details: AnyCodable?)
|
||||
{
|
||||
self.code = code
|
||||
self.message = message
|
||||
self.details = details
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case code
|
||||
case message
|
||||
case details
|
||||
}
|
||||
}
|
||||
|
||||
public struct ToolsInvokeResult: Codable, Sendable {
|
||||
public let ok: Bool
|
||||
public let toolname: String
|
||||
public let output: AnyCodable?
|
||||
public let requiresapproval: Bool?
|
||||
public let approvalid: String?
|
||||
public let source: AnyCodable?
|
||||
public let error: [String: AnyCodable]?
|
||||
|
||||
public init(
|
||||
ok: Bool,
|
||||
toolname: String,
|
||||
output: AnyCodable?,
|
||||
requiresapproval: Bool?,
|
||||
approvalid: String?,
|
||||
source: AnyCodable?,
|
||||
error: [String: AnyCodable]?)
|
||||
{
|
||||
self.ok = ok
|
||||
self.toolname = toolname
|
||||
self.output = output
|
||||
self.requiresapproval = requiresapproval
|
||||
self.approvalid = approvalid
|
||||
self.source = source
|
||||
self.error = error
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case ok
|
||||
case toolname = "toolName"
|
||||
case output
|
||||
case requiresapproval = "requiresApproval"
|
||||
case approvalid = "approvalId"
|
||||
case source
|
||||
case error
|
||||
}
|
||||
}
|
||||
|
||||
public struct SkillsBinsParams: Codable, Sendable {}
|
||||
|
||||
public struct SkillsBinsResult: Codable, Sendable {
|
||||
|
||||
@@ -46,4 +46,37 @@ struct CanvasWindowSmokeTests {
|
||||
controller.hideCanvas()
|
||||
controller.close()
|
||||
}
|
||||
|
||||
@Test func `A2UI auto navigation is idempotent for current host target`() throws {
|
||||
let root = FileManager().temporaryDirectory
|
||||
.appendingPathComponent("openclaw-canvas-test-\(UUID().uuidString)")
|
||||
try FileManager().createDirectory(at: root, withIntermediateDirectories: true)
|
||||
defer { try? FileManager().removeItem(at: root) }
|
||||
|
||||
let controller = try CanvasWindowController(
|
||||
sessionKey: "main",
|
||||
root: root,
|
||||
presentation: .window)
|
||||
defer { controller.close() }
|
||||
|
||||
let oldTarget = "http://127.0.0.1:18789/__openclaw__/a2ui/?platform=macos"
|
||||
let currentTarget = "http://127.0.0.1:18790/__openclaw__/a2ui/?platform=macos"
|
||||
let userTarget = "https://github.com/openclaw/openclaw"
|
||||
|
||||
#expect(controller.shouldAutoNavigateToA2UI(lastAutoTarget: nil, candidateTarget: currentTarget) == true)
|
||||
|
||||
controller.load(target: "/")
|
||||
#expect(controller.shouldAutoNavigateToA2UI(lastAutoTarget: nil, candidateTarget: currentTarget) == true)
|
||||
|
||||
controller.load(target: currentTarget)
|
||||
#expect(controller
|
||||
.shouldAutoNavigateToA2UI(lastAutoTarget: currentTarget, candidateTarget: currentTarget) == false)
|
||||
|
||||
controller.load(target: oldTarget)
|
||||
#expect(controller.shouldAutoNavigateToA2UI(lastAutoTarget: oldTarget, candidateTarget: currentTarget) == true)
|
||||
|
||||
controller.load(target: userTarget)
|
||||
#expect(controller
|
||||
.shouldAutoNavigateToA2UI(lastAutoTarget: currentTarget, candidateTarget: currentTarget) == false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import AppKit
|
||||
import Testing
|
||||
@testable import OpenClaw
|
||||
|
||||
@Suite(.serialized)
|
||||
@MainActor
|
||||
struct ExecApprovalPromptLayoutTests {
|
||||
@Test func `accessory view reserves nonzero alert layout space`() {
|
||||
let accessory = ExecApprovalsPromptPresenter.buildAccessoryView(
|
||||
ExecApprovalPromptRequest(
|
||||
command: "/bin/sh -lc \"hostname; uptime; echo '---'\"",
|
||||
cwd: "/Users/example/projects/openclaw",
|
||||
host: "node",
|
||||
security: "allowlist",
|
||||
ask: "on-miss",
|
||||
agentId: "main",
|
||||
resolvedPath: "/bin/sh",
|
||||
sessionKey: "session-1"))
|
||||
|
||||
#expect(accessory.frame.width >= 380)
|
||||
#expect(accessory.frame.height >= 160)
|
||||
|
||||
let alert = NSAlert()
|
||||
alert.messageText = "Allow this command?"
|
||||
alert.informativeText = "Review the command details before allowing."
|
||||
alert.accessoryView = accessory
|
||||
|
||||
#expect(alert.accessoryView?.frame.width == accessory.frame.width)
|
||||
#expect(alert.accessoryView?.frame.height == accessory.frame.height)
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,30 @@ import Testing
|
||||
@testable import OpenClaw
|
||||
|
||||
struct GatewayChannelConnectTests {
|
||||
private final class TLSFailureSession: WebSocketSessioning, GatewayTLSFailureProviding, @unchecked Sendable {
|
||||
private var failure: GatewayTLSValidationFailure?
|
||||
|
||||
init(failure: GatewayTLSValidationFailure) {
|
||||
self.failure = failure
|
||||
}
|
||||
|
||||
func makeWebSocketTask(url: URL) -> WebSocketTaskBox {
|
||||
_ = url
|
||||
let task = GatewayTestWebSocketTask(receiveHook: { _, receiveIndex in
|
||||
if receiveIndex == 0 {
|
||||
return .data(GatewayWebSocketTestSupport.connectChallengeData())
|
||||
}
|
||||
throw URLError(.userCancelledAuthentication)
|
||||
})
|
||||
return WebSocketTaskBox(task: task)
|
||||
}
|
||||
|
||||
func consumeLastTLSFailure() -> GatewayTLSValidationFailure? {
|
||||
defer { self.failure = nil }
|
||||
return self.failure
|
||||
}
|
||||
}
|
||||
|
||||
private enum FakeResponse {
|
||||
case helloOk(delayMs: Int)
|
||||
case invalid(delayMs: Int)
|
||||
@@ -109,4 +133,28 @@ struct GatewayChannelConnectTests {
|
||||
Issue.record("unexpected error: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
@Test func `connect maps user cancelled authentication with cached TLS failure`() async throws {
|
||||
let failure = GatewayTLSValidationFailure(
|
||||
kind: .pinMismatch,
|
||||
host: "gateway.example.ts.net",
|
||||
storeKey: "gateway.example.ts.net:443",
|
||||
expectedFingerprint: "old",
|
||||
observedFingerprint: "new",
|
||||
systemTrustOk: true)
|
||||
let session = TLSFailureSession(failure: failure)
|
||||
let channel = try GatewayChannelActor(
|
||||
url: #require(URL(string: "wss://gateway.example.ts.net")),
|
||||
token: nil,
|
||||
session: WebSocketSessionBox(session: session))
|
||||
|
||||
do {
|
||||
try await channel.connect()
|
||||
Issue.record("expected GatewayTLSValidationError")
|
||||
} catch let error as GatewayTLSValidationError {
|
||||
#expect(error.failure == failure)
|
||||
} catch {
|
||||
Issue.record("unexpected error: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
import Foundation
|
||||
import OpenClawKit
|
||||
import Testing
|
||||
|
||||
private extension NSLock {
|
||||
func withDeviceRetryLock<T>(_ body: () -> T) -> T {
|
||||
self.lock()
|
||||
defer { self.unlock() }
|
||||
return body()
|
||||
}
|
||||
}
|
||||
|
||||
private final class ConnectAuthRecorder: @unchecked Sendable {
|
||||
private let lock = NSLock()
|
||||
private var auths: [[String: Any]] = []
|
||||
|
||||
func append(from message: URLSessionWebSocketTask.Message) {
|
||||
guard let auth = Self.connectAuth(from: message) else { return }
|
||||
self.lock.withDeviceRetryLock {
|
||||
self.auths.append(auth)
|
||||
}
|
||||
}
|
||||
|
||||
func auth(at index: Int) -> [String: Any]? {
|
||||
self.lock.withDeviceRetryLock {
|
||||
guard self.auths.indices.contains(index) else { return nil }
|
||||
return self.auths[index]
|
||||
}
|
||||
}
|
||||
|
||||
private static func connectAuth(from message: URLSessionWebSocketTask.Message) -> [String: Any]? {
|
||||
let data: Data? = switch message {
|
||||
case let .data(raw):
|
||||
raw
|
||||
case let .string(text):
|
||||
Data(text.utf8)
|
||||
@unknown default:
|
||||
nil
|
||||
}
|
||||
guard let data,
|
||||
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
|
||||
json["type"] as? String == "req",
|
||||
json["method"] as? String == "connect",
|
||||
let params = json["params"] as? [String: Any],
|
||||
let auth = params["auth"] as? [String: Any]
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
return auth
|
||||
}
|
||||
}
|
||||
|
||||
private final class TrustedDeviceRetryGatewaySession: WebSocketSessioning, GatewayDeviceTokenRetryTrustProviding, @unchecked Sendable {
|
||||
let allowsDeviceTokenRetryAuth: Bool
|
||||
|
||||
private let lock = NSLock()
|
||||
private let recorder: ConnectAuthRecorder
|
||||
private var makeCount = 0
|
||||
|
||||
init(recorder: ConnectAuthRecorder, allowsDeviceTokenRetryAuth: Bool) {
|
||||
self.recorder = recorder
|
||||
self.allowsDeviceTokenRetryAuth = allowsDeviceTokenRetryAuth
|
||||
}
|
||||
|
||||
func makeWebSocketTask(url: URL) -> WebSocketTaskBox {
|
||||
_ = url
|
||||
let attemptIndex = self.lock.withDeviceRetryLock { () -> Int in
|
||||
let current = self.makeCount
|
||||
self.makeCount += 1
|
||||
return current
|
||||
}
|
||||
let recorder = self.recorder
|
||||
let task = GatewayTestWebSocketTask(
|
||||
sendHook: { _, message, sendIndex in
|
||||
if sendIndex == 0 {
|
||||
recorder.append(from: message)
|
||||
}
|
||||
},
|
||||
receiveHook: { task, receiveIndex in
|
||||
if receiveIndex == 0 {
|
||||
return .data(GatewayWebSocketTestSupport.connectChallengeData())
|
||||
}
|
||||
let id = task.snapshotConnectRequestID() ?? "connect"
|
||||
if attemptIndex == 0 {
|
||||
return .data(GatewayWebSocketTestSupport.connectAuthFailureData(
|
||||
id: id,
|
||||
detailCode: GatewayConnectAuthDetailCode.authTokenMismatch.rawValue,
|
||||
canRetryWithDeviceToken: true,
|
||||
recommendedNextStep: GatewayConnectRecoveryNextStep.retryWithDeviceToken.rawValue))
|
||||
}
|
||||
return .data(GatewayWebSocketTestSupport.connectOkData(id: id))
|
||||
})
|
||||
return WebSocketTaskBox(task: task)
|
||||
}
|
||||
}
|
||||
|
||||
@Suite(.serialized)
|
||||
struct GatewayChannelDeviceTokenRetryTests {
|
||||
@Test func `remote pinned TLS retries stale shared token with stored device token`() async throws {
|
||||
let tempDir = FileManager.default.temporaryDirectory
|
||||
.appendingPathComponent(UUID().uuidString, isDirectory: true)
|
||||
try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)
|
||||
let previousStateDir = ProcessInfo.processInfo.environment["OPENCLAW_STATE_DIR"]
|
||||
setenv("OPENCLAW_STATE_DIR", tempDir.path, 1)
|
||||
defer {
|
||||
if let previousStateDir {
|
||||
setenv("OPENCLAW_STATE_DIR", previousStateDir, 1)
|
||||
} else {
|
||||
unsetenv("OPENCLAW_STATE_DIR")
|
||||
}
|
||||
try? FileManager.default.removeItem(at: tempDir)
|
||||
}
|
||||
|
||||
let identity = DeviceIdentityStore.loadOrCreate()
|
||||
_ = DeviceAuthStore.storeToken(
|
||||
deviceId: identity.deviceId,
|
||||
role: "operator",
|
||||
token: "stored-device-token")
|
||||
|
||||
let recorder = ConnectAuthRecorder()
|
||||
let session = TrustedDeviceRetryGatewaySession(
|
||||
recorder: recorder,
|
||||
allowsDeviceTokenRetryAuth: true)
|
||||
let options = GatewayConnectOptions(
|
||||
role: "operator",
|
||||
scopes: ["operator.read"],
|
||||
caps: [],
|
||||
commands: [],
|
||||
permissions: [:],
|
||||
clientId: "openclaw-ios-test",
|
||||
clientMode: "ui",
|
||||
clientDisplayName: "iOS Test",
|
||||
includeDeviceIdentity: true)
|
||||
let channel = try GatewayChannelActor(
|
||||
url: #require(URL(string: "wss://gateway.example.com")),
|
||||
token: "stale-shared-token",
|
||||
session: WebSocketSessionBox(session: session),
|
||||
connectOptions: options)
|
||||
|
||||
do {
|
||||
try await channel.connect()
|
||||
Issue.record("expected stale shared-token connect to fail before device-token retry")
|
||||
} catch let error as GatewayConnectAuthError {
|
||||
#expect(error.detail == .authTokenMismatch)
|
||||
}
|
||||
|
||||
try await channel.connect()
|
||||
|
||||
let firstAuth = try #require(recorder.auth(at: 0))
|
||||
#expect(firstAuth["token"] as? String == "stale-shared-token")
|
||||
#expect(firstAuth["deviceToken"] == nil)
|
||||
|
||||
let retryAuth = try #require(recorder.auth(at: 1))
|
||||
#expect(retryAuth["token"] as? String == "stale-shared-token")
|
||||
#expect(retryAuth["deviceToken"] as? String == "stored-device-token")
|
||||
|
||||
await channel.shutdown()
|
||||
}
|
||||
}
|
||||
@@ -29,4 +29,61 @@ struct MacNodeModeCoordinatorTests {
|
||||
#expect(caps.contains(OpenClawCapability.browser.rawValue))
|
||||
#expect(commands.contains(OpenClawBrowserCommand.proxy.rawValue))
|
||||
}
|
||||
|
||||
@Test func `tls pin store key uses default wss port`() throws {
|
||||
let url = try #require(URL(string: "wss://gateway.example.ts.net"))
|
||||
#expect(MacNodeModeCoordinator.tlsPinStoreKey(for: url) == "gateway.example.ts.net:443")
|
||||
}
|
||||
|
||||
@Test func `auto repairs trusted tailscale serve pin mismatch`() throws {
|
||||
let url = try #require(URL(string: "wss://gateway.example.ts.net"))
|
||||
let failure = GatewayTLSValidationFailure(
|
||||
kind: .pinMismatch,
|
||||
host: "gateway.example.ts.net",
|
||||
storeKey: "gateway.example.ts.net:443",
|
||||
expectedFingerprint: "old",
|
||||
observedFingerprint: "new",
|
||||
systemTrustOk: true)
|
||||
|
||||
#expect(MacNodeModeCoordinator.shouldAutoRepairStaleTLSPin(url: url, failure: failure))
|
||||
}
|
||||
|
||||
@Test func `does not auto repair untrusted remote pin mismatch`() throws {
|
||||
let url = try #require(URL(string: "wss://gateway.example.com"))
|
||||
let failure = GatewayTLSValidationFailure(
|
||||
kind: .pinMismatch,
|
||||
host: "gateway.example.com",
|
||||
storeKey: "gateway.example.com:443",
|
||||
expectedFingerprint: "old",
|
||||
observedFingerprint: "new",
|
||||
systemTrustOk: true)
|
||||
|
||||
#expect(!MacNodeModeCoordinator.shouldAutoRepairStaleTLSPin(url: url, failure: failure))
|
||||
}
|
||||
|
||||
@Test func `auto repairs trusted loopback pin mismatch`() throws {
|
||||
let url = try #require(URL(string: "wss://127.0.0.1:18789"))
|
||||
let failure = GatewayTLSValidationFailure(
|
||||
kind: .pinMismatch,
|
||||
host: "127.0.0.1",
|
||||
storeKey: "127.0.0.1:18789",
|
||||
expectedFingerprint: "old",
|
||||
observedFingerprint: "new",
|
||||
systemTrustOk: true)
|
||||
|
||||
#expect(MacNodeModeCoordinator.shouldAutoRepairStaleTLSPin(url: url, failure: failure))
|
||||
}
|
||||
|
||||
@Test func `does not auto repair untrusted loopback pin mismatch`() throws {
|
||||
let url = try #require(URL(string: "wss://127.0.0.1:18789"))
|
||||
let failure = GatewayTLSValidationFailure(
|
||||
kind: .pinMismatch,
|
||||
host: "127.0.0.1",
|
||||
storeKey: "127.0.0.1:18789",
|
||||
expectedFingerprint: "old",
|
||||
observedFingerprint: "new",
|
||||
systemTrustOk: false)
|
||||
|
||||
#expect(!MacNodeModeCoordinator.shouldAutoRepairStaleTLSPin(url: url, failure: failure))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,9 @@ struct MenuSessionsInjectorTests {
|
||||
menu.addItem(NSMenuItem(title: "Send Heartbeats", action: nil, keyEquivalent: ""))
|
||||
|
||||
injector.injectForTesting(into: menu)
|
||||
#expect(menu.items.contains { $0.tag == 9_415_557 })
|
||||
let contextItem = menu.items.first { $0.tag == 9_415_557 && $0.title == "Context" }
|
||||
#expect(contextItem != nil)
|
||||
#expect(contextItem?.submenu != nil)
|
||||
}
|
||||
|
||||
@Test func `injects session rows`() throws {
|
||||
@@ -114,8 +116,12 @@ struct MenuSessionsInjectorTests {
|
||||
menu.addItem(NSMenuItem(title: "Settings…", action: nil, keyEquivalent: ""))
|
||||
|
||||
injector.injectForTesting(into: menu)
|
||||
#expect(menu.items.contains { $0.tag == 9_415_557 })
|
||||
let contextItem = try #require(menu.items.first { $0.tag == 9_415_557 && $0.title == "Context" })
|
||||
let contextSubmenu = try #require(contextItem.submenu)
|
||||
#expect(menu.items.filter { $0.tag == 9_415_557 && $0.title == "Context" }.count == 1)
|
||||
#expect(menu.items.contains { $0.tag == 9_415_557 && $0.isSeparatorItem })
|
||||
#expect(contextSubmenu.items.compactMap { $0.representedObject as? String }.filter { ["main", "discord:group:alpha"].contains($0) }.count == 2)
|
||||
#expect(contextSubmenu.items.allSatisfy { $0.title != "Usage cost (30 days)" })
|
||||
let sendHeartbeatsIndex = try #require(menu.items.firstIndex(where: { $0.title == "Send Heartbeats" }))
|
||||
let openDashboardIndex = try #require(menu.items.firstIndex(where: { $0.title == "Open Dashboard" }))
|
||||
let firstInjectedIndex = try #require(menu.items.firstIndex(where: { $0.tag == 9_415_557 }))
|
||||
@@ -160,9 +166,57 @@ struct MenuSessionsInjectorTests {
|
||||
|
||||
injector.injectForTesting(into: menu)
|
||||
|
||||
let contextItem = menu.items.first { $0.tag == 9_415_557 && $0.title == "Context" }
|
||||
#expect(contextItem?.submenu?.items.allSatisfy { $0.title != "Usage cost (30 days)" } == true)
|
||||
let usageCostItem = menu.items.first { $0.title == "Usage cost (30 days)" }
|
||||
#expect(usageCostItem != nil)
|
||||
#expect(usageCostItem?.submenu != nil)
|
||||
#expect(usageCostItem?.submenu?.delegate == nil)
|
||||
}
|
||||
|
||||
@Test func `node status text distinguishes paired disconnected nodes`() {
|
||||
let pairedDisconnected = Self.node(id: "paired", paired: true, connected: false)
|
||||
let unpairedDisconnected = Self.node(id: "unpaired", paired: false, connected: false)
|
||||
let connected = Self.node(id: "connected", paired: true, connected: true)
|
||||
|
||||
#expect(NodeMenuEntryFormatter.roleText(pairedDisconnected) == "paired · disconnected")
|
||||
#expect(NodeMenuEntryFormatter.roleText(unpairedDisconnected) == "unpaired · disconnected")
|
||||
#expect(NodeMenuEntryFormatter.roleText(connected) == "paired · connected")
|
||||
}
|
||||
|
||||
@Test func `sorted node entries include paired disconnected nodes`() {
|
||||
let injector = MenuSessionsInjector()
|
||||
defer { NodesStore.shared.nodes = [] }
|
||||
NodesStore.shared.nodes = [
|
||||
Self.node(id: "ignored", paired: false, connected: false, displayName: "Ignored"),
|
||||
Self.node(id: "paired", paired: true, connected: false, displayName: "MacBook"),
|
||||
Self.node(id: "connected", paired: true, connected: true, displayName: "iPhone"),
|
||||
]
|
||||
|
||||
let entries = injector.testingSortedNodeEntries()
|
||||
#expect(entries.map(\.nodeId) == ["connected", "paired"])
|
||||
}
|
||||
|
||||
private static func node(
|
||||
id: String,
|
||||
paired: Bool,
|
||||
connected: Bool,
|
||||
displayName: String? = nil) -> NodeInfo
|
||||
{
|
||||
NodeInfo(
|
||||
nodeId: id,
|
||||
displayName: displayName ?? id,
|
||||
platform: "macOS 26.3.1",
|
||||
version: nil,
|
||||
coreVersion: nil,
|
||||
uiVersion: nil,
|
||||
deviceFamily: "Mac",
|
||||
modelIdentifier: nil,
|
||||
remoteIp: nil,
|
||||
caps: nil,
|
||||
commands: nil,
|
||||
permissions: nil,
|
||||
paired: paired,
|
||||
connected: connected)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -912,9 +912,6 @@ public actor GatewayChannelActor {
|
||||
}
|
||||
|
||||
private func isTrustedDeviceRetryEndpoint() -> Bool {
|
||||
// This client currently treats loopback as the only trusted retry target.
|
||||
// Unlike the Node gateway client, it does not yet expose a pinned TLS-fingerprint
|
||||
// trust path for remote retry, so remote fallback remains disabled by default.
|
||||
guard let host = self.url.host?.trimmingCharacters(in: .whitespacesAndNewlines).lowercased(),
|
||||
!host.isEmpty
|
||||
else {
|
||||
@@ -923,6 +920,11 @@ public actor GatewayChannelActor {
|
||||
if host == "localhost" || host == "::1" || host == "127.0.0.1" || host.hasPrefix("127.") {
|
||||
return true
|
||||
}
|
||||
if self.url.scheme?.lowercased() == "wss",
|
||||
let trust = self.session as? GatewayDeviceTokenRetryTrustProviding
|
||||
{
|
||||
return trust.allowsDeviceTokenRetryAuth
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -1010,10 +1012,13 @@ public actor GatewayChannelActor {
|
||||
|
||||
/// Wrap low-level URLSession/WebSocket errors with context so UI can surface them.
|
||||
private func wrap(_ error: Error, context: String) -> Error {
|
||||
if error is GatewayConnectAuthError || error is GatewayResponseError || error is GatewayDecodingError {
|
||||
if error is GatewayConnectAuthError || error is GatewayResponseError || error is GatewayDecodingError || error is GatewayTLSValidationError {
|
||||
return error
|
||||
}
|
||||
if let urlError = error as? URLError {
|
||||
if let failure = (self.session as? GatewayTLSFailureProviding)?.consumeLastTLSFailure() {
|
||||
return GatewayTLSValidationError(failure: failure, context: context)
|
||||
}
|
||||
let desc = urlError.localizedDescription.isEmpty ? "cancelled" : urlError.localizedDescription
|
||||
return NSError(
|
||||
domain: URLError.errorDomain,
|
||||
|
||||
@@ -30,6 +30,9 @@ public struct GatewayConnectionProblem: Equatable, Sendable {
|
||||
case connectionRefused
|
||||
case reachabilityFailed
|
||||
case websocketCancelled
|
||||
case tlsPinMismatch
|
||||
case tlsCertificateUntrusted
|
||||
case tlsCertificateUnavailable
|
||||
case unknown
|
||||
}
|
||||
|
||||
@@ -170,6 +173,9 @@ public enum GatewayConnectionProblemMapper {
|
||||
if let responseError = error as? GatewayResponseError {
|
||||
return self.map(responseError)
|
||||
}
|
||||
if let tlsError = error as? GatewayTLSValidationError {
|
||||
return self.map(tlsError)
|
||||
}
|
||||
return self.mapTransportError(error)
|
||||
}
|
||||
|
||||
@@ -518,6 +524,51 @@ public enum GatewayConnectionProblemMapper {
|
||||
return nil
|
||||
}
|
||||
|
||||
private static func map(_ tlsError: GatewayTLSValidationError) -> GatewayConnectionProblem {
|
||||
let failure = tlsError.failure
|
||||
switch failure.kind {
|
||||
case .pinMismatch:
|
||||
let trustedSuffix = failure.systemTrustOk
|
||||
? " The new certificate is trusted by this device; this is commonly caused by certificate rotation."
|
||||
: " This device could not verify the new certificate."
|
||||
return GatewayConnectionProblem(
|
||||
kind: .tlsPinMismatch,
|
||||
owner: failure.systemTrustOk ? .network : .unknown,
|
||||
title: "Gateway certificate changed",
|
||||
message: "The saved TLS certificate pin for \(failure.host) no longer matches the gateway certificate.\(trustedSuffix)",
|
||||
actionLabel: "Review certificate",
|
||||
actionCommand: nil,
|
||||
docsURL: URL(string: "https://docs.openclaw.ai/gateway/troubleshooting"),
|
||||
retryable: false,
|
||||
pauseReconnect: true,
|
||||
technicalDetails: tlsError.localizedDescription)
|
||||
case .certificateUnavailable:
|
||||
return GatewayConnectionProblem(
|
||||
kind: .tlsCertificateUnavailable,
|
||||
owner: .network,
|
||||
title: "Gateway certificate unavailable",
|
||||
message: "OpenClaw could not read the gateway certificate for \(failure.host).",
|
||||
actionLabel: "Retry",
|
||||
actionCommand: nil,
|
||||
docsURL: URL(string: "https://docs.openclaw.ai/gateway/troubleshooting"),
|
||||
retryable: true,
|
||||
pauseReconnect: false,
|
||||
technicalDetails: tlsError.localizedDescription)
|
||||
case .untrustedCertificate:
|
||||
return GatewayConnectionProblem(
|
||||
kind: .tlsCertificateUntrusted,
|
||||
owner: .network,
|
||||
title: "Gateway certificate is not trusted",
|
||||
message: "This device does not trust the TLS certificate presented by \(failure.host).",
|
||||
actionLabel: "Check certificate",
|
||||
actionCommand: nil,
|
||||
docsURL: URL(string: "https://docs.openclaw.ai/gateway/troubleshooting"),
|
||||
retryable: false,
|
||||
pauseReconnect: true,
|
||||
technicalDetails: tlsError.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
private static func mapTransportError(_ error: Error) -> GatewayConnectionProblem? {
|
||||
let nsError = error as NSError
|
||||
let rawMessage = nsError.userInfo[NSLocalizedDescriptionKey] as? String ?? nsError.localizedDescription
|
||||
|
||||
@@ -16,6 +16,69 @@ public struct GatewayTLSParams: Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
public enum GatewayTLSValidationFailureKind: String, Sendable {
|
||||
case pinMismatch
|
||||
case certificateUnavailable
|
||||
case untrustedCertificate
|
||||
}
|
||||
|
||||
public struct GatewayTLSValidationFailure: Equatable, Sendable {
|
||||
public let kind: GatewayTLSValidationFailureKind
|
||||
public let host: String
|
||||
public let storeKey: String?
|
||||
public let expectedFingerprint: String?
|
||||
public let observedFingerprint: String?
|
||||
public let systemTrustOk: Bool
|
||||
|
||||
public init(
|
||||
kind: GatewayTLSValidationFailureKind,
|
||||
host: String,
|
||||
storeKey: String?,
|
||||
expectedFingerprint: String?,
|
||||
observedFingerprint: String?,
|
||||
systemTrustOk: Bool)
|
||||
{
|
||||
self.kind = kind
|
||||
self.host = host
|
||||
self.storeKey = storeKey
|
||||
self.expectedFingerprint = expectedFingerprint
|
||||
self.observedFingerprint = observedFingerprint
|
||||
self.systemTrustOk = systemTrustOk
|
||||
}
|
||||
}
|
||||
|
||||
public struct GatewayTLSValidationError: LocalizedError, Sendable {
|
||||
public let failure: GatewayTLSValidationFailure
|
||||
public let context: String
|
||||
|
||||
public init(failure: GatewayTLSValidationFailure, context: String) {
|
||||
self.failure = failure
|
||||
self.context = context
|
||||
}
|
||||
|
||||
public var errorDescription: String? {
|
||||
let prefix = self.context.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
switch self.failure.kind {
|
||||
case .pinMismatch:
|
||||
let expected = self.failure.expectedFingerprint ?? "unknown"
|
||||
let observed = self.failure.observedFingerprint ?? "unknown"
|
||||
return "\(prefix): TLS certificate pin mismatch for \(self.failure.host) (expected \(expected), observed \(observed))"
|
||||
case .certificateUnavailable:
|
||||
return "\(prefix): TLS certificate unavailable for \(self.failure.host)"
|
||||
case .untrustedCertificate:
|
||||
return "\(prefix): TLS certificate is not trusted for \(self.failure.host)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public protocol GatewayTLSFailureProviding: AnyObject {
|
||||
func consumeLastTLSFailure() -> GatewayTLSValidationFailure?
|
||||
}
|
||||
|
||||
public protocol GatewayDeviceTokenRetryTrustProviding: AnyObject {
|
||||
var allowsDeviceTokenRetryAuth: Bool { get }
|
||||
}
|
||||
|
||||
public enum GatewayTLSStore {
|
||||
private static let keychainService = "ai.openclaw.tls-pinning"
|
||||
|
||||
@@ -35,6 +98,15 @@ public enum GatewayTLSStore {
|
||||
_ = GenericPasswordKeychainStore.saveString(value, service: self.keychainService, account: stableID)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public static func replaceFingerprint(_ value: String, stableID: String) -> Bool {
|
||||
guard GenericPasswordKeychainStore.saveString(value, service: self.keychainService, account: stableID) else {
|
||||
return false
|
||||
}
|
||||
self.clearLegacyFingerprint(stableID: stableID)
|
||||
return true
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public static func clearFingerprint(stableID: String) -> Bool {
|
||||
let removedKeychain = GenericPasswordKeychainStore.delete(
|
||||
@@ -87,8 +159,10 @@ public enum GatewayTLSStore {
|
||||
}
|
||||
}
|
||||
|
||||
public final class GatewayTLSPinningSession: NSObject, WebSocketSessioning, URLSessionDelegate, @unchecked Sendable {
|
||||
public final class GatewayTLSPinningSession: NSObject, WebSocketSessioning, URLSessionDelegate, GatewayTLSFailureProviding, GatewayDeviceTokenRetryTrustProviding, @unchecked Sendable {
|
||||
private let params: GatewayTLSParams
|
||||
private let failureLock = NSLock()
|
||||
private var lastTLSFailure: GatewayTLSValidationFailure?
|
||||
private lazy var session: URLSession = {
|
||||
let config = URLSessionConfiguration.default
|
||||
config.waitsForConnectivity = true
|
||||
@@ -100,6 +174,30 @@ public final class GatewayTLSPinningSession: NSObject, WebSocketSessioning, URLS
|
||||
super.init()
|
||||
}
|
||||
|
||||
public var allowsDeviceTokenRetryAuth: Bool {
|
||||
self.params.expectedFingerprint?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false
|
||||
}
|
||||
|
||||
public func consumeLastTLSFailure() -> GatewayTLSValidationFailure? {
|
||||
self.failureLock.lock()
|
||||
defer { self.failureLock.unlock() }
|
||||
let failure = self.lastTLSFailure
|
||||
self.lastTLSFailure = nil
|
||||
return failure
|
||||
}
|
||||
|
||||
private func recordTLSFailure(_ failure: GatewayTLSValidationFailure) {
|
||||
self.failureLock.lock()
|
||||
self.lastTLSFailure = failure
|
||||
self.failureLock.unlock()
|
||||
}
|
||||
|
||||
private func clearTLSFailure() {
|
||||
self.failureLock.lock()
|
||||
self.lastTLSFailure = nil
|
||||
self.failureLock.unlock()
|
||||
}
|
||||
|
||||
public func makeWebSocketTask(url: URL) -> WebSocketTaskBox {
|
||||
let task = self.session.webSocketTask(with: url)
|
||||
task.maximumMessageSize = 16 * 1024 * 1024
|
||||
@@ -118,12 +216,23 @@ public final class GatewayTLSPinningSession: NSObject, WebSocketSessioning, URLS
|
||||
return
|
||||
}
|
||||
|
||||
let host = challenge.protectionSpace.host
|
||||
let systemTrustOk = SecTrustEvaluateWithError(trust, nil)
|
||||
let expected = self.params.expectedFingerprint.map(normalizeFingerprint)
|
||||
if let fingerprint = certificateFingerprint(trust) {
|
||||
let fingerprint = certificateFingerprint(trust)
|
||||
if let fingerprint {
|
||||
if let expected {
|
||||
if fingerprint == expected {
|
||||
self.clearTLSFailure()
|
||||
completionHandler(.useCredential, URLCredential(trust: trust))
|
||||
} else {
|
||||
self.recordTLSFailure(GatewayTLSValidationFailure(
|
||||
kind: .pinMismatch,
|
||||
host: host,
|
||||
storeKey: self.params.storeKey,
|
||||
expectedFingerprint: expected,
|
||||
observedFingerprint: fingerprint,
|
||||
systemTrustOk: systemTrustOk))
|
||||
completionHandler(.cancelAuthenticationChallenge, nil)
|
||||
}
|
||||
return
|
||||
@@ -132,15 +241,23 @@ public final class GatewayTLSPinningSession: NSObject, WebSocketSessioning, URLS
|
||||
if let storeKey = params.storeKey {
|
||||
GatewayTLSStore.saveFingerprint(fingerprint, stableID: storeKey)
|
||||
}
|
||||
self.clearTLSFailure()
|
||||
completionHandler(.useCredential, URLCredential(trust: trust))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
let ok = SecTrustEvaluateWithError(trust, nil)
|
||||
if ok || !self.params.required {
|
||||
if systemTrustOk || !self.params.required {
|
||||
self.clearTLSFailure()
|
||||
completionHandler(.useCredential, URLCredential(trust: trust))
|
||||
} else {
|
||||
self.recordTLSFailure(GatewayTLSValidationFailure(
|
||||
kind: fingerprint == nil ? .certificateUnavailable : .untrustedCertificate,
|
||||
host: host,
|
||||
storeKey: self.params.storeKey,
|
||||
expectedFingerprint: expected,
|
||||
observedFingerprint: fingerprint,
|
||||
systemTrustOk: false))
|
||||
completionHandler(.cancelAuthenticationChallenge, nil)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -473,6 +473,7 @@ public struct SendParams: Codable, Sendable {
|
||||
public let message: String?
|
||||
public let mediaurl: String?
|
||||
public let mediaurls: [String]?
|
||||
public let asvoice: Bool?
|
||||
public let gifplayback: Bool?
|
||||
public let channel: String?
|
||||
public let accountid: String?
|
||||
@@ -487,6 +488,7 @@ public struct SendParams: Codable, Sendable {
|
||||
message: String?,
|
||||
mediaurl: String?,
|
||||
mediaurls: [String]?,
|
||||
asvoice: Bool?,
|
||||
gifplayback: Bool?,
|
||||
channel: String?,
|
||||
accountid: String?,
|
||||
@@ -500,6 +502,7 @@ public struct SendParams: Codable, Sendable {
|
||||
self.message = message
|
||||
self.mediaurl = mediaurl
|
||||
self.mediaurls = mediaurls
|
||||
self.asvoice = asvoice
|
||||
self.gifplayback = gifplayback
|
||||
self.channel = channel
|
||||
self.accountid = accountid
|
||||
@@ -515,6 +518,7 @@ public struct SendParams: Codable, Sendable {
|
||||
case message
|
||||
case mediaurl = "mediaUrl"
|
||||
case mediaurls = "mediaUrls"
|
||||
case asvoice = "asVoice"
|
||||
case gifplayback = "gifPlayback"
|
||||
case channel
|
||||
case accountid = "accountId"
|
||||
@@ -3208,6 +3212,188 @@ public struct AgentsFilesSetResult: Codable, Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
public struct ArtifactSummary: Codable, Sendable {
|
||||
public let id: String
|
||||
public let type: String
|
||||
public let title: String
|
||||
public let mimetype: String?
|
||||
public let sizebytes: Int?
|
||||
public let sessionkey: String?
|
||||
public let runid: String?
|
||||
public let taskid: String?
|
||||
public let messageseq: Int?
|
||||
public let source: String?
|
||||
public let download: [String: AnyCodable]
|
||||
|
||||
public init(
|
||||
id: String,
|
||||
type: String,
|
||||
title: String,
|
||||
mimetype: String?,
|
||||
sizebytes: Int?,
|
||||
sessionkey: String?,
|
||||
runid: String?,
|
||||
taskid: String?,
|
||||
messageseq: Int?,
|
||||
source: String?,
|
||||
download: [String: AnyCodable])
|
||||
{
|
||||
self.id = id
|
||||
self.type = type
|
||||
self.title = title
|
||||
self.mimetype = mimetype
|
||||
self.sizebytes = sizebytes
|
||||
self.sessionkey = sessionkey
|
||||
self.runid = runid
|
||||
self.taskid = taskid
|
||||
self.messageseq = messageseq
|
||||
self.source = source
|
||||
self.download = download
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case id
|
||||
case type
|
||||
case title
|
||||
case mimetype = "mimeType"
|
||||
case sizebytes = "sizeBytes"
|
||||
case sessionkey = "sessionKey"
|
||||
case runid = "runId"
|
||||
case taskid = "taskId"
|
||||
case messageseq = "messageSeq"
|
||||
case source
|
||||
case download
|
||||
}
|
||||
}
|
||||
|
||||
public struct ArtifactsListParams: Codable, Sendable {
|
||||
public let sessionkey: String?
|
||||
public let runid: String?
|
||||
public let taskid: String?
|
||||
|
||||
public init(
|
||||
sessionkey: String?,
|
||||
runid: String?,
|
||||
taskid: String?)
|
||||
{
|
||||
self.sessionkey = sessionkey
|
||||
self.runid = runid
|
||||
self.taskid = taskid
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case sessionkey = "sessionKey"
|
||||
case runid = "runId"
|
||||
case taskid = "taskId"
|
||||
}
|
||||
}
|
||||
|
||||
public struct ArtifactsListResult: Codable, Sendable {
|
||||
public let artifacts: [ArtifactSummary]
|
||||
|
||||
public init(
|
||||
artifacts: [ArtifactSummary])
|
||||
{
|
||||
self.artifacts = artifacts
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case artifacts
|
||||
}
|
||||
}
|
||||
|
||||
public struct ArtifactsGetParams: Codable, Sendable {
|
||||
public let sessionkey: String?
|
||||
public let runid: String?
|
||||
public let taskid: String?
|
||||
public let artifactid: String
|
||||
|
||||
public init(
|
||||
sessionkey: String?,
|
||||
runid: String?,
|
||||
taskid: String?,
|
||||
artifactid: String)
|
||||
{
|
||||
self.sessionkey = sessionkey
|
||||
self.runid = runid
|
||||
self.taskid = taskid
|
||||
self.artifactid = artifactid
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case sessionkey = "sessionKey"
|
||||
case runid = "runId"
|
||||
case taskid = "taskId"
|
||||
case artifactid = "artifactId"
|
||||
}
|
||||
}
|
||||
|
||||
public struct ArtifactsGetResult: Codable, Sendable {
|
||||
public let artifact: ArtifactSummary
|
||||
|
||||
public init(
|
||||
artifact: ArtifactSummary)
|
||||
{
|
||||
self.artifact = artifact
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case artifact
|
||||
}
|
||||
}
|
||||
|
||||
public struct ArtifactsDownloadParams: Codable, Sendable {
|
||||
public let sessionkey: String?
|
||||
public let runid: String?
|
||||
public let taskid: String?
|
||||
public let artifactid: String
|
||||
|
||||
public init(
|
||||
sessionkey: String?,
|
||||
runid: String?,
|
||||
taskid: String?,
|
||||
artifactid: String)
|
||||
{
|
||||
self.sessionkey = sessionkey
|
||||
self.runid = runid
|
||||
self.taskid = taskid
|
||||
self.artifactid = artifactid
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case sessionkey = "sessionKey"
|
||||
case runid = "runId"
|
||||
case taskid = "taskId"
|
||||
case artifactid = "artifactId"
|
||||
}
|
||||
}
|
||||
|
||||
public struct ArtifactsDownloadResult: Codable, Sendable {
|
||||
public let artifact: ArtifactSummary
|
||||
public let encoding: String?
|
||||
public let data: String?
|
||||
public let url: String?
|
||||
|
||||
public init(
|
||||
artifact: ArtifactSummary,
|
||||
encoding: String?,
|
||||
data: String?,
|
||||
url: String?)
|
||||
{
|
||||
self.artifact = artifact
|
||||
self.encoding = encoding
|
||||
self.data = data
|
||||
self.url = url
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case artifact
|
||||
case encoding
|
||||
case data
|
||||
case url
|
||||
}
|
||||
}
|
||||
|
||||
public struct AgentsListParams: Codable, Sendable {}
|
||||
|
||||
public struct AgentsListResult: Codable, Sendable {
|
||||
@@ -3644,6 +3830,100 @@ public struct ToolsEffectiveResult: Codable, Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
public struct ToolsInvokeParams: Codable, Sendable {
|
||||
public let name: String
|
||||
public let args: [String: AnyCodable]?
|
||||
public let sessionkey: String?
|
||||
public let agentid: String?
|
||||
public let confirm: Bool?
|
||||
public let idempotencykey: String?
|
||||
|
||||
public init(
|
||||
name: String,
|
||||
args: [String: AnyCodable]?,
|
||||
sessionkey: String?,
|
||||
agentid: String?,
|
||||
confirm: Bool?,
|
||||
idempotencykey: String?)
|
||||
{
|
||||
self.name = name
|
||||
self.args = args
|
||||
self.sessionkey = sessionkey
|
||||
self.agentid = agentid
|
||||
self.confirm = confirm
|
||||
self.idempotencykey = idempotencykey
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case name
|
||||
case args
|
||||
case sessionkey = "sessionKey"
|
||||
case agentid = "agentId"
|
||||
case confirm
|
||||
case idempotencykey = "idempotencyKey"
|
||||
}
|
||||
}
|
||||
|
||||
public struct ToolsInvokeError: Codable, Sendable {
|
||||
public let code: String
|
||||
public let message: String
|
||||
public let details: AnyCodable?
|
||||
|
||||
public init(
|
||||
code: String,
|
||||
message: String,
|
||||
details: AnyCodable?)
|
||||
{
|
||||
self.code = code
|
||||
self.message = message
|
||||
self.details = details
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case code
|
||||
case message
|
||||
case details
|
||||
}
|
||||
}
|
||||
|
||||
public struct ToolsInvokeResult: Codable, Sendable {
|
||||
public let ok: Bool
|
||||
public let toolname: String
|
||||
public let output: AnyCodable?
|
||||
public let requiresapproval: Bool?
|
||||
public let approvalid: String?
|
||||
public let source: AnyCodable?
|
||||
public let error: [String: AnyCodable]?
|
||||
|
||||
public init(
|
||||
ok: Bool,
|
||||
toolname: String,
|
||||
output: AnyCodable?,
|
||||
requiresapproval: Bool?,
|
||||
approvalid: String?,
|
||||
source: AnyCodable?,
|
||||
error: [String: AnyCodable]?)
|
||||
{
|
||||
self.ok = ok
|
||||
self.toolname = toolname
|
||||
self.output = output
|
||||
self.requiresapproval = requiresapproval
|
||||
self.approvalid = approvalid
|
||||
self.source = source
|
||||
self.error = error
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case ok
|
||||
case toolname = "toolName"
|
||||
case output
|
||||
case requiresapproval = "requiresApproval"
|
||||
case approvalid = "approvalId"
|
||||
case source
|
||||
case error
|
||||
}
|
||||
}
|
||||
|
||||
public struct SkillsBinsParams: Codable, Sendable {}
|
||||
|
||||
public struct SkillsBinsResult: Codable, Sendable {
|
||||
|
||||
@@ -89,4 +89,41 @@ import Testing
|
||||
|
||||
#expect(mapped == nil)
|
||||
}
|
||||
|
||||
@Test func tlsPinMismatchMapsToActionableProblem() {
|
||||
let error = GatewayTLSValidationError(
|
||||
failure: GatewayTLSValidationFailure(
|
||||
kind: .pinMismatch,
|
||||
host: "gateway.example.ts.net",
|
||||
storeKey: "gateway.example.ts.net:443",
|
||||
expectedFingerprint: "old",
|
||||
observedFingerprint: "new",
|
||||
systemTrustOk: true),
|
||||
context: "connect to gateway")
|
||||
|
||||
let problem = GatewayConnectionProblemMapper.map(error: error)
|
||||
|
||||
#expect(problem?.kind == .tlsPinMismatch)
|
||||
#expect(problem?.retryable == false)
|
||||
#expect(problem?.pauseReconnect == true)
|
||||
#expect(problem?.actionLabel == "Review certificate")
|
||||
}
|
||||
|
||||
@Test func untrustedTLSCertificatePausesReconnect() {
|
||||
let error = GatewayTLSValidationError(
|
||||
failure: GatewayTLSValidationFailure(
|
||||
kind: .untrustedCertificate,
|
||||
host: "gateway.example.com",
|
||||
storeKey: "gateway.example.com:443",
|
||||
expectedFingerprint: nil,
|
||||
observedFingerprint: nil,
|
||||
systemTrustOk: false),
|
||||
context: "connect to gateway")
|
||||
|
||||
let problem = GatewayConnectionProblemMapper.map(error: error)
|
||||
|
||||
#expect(problem?.kind == .tlsCertificateUntrusted)
|
||||
#expect(problem?.retryable == false)
|
||||
#expect(problem?.pauseReconnect == true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
c3bcb3a3da46bbbe15a7798869911cab109df950ee51c79fd86c96bb809dfdf1 config-baseline.json
|
||||
8f573caa7f4cf01ae9d4805d3d14e1ba6772f651f6da182baaf2b469592749a4 config-baseline.core.json
|
||||
92712871defa92eeda8161b516db85574681f2b70678b940508a808b987aeae2 config-baseline.channel.json
|
||||
aca3215b7382af82b5060d73c631a7f82661c6e99193fa5eb1c5b4b499fb657b config-baseline.plugin.json
|
||||
1deb67d0a40456e77cb67685f6ae2f14a8ddc2c4be488d4b1a1f1127598982dd config-baseline.json
|
||||
ac7537ed5b5a2d9e7fa50977aa99f5e0babfbe1a93c7c14b93a184b36bb4f539 config-baseline.core.json
|
||||
f3326cd9490169afefe93625f63699266b75db93855ed439c9692e3c286a990c config-baseline.channel.json
|
||||
7731a0b93cb335b56fac4c807447ba659fea51ea7a6cd844dc0ef5616669ee75 config-baseline.plugin.json
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
dd840b7c222ca003aa5336aabff8a126e3e254474941ddab93165e0e44944ffa plugin-sdk-api-baseline.json
|
||||
443878722940029e4ae5220f3c23ffc321559b73848f6a7a3f4cab98c076924e plugin-sdk-api-baseline.jsonl
|
||||
37787172adf7a55a32097599b4bf5729fc7138c8743c6f4c9d58fc8d01df72a1 plugin-sdk-api-baseline.json
|
||||
0ec4957528477832085c638a5f7f691c878ba199f3e81f330f162c27cfd9ebf4 plugin-sdk-api-baseline.jsonl
|
||||
|
||||
@@ -85,6 +85,9 @@ the target agent signs in separately and creates its own local profile.
|
||||
- Runtime-only credentials owned by external CLIs are discovered only when the
|
||||
provider, runtime, or auth profile is in scope for the current operation, or
|
||||
when a stored local profile for that external source already exists.
|
||||
- Auth-store callers should choose an explicit external-CLI discovery mode:
|
||||
`none` for persisted/plugin auth only, `existing` for refreshing already
|
||||
stored external CLI profiles, or `scoped` for a concrete provider/profile set.
|
||||
- Read-only/status paths pass `allowKeychainPrompt: false`; they use file-backed
|
||||
external CLI credentials only and do not read or reuse macOS Keychain results.
|
||||
|
||||
|
||||
@@ -96,13 +96,13 @@ Not every agent run creates a task. Heartbeat turns and normal interactive chat
|
||||
| Subagent orchestration | `subagent` | Spawning a subagent via `sessions_spawn` | `done_only` |
|
||||
| Cron jobs (all types) | `cron` | Every cron execution (main-session and isolated) | `silent` |
|
||||
| CLI operations | `cli` | `openclaw agent` commands that run through the gateway | `silent` |
|
||||
| Agent media jobs | `cli` | Session-backed `video_generate` runs | `silent` |
|
||||
| Agent media jobs | `cli` | Session-backed `music_generate`/`video_generate` runs | `silent` |
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Notify defaults for cron and media">
|
||||
Main-session cron tasks use `silent` notify policy by default — they create records for tracking but do not generate notifications. Isolated cron tasks also default to `silent` but are more visible because they run in their own session.
|
||||
|
||||
Session-backed `video_generate` runs also use `silent` notify policy. They still create task records, but completion is handed back to the original agent session as an internal wake so the agent can write the follow-up message and attach the finished video itself. If you opt into `tools.media.asyncCompletion.directSend`, async `music_generate` and `video_generate` completions try direct channel delivery first before falling back to the requester-session wake path.
|
||||
Session-backed `music_generate` and `video_generate` runs also use `silent` notify policy. They still create task records, but completion is handed back to the original agent session as an internal wake so the agent can write the follow-up message and attach the finished media itself. If you opt into `tools.media.asyncCompletion.directSend`, async `video_generate` completions can try direct channel delivery first; async `music_generate` completions stay on the requester-session wake path.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Concurrent video_generate guardrail">
|
||||
@@ -247,6 +247,7 @@ openclaw tasks notify <lookup> state_changes
|
||||
Reconciliation is runtime-aware:
|
||||
|
||||
- ACP/subagent tasks check their backing child session.
|
||||
- Subagent tasks whose child session has a restart-recovery tombstone are marked lost instead of being treated as recoverable backing sessions.
|
||||
- Cron tasks check whether the cron runtime still owns the job, then recover terminal status from persisted cron run logs/job state before falling back to `lost`. Only the Gateway process is authoritative for the in-memory cron active-job set; offline CLI audit uses durable history but does not mark a cron task lost solely because that local Set is empty.
|
||||
- Chat-backed CLI tasks check the owning live run context, not just the chat session row.
|
||||
|
||||
|
||||
@@ -581,6 +581,7 @@ Full configuration: [Configuration](/gateway/configuration)
|
||||
- `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.replyContextApiFallback`: When an inbound reply lands without `replyToBody`/`replyToSender` and the in-memory reply-context cache misses, fetch the original message from the BlueBubbles HTTP API as a best-effort fallback (default: `false`). Useful for multi-instance deployments sharing one BlueBubbles account, after process restarts, or after long-lived TTL/LRU cache eviction. The fetch is SSRF-guarded by the same policy as every other BlueBubbles client request, never throws, and populates the cache so subsequent replies amortize. Per-account override: `channels.bluebubbles.accounts.<accountId>.replyContextApiFallback`. A channel-level setting propagates to accounts that omit the flag.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Actions and accounts">
|
||||
|
||||
@@ -1048,6 +1048,8 @@ Auto-join example:
|
||||
],
|
||||
daveEncryption: true,
|
||||
decryptionFailureTolerance: 24,
|
||||
connectTimeoutMs: 30000,
|
||||
reconnectGraceMs: 15000,
|
||||
tts: {
|
||||
provider: "openai",
|
||||
openai: { voice: "onyx" },
|
||||
@@ -1063,11 +1065,14 @@ Notes:
|
||||
- `voice.tts` overrides `messages.tts` for voice playback only.
|
||||
- `voice.model` overrides the LLM used for Discord voice channel responses only. Leave it unset to inherit the routed agent model.
|
||||
- STT uses `tools.media.audio`; `voice.model` does not affect transcription.
|
||||
- Per-channel Discord `systemPrompt` overrides apply to voice transcript turns for that voice channel.
|
||||
- Voice transcript turns derive owner status from Discord `allowFrom` (or `dm.allowFrom`); non-owner speakers cannot access owner-only tools (for example `gateway` and `cron`).
|
||||
- Voice is enabled by default; set `channels.discord.voice.enabled=false` to disable voice runtime and the `GuildVoiceStates` gateway intent.
|
||||
- `channels.discord.intents.voiceStates` can explicitly override voice-state intent subscription. Leave it unset for the intent to follow `voice.enabled`.
|
||||
- Discord voice is opt-in for text-only configs; set `channels.discord.voice.enabled=true` (or keep an existing `channels.discord.voice` block) to enable `/vc` commands, the voice runtime, and the `GuildVoiceStates` gateway intent.
|
||||
- `channels.discord.intents.voiceStates` can explicitly override voice-state intent subscription. Leave it unset for the intent to follow effective voice enablement.
|
||||
- `voice.daveEncryption` and `voice.decryptionFailureTolerance` pass through to `@discordjs/voice` join options.
|
||||
- `@discordjs/voice` defaults are `daveEncryption=true` and `decryptionFailureTolerance=24` if unset.
|
||||
- `voice.connectTimeoutMs` controls the initial `@discordjs/voice` Ready wait for `/vc join` and auto-join attempts. Default: `30000`.
|
||||
- `voice.reconnectGraceMs` controls how long OpenClaw waits for a disconnected voice session to begin reconnecting before destroying it. Default: `15000`.
|
||||
- OpenClaw also watches receive decrypt failures and auto-recovers by leaving/rejoining the voice channel after repeated failures in a short window.
|
||||
- If receive logs repeatedly show `DecryptionFailed(UnencryptedWhenPassthroughDisabled)` after updating, collect a dependency report and logs. The bundled `@discordjs/voice` line includes the upstream padding fix from discord.js PR #11449, which closed discord.js issue #11419.
|
||||
|
||||
@@ -1075,7 +1080,7 @@ Voice channel pipeline:
|
||||
|
||||
- Discord PCM capture is converted to a WAV temp file.
|
||||
- `tools.media.audio` handles STT, for example `openai/gpt-4o-mini-transcribe`.
|
||||
- The transcript is sent through normal Discord ingress and routing.
|
||||
- The transcript is sent through Discord ingress and routing while the response LLM runs with a voice-output policy that hides the agent `tts` tool and asks for returned text, because Discord voice owns final TTS playback.
|
||||
- `voice.model`, when set, overrides only the response LLM for this voice-channel turn.
|
||||
- `voice.tts` is merged over `messages.tts`; the resulting audio is played in the joined channel.
|
||||
|
||||
|
||||
@@ -43,7 +43,11 @@ otherwise -> reply
|
||||
For group/channel rooms, OpenClaw defaults to `messages.groupChat.visibleReplies: "message_tool"`.
|
||||
That means the agent still processes the turn and can update memory/session state, but its normal final answer is not automatically posted back into the room. To speak visibly, the agent uses `message(action=send)`.
|
||||
|
||||
For direct chats and any other source turn, use `messages.visibleReplies: "message_tool"` to apply the same tool-only visible-reply behavior globally. `messages.groupChat.visibleReplies` remains the more specific override for group/channel rooms.
|
||||
If the message tool is unavailable under the active tool policy, OpenClaw falls
|
||||
back to automatic visible replies instead of silently suppressing the response.
|
||||
`openclaw doctor` warns about this mismatch.
|
||||
|
||||
For direct chats and any other source turn, use `messages.visibleReplies: "message_tool"` to apply the same tool-only visible-reply behavior globally. Harnesses can also choose this as their unset default; the Codex harness does this for Codex-mode direct chats. `messages.groupChat.visibleReplies` remains the more specific override for group/channel rooms.
|
||||
|
||||
This replaces the old pattern of forcing the model to answer `NO_REPLY` for most lurk-mode turns. In tool-only mode, doing nothing visible simply means not calling the message tool.
|
||||
|
||||
@@ -61,6 +65,9 @@ To restore legacy automatic final replies for group/channel rooms:
|
||||
}
|
||||
```
|
||||
|
||||
The gateway hot-reloads `messages` config after the file is saved. Restart only
|
||||
when file watching or config reload is disabled in the deployment.
|
||||
|
||||
To require visible output to go through the message tool for every source chat:
|
||||
|
||||
```json5
|
||||
@@ -254,6 +261,7 @@ Control how group/room messages are handled per channel:
|
||||
<Accordion title="Per-channel notes">
|
||||
- `groupPolicy` is separate from mention-gating (which requires @mentions).
|
||||
- WhatsApp/Telegram/Signal/iMessage/Microsoft Teams/Zalo: use `groupAllowFrom` (fallback: explicit `allowFrom`).
|
||||
- Signal: `groupAllowFrom` can match either the inbound Signal group id or the sender phone/UUID.
|
||||
- DM pairing approvals (`*-allowFrom` store entries) apply to DM access only; group sender authorization stays explicit to group allowlists.
|
||||
- Discord: allowlist uses `channels.discord.guilds.<id>.channels`.
|
||||
- Slack: allowlist uses `channels.slack.channels`.
|
||||
@@ -328,6 +336,7 @@ Replying to a bot message counts as an implicit mention when the channel support
|
||||
- Surfaces that provide explicit mentions still pass; patterns are a fallback.
|
||||
- Per-agent override: `agents.list[].groupChat.mentionPatterns` (useful when multiple agents share a group).
|
||||
- Mention gating is only enforced when mention detection is possible (native mentions or `mentionPatterns` are configured).
|
||||
- Allowlisting a group or sender does not disable mention gating; set that group's `requireMention` to `false` when all messages should trigger.
|
||||
- Group chat prompt context carries the resolved silent-reply instruction every turn; workspace files should not duplicate `NO_REPLY` mechanics.
|
||||
- Groups where silent replies are allowed treat clean empty or reasoning-only model turns as silent, equivalent to `NO_REPLY`. Direct chats do the same only when direct silent replies are explicitly allowed; otherwise empty replies remain failed agent turns.
|
||||
- Discord defaults live in `channels.discord.guilds."*"` (overridable per guild/channel).
|
||||
|
||||
@@ -93,7 +93,9 @@ Native slash commands are opt-in. When enabled, OpenClaw registers `oc_*` slash
|
||||
- If `callbackUrl` is omitted, OpenClaw derives one from gateway host/port + `callbackPath`.
|
||||
- For multi-account setups, `commands` can be set at the top level or under `channels.mattermost.accounts.<id>.commands` (account values override top-level fields).
|
||||
- Command callbacks are validated with the per-command tokens returned by Mattermost when OpenClaw registers `oc_*` commands.
|
||||
- Slash callbacks fail closed when registration failed, startup was partial, or the callback token does not match one of the registered commands.
|
||||
- OpenClaw refreshes current Mattermost command registration before accepting each callback so stale tokens from deleted or regenerated slash commands stop being accepted without a gateway restart.
|
||||
- Callback validation fails closed if the Mattermost API cannot confirm the command is still current; failed validations are cached briefly, concurrent lookups are coalesced, and fresh lookup starts are rate-limited per command to bound replay pressure.
|
||||
- Slash callbacks fail closed when registration failed, startup was partial, or the callback token does not match the resolved command's registered token (a token valid for one command cannot reach upstream validation for a different command).
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Reachability requirement">
|
||||
|
||||
@@ -14,7 +14,9 @@ read_when:
|
||||
- Slack-class target grammar:
|
||||
- `dm:<user>`
|
||||
- `channel:<room>`
|
||||
- `group:<room>`
|
||||
- `thread:<room>/<thread>`
|
||||
- Shared `channel:` and `group:` conversations are surfaced to agents as group/channel room turns, so they exercise the same visible-reply and message-tool routing policy used by Discord, Slack, Telegram, and similar transports.
|
||||
- HTTP-backed synthetic bus for inbound message injection, outbound transcript capture, thread creation, reactions, edits, deletes, and search/read actions.
|
||||
- Host-side self-check runner that writes a Markdown report to `.artifacts/qa-e2e/`.
|
||||
|
||||
|
||||
@@ -239,12 +239,15 @@ Built-in commands intercepted before the AI queue:
|
||||
| `/bot-ping` | Latency test |
|
||||
| `/bot-version` | Show the OpenClaw framework version |
|
||||
| `/bot-help` | List all commands |
|
||||
| `/bot-me` | Show the sender's QQ user ID (openid) for `allowFrom`/`groupAllowFrom` setup |
|
||||
| `/bot-upgrade` | Show the QQBot upgrade guide link |
|
||||
| `/bot-logs` | Export recent gateway logs as a file |
|
||||
| `/bot-approve` | Approve a pending QQ Bot action (for example, confirming a C2C or group upload) through the native flow. |
|
||||
|
||||
Append `?` to any command for usage help (for example `/bot-upgrade ?`).
|
||||
|
||||
Admin commands (`/bot-me`, `/bot-upgrade`, `/bot-logs`, `/bot-clear-storage`, `/bot-streaming`, `/bot-approve`) are direct-message-only and require the sender's openid in an explicit non-wildcard `allowFrom` list. A wildcard `allowFrom: ["*"]` permits chat but does not grant admin command access. Group messages match against `groupAllowFrom` first and fall back to `allowFrom`. Running an admin command in a group returns a hint rather than silently dropping.
|
||||
|
||||
## Engine architecture
|
||||
|
||||
QQ Bot ships as a self-contained engine inside the plugin:
|
||||
|
||||
@@ -194,9 +194,10 @@ DMs:
|
||||
Groups:
|
||||
|
||||
- `channels.signal.groupPolicy = open | allowlist | disabled`.
|
||||
- `channels.signal.groupAllowFrom` controls who can trigger in groups when `allowlist` is set.
|
||||
- `channels.signal.groupAllowFrom` controls which groups or senders can trigger group replies when `allowlist` is set; entries can be Signal group IDs (raw, `group:<id>`, or `signal:group:<id>`), sender phone numbers, `uuid:<id>` values, or `*`.
|
||||
- `channels.signal.groups["<group-id>" | "*"]` can override group behavior with `requireMention`, `tools`, and `toolsBySender`.
|
||||
- Use `channels.signal.accounts.<id>.groups` for per-account overrides in multi-account setups.
|
||||
- Allowlisting a Signal group through `groupAllowFrom` does not disable mention gating by itself. A specifically configured `channels.signal.groups["<group-id>"]` entry processes every group message unless `requireMention=true` is set.
|
||||
- Runtime note: if `channels.signal` is completely missing, runtime falls back to `groupPolicy="allowlist"` for group checks (even if `channels.defaults.groupPolicy` is set).
|
||||
|
||||
## How it works (behavior)
|
||||
@@ -314,7 +315,7 @@ Provider options:
|
||||
- `channels.signal.dmPolicy`: `pairing | allowlist | open | disabled` (default: pairing).
|
||||
- `channels.signal.allowFrom`: DM allowlist (E.164 or `uuid:<id>`). `open` requires `"*"`. Signal has no usernames; use phone/UUID ids.
|
||||
- `channels.signal.groupPolicy`: `open | allowlist | disabled` (default: allowlist).
|
||||
- `channels.signal.groupAllowFrom`: group sender allowlist.
|
||||
- `channels.signal.groupAllowFrom`: group allowlist; accepts Signal group IDs (raw, `group:<id>`, or `signal:group:<id>`), sender E.164 numbers, or `uuid:<id>` values.
|
||||
- `channels.signal.groups`: per-group overrides keyed by Signal group id (or `"*"`). Supported fields: `requireMention`, `tools`, `toolsBySender`.
|
||||
- `channels.signal.accounts.<id>.groups`: per-account version of `channels.signal.groups` for multi-account setups.
|
||||
- `channels.signal.historyLimit`: max group messages to include as context (0 disables).
|
||||
|
||||
@@ -169,6 +169,7 @@ Base manifest (Socket Mode default):
|
||||
"features": {
|
||||
"bot_user": { "display_name": "OpenClaw", "always_online": true },
|
||||
"app_home": {
|
||||
"home_tab_enabled": true,
|
||||
"messages_tab_enabled": true,
|
||||
"messages_tab_read_only_enabled": false
|
||||
},
|
||||
@@ -212,6 +213,7 @@ Base manifest (Socket Mode default):
|
||||
"socket_mode_enabled": true,
|
||||
"event_subscriptions": {
|
||||
"bot_events": [
|
||||
"app_home_opened",
|
||||
"app_mention",
|
||||
"channel_rename",
|
||||
"member_joined_channel",
|
||||
@@ -264,6 +266,8 @@ For **HTTP Request URLs mode**, replace `settings` with the HTTP variant and add
|
||||
|
||||
Surface different features that extend the above defaults.
|
||||
|
||||
The default manifest enables the Slack App Home **Home** tab and subscribes to `app_home_opened`. When a workspace member opens the Home tab, OpenClaw publishes a safe default Home view with `views.publish`; no conversation payload or private configuration is included. The **Messages** tab remains enabled for Slack DMs.
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Optional native slash commands">
|
||||
|
||||
@@ -582,6 +586,8 @@ Current Slack message actions include `send`, `upload-file`, `download-file`, `r
|
||||
- `toolsBySender` key format: `id:`, `e164:`, `username:`, `name:`, or `"*"` wildcard
|
||||
(legacy unprefixed keys still map to `id:` only)
|
||||
|
||||
`allowBots` is conservative for channels and private channels: bot-authored room messages are accepted only when the sending bot is explicitly listed in that room's `users` allowlist, or when at least one explicit Slack owner ID from `channels.slack.allowFrom` is currently a room member. Wildcards and display-name owner entries do not satisfy owner presence. Owner presence uses Slack `conversations.members`; make sure the app has the matching read scope for the room type (`channels:read` for public channels, `groups:read` for private channels). If the member lookup fails, OpenClaw drops the bot-authored room message.
|
||||
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
|
||||
@@ -310,8 +310,6 @@ curl "https://api.telegram.org/bot<bot_token>/getUpdates"
|
||||
|
||||
Preview streaming is separate from block streaming. When block streaming is explicitly enabled for Telegram, OpenClaw skips the preview stream to avoid double-streaming.
|
||||
|
||||
If native draft transport is unavailable/rejected, OpenClaw automatically falls back to `sendMessage` + `editMessageText`.
|
||||
|
||||
Telegram-only reasoning stream:
|
||||
|
||||
- `/reasoning stream` sends reasoning to the live preview while generating
|
||||
@@ -726,7 +724,7 @@ curl "https://api.telegram.org/bot<bot_token>/getUpdates"
|
||||
- `channels.telegram.textChunkLimit` default is 4000.
|
||||
- `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.timeoutSeconds` overrides Telegram API client timeout (if unset, grammY default applies). Long-polling bot clients clamp configured values below the 45-second `getUpdates` request guard so idle polls are not aborted before the 30-second poll window completes.
|
||||
- `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.
|
||||
@@ -866,6 +864,7 @@ 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 Telegram sockets recycle on a short fixed cadence, check for a low `channels.telegram.timeoutSeconds`; long-polling bot clients clamp configured values below the `getUpdates` request guard, but older releases could abort every poll when this was set below the long-poll timeout.
|
||||
- If logs include `Polling stall detected`, OpenClaw restarts polling and rebuilds the Telegram transport after 120 seconds without completed long-poll liveness by default.
|
||||
- `openclaw channels status --probe` and `openclaw doctor` warn when a running polling account has not completed `getUpdates` after startup grace, when a running webhook account has not completed `setWebhook` after startup grace, or when the last successful polling transport activity is stale.
|
||||
- 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`.
|
||||
|
||||
35
docs/ci.md
35
docs/ci.md
@@ -112,7 +112,13 @@ pnpm test:perf:groups:compare .artifacts/test-perf/baseline-before.json .artifac
|
||||
|
||||
`Full Release Validation` is the manual umbrella workflow for "run everything before release." It accepts a branch, tag, or full commit SHA, dispatches the manual `CI` workflow with that target, dispatches `Plugin Prerelease` for release-only plugin/package/static/Docker proof, and dispatches `OpenClaw Release Checks` for install smoke, package acceptance, Docker release-path suites, live/E2E, OpenWebUI, QA Lab parity, Matrix, and Telegram lanes. It can also run the post-publish `NPM Telegram Beta E2E` workflow when a published package spec is provided.
|
||||
|
||||
`release_profile` controls live/provider breadth passed into release checks:
|
||||
See [Full release validation](/reference/full-release-validation) for the
|
||||
stage matrix, exact workflow job names, profile differences, artifacts, and
|
||||
focused rerun handles.
|
||||
|
||||
`release_profile` controls live/provider breadth passed into release checks. The
|
||||
manual release workflows default to `stable`; use `full` only when you
|
||||
intentionally want the broad advisory provider/media matrix.
|
||||
|
||||
- `minimum` keeps the fastest OpenAI/core release-critical lanes.
|
||||
- `stable` adds the stable provider/backend set.
|
||||
@@ -120,10 +126,16 @@ pnpm test:perf:groups:compare .artifacts/test-perf/baseline-before.json .artifac
|
||||
|
||||
The umbrella records the dispatched child run ids, and the final `Verify full validation` job re-checks current child run conclusions and appends slowest-job tables for each child run. If a child workflow is rerun and turns green, rerun only the parent verifier job to refresh the umbrella result and timing summary.
|
||||
|
||||
For recovery, both `Full Release Validation` and `OpenClaw Release Checks` accept `rerun_group`. Use `all` for a release candidate, `ci` for only the normal full CI child, `release-checks` for every release child, or a narrower group: `install-smoke`, `cross-os`, `live-e2e`, `package`, `qa`, `qa-parity`, `qa-live`, or `npm-telegram` on the umbrella. This keeps a failed release box rerun bounded after a focused fix.
|
||||
For recovery, both `Full Release Validation` and `OpenClaw Release Checks` accept `rerun_group`. Use `all` for a release candidate, `ci` for only the normal full CI child, `plugin-prerelease` for only the plugin prerelease child, `release-checks` for every release child, or a narrower group: `install-smoke`, `cross-os`, `live-e2e`, `package`, `qa`, `qa-parity`, `qa-live`, or `npm-telegram` on the umbrella. This keeps a failed release box rerun bounded after a focused fix.
|
||||
|
||||
`OpenClaw Release Checks` uses the trusted workflow ref to resolve the selected ref once into a `release-package-under-test` tarball, then passes that artifact to both the live/E2E release-path Docker workflow and the package acceptance shard. That keeps the package bytes consistent across release boxes and avoids repacking the same candidate in multiple child jobs.
|
||||
|
||||
Duplicate `Full Release Validation` runs for `ref=main` and `rerun_group=all`
|
||||
supersede the older umbrella. The parent monitor cancels any child workflow it
|
||||
has already dispatched when the parent is cancelled, so newer main validation
|
||||
does not sit behind a stale two-hour release-check run. Release branch/tag
|
||||
validation and focused rerun groups keep `cancel-in-progress: false`.
|
||||
|
||||
## Live and E2E shards
|
||||
|
||||
The release live/E2E child keeps broad native `pnpm test:live` coverage, but it runs it as named shards through `scripts/test-live-shard.mjs` instead of one serial job:
|
||||
@@ -144,7 +156,7 @@ That keeps the same file coverage while making slow live provider failures easie
|
||||
|
||||
The native live media shards run in `ghcr.io/openclaw/openclaw-live-media-runner:ubuntu-24.04`, built by the `Live Media Runner Image` workflow. That image preinstalls `ffmpeg` and `ffprobe`; media jobs only verify the binaries before setup. Keep Docker-backed live suites on normal Blacksmith runners — container jobs are the wrong place to launch nested Docker tests.
|
||||
|
||||
Docker-backed live model/backend shards use a separate shared `ghcr.io/openclaw/openclaw-live-test:<sha>` image per selected commit. The live release workflow builds and pushes that image once, then the Docker live model, gateway, CLI backend, ACP bind, and Codex harness shards run with `OPENCLAW_SKIP_DOCKER_BUILD=1`. If those shards rebuild the full source Docker target independently, the release run is misconfigured and will waste wall clock on duplicate image builds.
|
||||
Docker-backed live model/backend shards use a separate shared `ghcr.io/openclaw/openclaw-live-test:<sha>` image per selected commit. The live release workflow builds and pushes that image once, then the Docker live model, provider-sharded gateway, CLI backend, ACP bind, and Codex harness shards run with `OPENCLAW_SKIP_DOCKER_BUILD=1`. Gateway Docker shards carry explicit script-level `timeout` caps below the workflow job timeout so a stuck container or cleanup path fails fast instead of consuming the whole release-check budget. If those shards rebuild the full source Docker target independently, the release run is misconfigured and will waste wall clock on duplicate image builds.
|
||||
|
||||
## Package Acceptance
|
||||
|
||||
@@ -169,14 +181,14 @@ Keep `workflow_ref` and `package_ref` separate. `workflow_ref` is the trusted wo
|
||||
### Suite profiles
|
||||
|
||||
- `smoke` — `npm-onboard-channel-agent`, `gateway-network`, `config-reload`
|
||||
- `package` — `npm-onboard-channel-agent`, `doctor-switch`, `update-channel-switch`, `bundled-channel-deps-compat`, `plugins-offline`, `plugin-update`
|
||||
- `package` — `npm-onboard-channel-agent`, `doctor-switch`, `update-channel-switch`, `upgrade-survivor`, `published-upgrade-survivor`, `bundled-channel-deps-compat`, `plugins-offline`, `plugin-update`
|
||||
- `product` — `package` plus `mcp-channels`, `cron-mcp-cleanup`, `openai-web-search-minimal`, `openwebui`
|
||||
- `full` — full Docker release-path chunks with OpenWebUI
|
||||
- `custom` — exact `docker_lanes`; required when `suite_profile=custom`
|
||||
|
||||
The `package` profile uses offline plugin coverage so published-package validation is not gated on live ClawHub availability. The optional Telegram lane reuses the `package-under-test` artifact in `NPM Telegram Beta E2E`, with the published npm spec path kept for standalone dispatches.
|
||||
|
||||
Release checks call Package Acceptance with `source=ref`, `package_ref=<release-ref>`, `workflow_ref=<release workflow ref>`, `suite_profile=custom`, `docker_lanes='bundled-channel-deps-compat plugins-offline'`, and `telegram_mode=mock-openai`. Release-path Docker chunks cover the overlapping package/update/plugin lanes; Package Acceptance keeps the artifact-native bundled-channel compat, offline plugin, and Telegram proof against the same resolved package tarball. Cross-OS release checks still cover OS-specific onboarding, installer, and platform behavior; package/update product validation should start with Package Acceptance. The Windows packaged and installer fresh lanes also verify that an installed package can import a browser-control override from a raw absolute Windows path. The OpenAI cross-OS agent-turn smoke defaults to `OPENCLAW_CROSS_OS_OPENAI_MODEL` when set, otherwise `openai/gpt-5.4-mini`, so the install and gateway proof stays fast and deterministic.
|
||||
Release checks call Package Acceptance with `source=ref`, `package_ref=<release-ref>`, `workflow_ref=<release workflow ref>`, `suite_profile=custom`, `docker_lanes='bundled-channel-deps-compat plugins-offline'`, and `telegram_mode=mock-openai`. Release-path Docker chunks cover the overlapping package/update/plugin lanes; Package Acceptance keeps the artifact-native bundled-channel compat, offline plugin, and Telegram proof against the same resolved package tarball. Cross-OS release checks still cover OS-specific onboarding, installer, and platform behavior; package/update product validation should start with Package Acceptance. The `published-upgrade-survivor` Docker lane validates one published package baseline per run. In Package Acceptance, the resolved `package-under-test` tarball is always the candidate and `published_upgrade_survivor_baseline` selects the fallback published baseline, defaulting to `openclaw@latest`; failed-lane rerun commands preserve that baseline. Set `published_upgrade_survivor_baselines=release-history` to expand the lane across a deduped history matrix: the latest six stable releases, `2026.4.23`, and the latest stable release before `2026-03-15`. Set `published_upgrade_survivor_scenarios=reported-issues` to expand the same baselines across issue-shaped fixtures for Feishu config/runtime-deps, preserved bootstrap/persona files, tilde log paths, and stale versioned runtime-deps roots. Local aggregate runs can pass exact package specs with `OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPECS`, keep a single lane with `OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC` such as `openclaw@2026.4.15`, or set `OPENCLAW_UPGRADE_SURVIVOR_SCENARIOS` for the scenario matrix. The published lane configures the baseline with a baked `openclaw config set` command recipe, records recipe steps in `summary.json`, and probes `/healthz`, `/readyz`, plus RPC status after Gateway start. The Windows packaged and installer fresh lanes also verify that an installed package can import a browser-control override from a raw absolute Windows path. The OpenAI cross-OS agent-turn smoke defaults to `OPENCLAW_CROSS_OS_OPENAI_MODEL` when set, otherwise `openai/gpt-5.4-mini`, so the install and gateway proof stays fast and deterministic.
|
||||
|
||||
### Legacy compatibility windows
|
||||
|
||||
@@ -295,7 +307,7 @@ The scheduled live/E2E workflow runs the full release-path Docker suite daily.
|
||||
|
||||
## Plugin Prerelease
|
||||
|
||||
`Plugin Prerelease` is more expensive product/package coverage, so it is a separate workflow dispatched by `Full Release Validation` or by an explicit operator. Normal pull requests, `main` pushes, and standalone manual CI dispatches keep that suite off. It balances bundled plugin tests across eight extension workers; those extension shard jobs run up to two plugin config groups at a time with one Vitest worker per group and a larger Node heap so import-heavy plugin batches do not create extra CI jobs.
|
||||
`Plugin Prerelease` is more expensive product/package coverage, so it is a separate workflow dispatched by `Full Release Validation` or by an explicit operator. Normal pull requests, `main` pushes, and standalone manual CI dispatches keep that suite off. It balances bundled plugin tests across eight extension workers; those extension shard jobs run up to two plugin config groups at a time with one Vitest worker per group and a larger Node heap so import-heavy plugin batches do not create extra CI jobs. The release-only Docker prerelease path batches targeted Docker lanes in small groups to avoid reserving dozens of runners for one-to-three-minute jobs.
|
||||
|
||||
## QA Lab
|
||||
|
||||
@@ -407,6 +419,17 @@ The sanity check fails fast when required root files such as `pnpm-lock.yaml` di
|
||||
|
||||
`pnpm testbox:run` also terminates a local Blacksmith CLI invocation that stays in the sync phase for more than five minutes without post-sync output. Set `OPENCLAW_TESTBOX_SYNC_TIMEOUT_MS=0` to disable that guard, or use a larger millisecond value for unusually large local diffs.
|
||||
|
||||
Crabbox is the repo-owned second remote-box path for Linux proof when Blacksmith is unavailable or when owned cloud capacity is preferable. Warm a box, hydrate it through the project workflow, then run commands through the Crabbox CLI:
|
||||
|
||||
```bash
|
||||
pnpm crabbox:warmup -- --idle-timeout 90m
|
||||
pnpm crabbox:hydrate -- --id <cbx_id>
|
||||
pnpm crabbox:run -- --id <cbx_id> --shell "OPENCLAW_TESTBOX=1 pnpm check:changed"
|
||||
pnpm crabbox:stop -- <cbx_id>
|
||||
```
|
||||
|
||||
`.crabbox.yaml` owns provider, sync, and GitHub Actions hydration defaults. It excludes local `.git` so the hydrated Actions checkout keeps its own remote Git metadata instead of syncing maintainer-local remotes and object stores, and it excludes local runtime/build artifacts that should never be transferred. `.github/workflows/crabbox-hydrate.yml` owns checkout, Node/pnpm setup, `origin/main` fetch, and the non-secret environment handoff that later `crabbox run --id <cbx_id>` commands source.
|
||||
|
||||
## Related
|
||||
|
||||
- [Install overview](/install)
|
||||
|
||||
@@ -51,6 +51,8 @@ openclaw channels remove --channel telegram --delete
|
||||
`openclaw channels add --help` shows per-channel flags (token, private key, app token, signal-cli paths, etc).
|
||||
</Tip>
|
||||
|
||||
`channels remove` only operates on installed/configured channel plugins. Use `channels add` first for installable catalog channels.
|
||||
|
||||
Common non-interactive add surfaces include:
|
||||
|
||||
- bot-token channels: `--token`, `--bot-token`, `--app-token`, `--token-file`
|
||||
@@ -132,6 +134,7 @@ Notes:
|
||||
- Use `--kind user|group|auto` to force the target type.
|
||||
- Resolution prefers active matches when multiple entries share the same name.
|
||||
- `channels resolve` is read-only. If a selected account is configured via SecretRef but that credential is unavailable in the current command path, the command returns degraded unresolved results with notes instead of aborting the entire run.
|
||||
- `channels resolve` does not install channel plugins. Use `channels add --channel <name>` before resolving names for an installable catalog channel.
|
||||
|
||||
## Related
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@ Available sections:
|
||||
Notes:
|
||||
|
||||
- Choosing where the Gateway runs always updates `gateway.mode`. You can select "Continue" without other sections if that is all you need.
|
||||
- After local config writes, configure materializes newly required bundled plugin runtime dependencies. This is a narrow package-manager repair step, not a full `openclaw doctor` run. Remote gateway config does not install local plugin dependencies.
|
||||
- Channel-oriented services (Slack/Discord/Matrix/Microsoft Teams) prompt for channel/room allowlists during setup. You can enter names or IDs; the wizard resolves names to IDs when possible.
|
||||
- If you run the daemon install step, token auth requires a token, and `gateway.auth.token` is SecretRef-managed, configure validates the SecretRef but does not persist resolved plaintext token values into supervisor service environment metadata.
|
||||
- If token auth requires a token and the configured token SecretRef is unresolved, configure blocks daemon install with actionable remediation guidance.
|
||||
|
||||
@@ -226,6 +226,8 @@ openclaw cron edit <job-id> --session current
|
||||
openclaw cron edit <job-id> --session "session:daily-brief"
|
||||
```
|
||||
|
||||
`openclaw cron add` warns when `--agent` is omitted on agent-turn jobs and falls back to the default agent (`main`). Pass `--agent <id>` at create time to pin a specific agent.
|
||||
|
||||
Delivery tweaks:
|
||||
|
||||
```bash
|
||||
|
||||
@@ -29,10 +29,10 @@ openclaw doctor --generate-gateway-token
|
||||
|
||||
- `--no-workspace-suggestions`: disable workspace memory/search suggestions
|
||||
- `--yes`: accept defaults without prompting
|
||||
- `--repair`: apply recommended repairs without prompting
|
||||
- `--repair`: apply recommended non-service repairs without prompting; gateway service installs and rewrites still require interactive confirmation or explicit gateway commands
|
||||
- `--fix`: alias for `--repair`
|
||||
- `--force`: apply aggressive repairs, including overwriting custom service config when needed
|
||||
- `--non-interactive`: run without prompts; safe migrations only
|
||||
- `--non-interactive`: run without prompts; safe migrations and non-service repairs only
|
||||
- `--generate-gateway-token`: generate and configure a gateway token
|
||||
- `--deep`: scan system services for extra gateway installs
|
||||
|
||||
@@ -41,6 +41,7 @@ Notes:
|
||||
- Interactive prompts (like keychain/OAuth fixes) only run when stdin is a TTY and `--non-interactive` is **not** set. Headless runs (cron, Telegram, no terminal) will skip prompts.
|
||||
- Performance: non-interactive `doctor` runs skip eager plugin loading so headless health checks stay fast. Interactive sessions still fully load plugins when a check needs their contribution.
|
||||
- `--fix` (alias for `--repair`) writes a backup to `~/.openclaw/openclaw.json.bak` and drops unknown config keys, listing each removal.
|
||||
- `doctor --fix --non-interactive` reports missing or stale gateway service definitions but does not install or rewrite them outside update repair mode. Run `openclaw gateway install` for a missing service, or `openclaw gateway install --force` when you intentionally want to replace the launcher.
|
||||
- State integrity checks now detect orphan transcript files in the sessions directory. Archiving them as `.deleted.<timestamp>` requires an interactive confirmation; `--fix`, `--yes`, and headless runs leave them in place.
|
||||
- Doctor also scans `~/.openclaw/cron/jobs.json` (or `cron.store`) for legacy cron job shapes and can rewrite them in place before the scheduler has to auto-normalize them at runtime.
|
||||
- Doctor repairs missing bundled plugin runtime dependencies without writing into packaged global installs. For root-owned npm installs or hardened systemd units, set `OPENCLAW_PLUGIN_STAGE_DIR` to a writable directory such as `/var/lib/openclaw/plugin-runtime-deps`; it can also be a path-list such as `/opt/openclaw/plugin-runtime-deps:/var/lib/openclaw/plugin-runtime-deps`, where earlier roots are read-only lookup layers and the final root is the repair target.
|
||||
@@ -52,6 +53,7 @@ Notes:
|
||||
- Repeat `doctor --fix` runs no longer report/apply Talk normalization when the only difference is object key order.
|
||||
- Doctor includes a memory-search readiness check and can recommend `openclaw configure --section model` when embedding credentials are missing.
|
||||
- Doctor warns when no command owner is configured. The command owner is the human operator account allowed to run owner-only commands and approve dangerous actions. DM pairing only lets someone talk to the bot; if you approved a sender before first-owner bootstrap existed, set `commands.ownerAllowFrom` explicitly.
|
||||
- Doctor warns when Codex-mode agents are configured and personal Codex CLI assets exist in the operator's Codex home. Local Codex app-server launches use isolated per-agent homes, so use `openclaw migrate codex --dry-run` to inventory assets that should be promoted deliberately.
|
||||
- If sandbox mode is enabled but Docker is unavailable, doctor reports a high-signal warning with remediation (`install Docker` or `openclaw config set agents.defaults.sandbox.mode off`).
|
||||
- If `gateway.auth.token`/`gateway.auth.password` are SecretRef-managed and unavailable in the current command path, doctor reports a read-only warning and does not write plaintext fallback credentials.
|
||||
- If channel SecretRef inspection fails in a fix path, doctor continues and reports a warning instead of exiting early.
|
||||
|
||||
@@ -146,7 +146,7 @@ When you set `--url`, the CLI does not fall back to config or environment creden
|
||||
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. Local or authenticated detailed readiness responses include an `eventLoop` diagnostic block with event-loop delay, event-loop utilization, CPU core ratio, and a `degraded` flag.
|
||||
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 plugin runtime dependencies, sidecars, channels, or configured hooks are still settling. Local or authenticated detailed readiness responses include an `eventLoop` diagnostic block with event-loop delay, event-loop utilization, CPU core ratio, and a `degraded` flag.
|
||||
|
||||
### `gateway usage-cost`
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ title: "Migrate"
|
||||
|
||||
# `openclaw migrate`
|
||||
|
||||
Import state from another agent system through a plugin-owned migration provider. Bundled providers cover [Claude](/install/migrating-claude) and [Hermes](/install/migrating-hermes); third-party plugins can register additional providers.
|
||||
Import state from another agent system through a plugin-owned migration provider. Bundled providers cover Codex CLI state, [Claude](/install/migrating-claude), and [Hermes](/install/migrating-hermes); third-party plugins can register additional providers.
|
||||
|
||||
<Tip>
|
||||
For user-facing walkthroughs, see [Migrating from Claude](/install/migrating-claude) and [Migrating from Hermes](/install/migrating-hermes). The [migration hub](/install/migrating) lists all paths.
|
||||
@@ -19,8 +19,12 @@ For user-facing walkthroughs, see [Migrating from Claude](/install/migrating-cla
|
||||
```bash
|
||||
openclaw migrate list
|
||||
openclaw migrate claude --dry-run
|
||||
openclaw migrate codex --dry-run
|
||||
openclaw migrate codex --skill gog-vault77-google-workspace
|
||||
openclaw migrate hermes --dry-run
|
||||
openclaw migrate hermes
|
||||
openclaw migrate apply codex --yes --skill gog-vault77-google-workspace
|
||||
openclaw migrate apply codex --yes
|
||||
openclaw migrate apply claude --yes
|
||||
openclaw migrate apply hermes --yes
|
||||
openclaw migrate apply hermes --include-secrets --yes
|
||||
@@ -47,6 +51,9 @@ openclaw onboard --import-from hermes --import-source ~/.hermes
|
||||
<ParamField path="--yes" type="boolean">
|
||||
Skip the confirmation prompt. Required in non-interactive mode.
|
||||
</ParamField>
|
||||
<ParamField path="--skill <name>" type="string">
|
||||
Select one skill copy item by skill name or item id. Repeat the flag to migrate multiple skills. When omitted, interactive Codex migrations show a checkbox selector and non-interactive migrations keep all planned skills.
|
||||
</ParamField>
|
||||
<ParamField path="--no-backup" type="boolean">
|
||||
Skip the pre-apply backup. Requires `--force` when local OpenClaw state exists.
|
||||
</ParamField>
|
||||
@@ -99,6 +106,43 @@ For a user-facing walkthrough, see [Migrating from Claude](/install/migrating-cl
|
||||
|
||||
Claude hooks, permissions, environment defaults, local memory, path-scoped rules, subagents, caches, plans, and project history are preserved in the migration report or reported as manual-review items. OpenClaw does not execute hooks, copy broad allowlists, or import OAuth/Desktop credential state automatically.
|
||||
|
||||
## Codex provider
|
||||
|
||||
The bundled Codex provider detects Codex CLI state at `~/.codex` by default, or
|
||||
at `CODEX_HOME` when that environment variable is set. Use `--from <path>` to
|
||||
inventory a specific Codex home.
|
||||
|
||||
Use this provider when moving to the OpenClaw Codex harness and you want to
|
||||
promote useful personal Codex CLI assets deliberately. Local Codex app-server
|
||||
launches use per-agent `CODEX_HOME` and `HOME` directories, so they do not read
|
||||
your personal Codex CLI state by default.
|
||||
|
||||
Running `openclaw migrate codex` in an interactive terminal previews the full
|
||||
plan, then opens a checkbox selector for skill copy items before the final
|
||||
apply confirmation. All skills start selected; uncheck any skill you do not want
|
||||
copied into this agent. For scripted or exact runs, pass `--skill <name>` once
|
||||
per skill, for example:
|
||||
|
||||
```bash
|
||||
openclaw migrate codex --dry-run --skill gog-vault77-google-workspace
|
||||
openclaw migrate apply codex --yes --skill gog-vault77-google-workspace
|
||||
```
|
||||
|
||||
### What Codex imports
|
||||
|
||||
- Codex CLI skill directories under `$CODEX_HOME/skills`, excluding Codex's
|
||||
`.system` cache.
|
||||
- Personal AgentSkills under `$HOME/.agents/skills`, copied into the current
|
||||
OpenClaw agent workspace when you want per-agent ownership.
|
||||
|
||||
### Manual-review Codex state
|
||||
|
||||
Codex native plugins, `config.toml`, and native `hooks/hooks.json` are not
|
||||
activated automatically. Plugins may expose MCP servers, apps, hooks, or other
|
||||
executable behavior, so the provider reports them for review instead of loading
|
||||
them into OpenClaw. Config and hook files are copied into the migration report
|
||||
for manual review.
|
||||
|
||||
## Hermes provider
|
||||
|
||||
The bundled Hermes provider detects state at `~/.hermes` by default. Use `--from <path>` when Hermes lives elsewhere.
|
||||
|
||||
@@ -56,6 +56,11 @@ Notes:
|
||||
rows from plugin manifests or bundled provider catalog metadata even when you
|
||||
have not authenticated with that provider yet. Those rows still show as
|
||||
unavailable until matching auth is configured.
|
||||
- `models list` keeps the control plane responsive while provider catalog
|
||||
discovery is slow. The default and configured views fall back to configured or
|
||||
synthetic model rows after a short wait and let discovery finish in the
|
||||
background. Use `--all` when you need the exact full discovered catalog and
|
||||
are willing to wait for provider discovery.
|
||||
- Broad `models list --all` merges manifest catalog rows over registry rows
|
||||
without loading provider runtime supplement hooks. Provider-filtered manifest
|
||||
fast paths use only providers marked `static`; providers marked `refreshable`
|
||||
|
||||
@@ -119,6 +119,8 @@ Gateway token options in non-interactive mode:
|
||||
- With `--install-daemon`, if token mode requires a token and the configured token SecretRef is unresolved, onboarding fails closed with remediation guidance.
|
||||
- With `--install-daemon`, if both `gateway.auth.token` and `gateway.auth.password` are configured and `gateway.auth.mode` is unset, onboarding blocks install until mode is set explicitly.
|
||||
- Local onboarding writes `gateway.mode="local"` into the config. If a later config file is missing `gateway.mode`, treat that as config damage or an incomplete manual edit, not as a valid local-mode shortcut.
|
||||
- Local onboarding materializes newly required bundled plugin runtime dependencies after writing config, before workspace/bootstrap, daemon install, or health checks continue. This is a narrow package-manager repair step, not a full `openclaw doctor` run.
|
||||
- Remote onboarding only writes connection info for the remote Gateway and does not install local bundled plugin dependencies.
|
||||
- `--allow-unconfigured` is a separate gateway runtime escape hatch. It does not mean onboarding may omit `gateway.mode`.
|
||||
|
||||
Example:
|
||||
|
||||
@@ -33,6 +33,7 @@ openclaw plugins list --verbose
|
||||
openclaw plugins list --json
|
||||
openclaw plugins install <path-or-spec>
|
||||
openclaw plugins inspect <id>
|
||||
openclaw plugins inspect <id> --runtime
|
||||
openclaw plugins inspect <id> --json
|
||||
openclaw plugins inspect --all
|
||||
openclaw plugins info <id>
|
||||
@@ -70,6 +71,8 @@ Native OpenClaw plugins must ship `openclaw.plugin.json` with an inline JSON Sch
|
||||
openclaw plugins install <package> # ClawHub first, then npm
|
||||
openclaw plugins install clawhub:<package> # ClawHub only
|
||||
openclaw plugins install npm:<package> # npm only
|
||||
openclaw plugins install git:github.com/<owner>/<repo> # git repo
|
||||
openclaw plugins install git:github.com/<owner>/<repo>@<ref>
|
||||
openclaw plugins install <package> --force # overwrite existing install
|
||||
openclaw plugins install <package> --pin # pin version
|
||||
openclaw plugins install <package> --dangerously-force-unsafe-install
|
||||
@@ -107,7 +110,7 @@ current OpenClaw or a local checkout until a newer npm package is published.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="--pin scope">
|
||||
`--pin` applies to npm installs only. It is not supported with `--marketplace`, because marketplace installs persist marketplace source metadata instead of an npm spec.
|
||||
`--pin` applies to npm installs only. It is not supported with `git:` installs; use an explicit git ref such as `git:github.com/acme/plugin@v1.2.3` when you want a pinned source. It is not supported with `--marketplace`, because marketplace installs persist marketplace source metadata instead of an npm spec.
|
||||
</Accordion>
|
||||
<Accordion title="--dangerously-force-unsafe-install">
|
||||
`--dangerously-force-unsafe-install` is a break-glass option for false positives in the built-in dangerous-code scanner. It allows the install to continue even when the built-in scanner reports `critical` findings, but it does **not** bypass plugin `before_install` hook policy blocks and does **not** bypass scan failures.
|
||||
@@ -128,6 +131,14 @@ current OpenClaw or a local checkout until a newer npm package is published.
|
||||
|
||||
If a bare install spec matches a bundled plugin id (for example `diffs`), OpenClaw installs the bundled plugin directly. To install an npm package with the same name, use an explicit scoped spec (for example `@scope/diffs`).
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Git repositories">
|
||||
Use `git:<repo>` to install directly from a git repository. Supported forms include `git:github.com/owner/repo`, `git:owner/repo`, full `https://`, `ssh://`, `git://`, `file://`, and `git@host:owner/repo.git` clone URLs. Add `@<ref>` or `#<ref>` to check out a branch, tag, or commit before install.
|
||||
|
||||
Git installs clone into a temporary directory, check out the requested ref when present, then use the normal plugin directory installer. That means manifest validation, dangerous-code scanning, runtime dependency staging, and install records behave like local-path installs. Recorded git installs include the source URL/ref plus the resolved commit so `openclaw plugins update` can re-resolve the source later.
|
||||
|
||||
After installing from git, use `openclaw plugins inspect <id> --runtime --json` to verify runtime registrations such as gateway methods and CLI commands. If the plugin registered a CLI root with `api.registerCli`, execute that command directly through the OpenClaw root CLI, for example `openclaw demo-plugin ping`.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Archives">
|
||||
Supported archives: `.zip`, `.tgz`, `.tar.gz`, `.tar`. Native OpenClaw plugin archives must contain a valid `openclaw.plugin.json` at the extracted plugin root; archives that only contain `package.json` are rejected before OpenClaw writes install records.
|
||||
@@ -234,7 +245,7 @@ directory remains inert so normal packaged installs still use compiled dist.
|
||||
|
||||
For runtime hook debugging:
|
||||
|
||||
- `openclaw plugins inspect <id> --json` shows registered hooks and diagnostics from a module-loaded inspection pass.
|
||||
- `openclaw plugins inspect <id> --runtime --json` shows registered hooks and diagnostics from a module-loaded inspection pass. Runtime inspection never downloads missing bundled runtime dependencies; use `openclaw plugins deps --repair` when repair is needed.
|
||||
- `openclaw gateway status --deep --require-rpc` confirms the reachable Gateway, service/process hints, config path, and RPC health.
|
||||
- Non-bundled conversation hooks (`llm_input`, `llm_output`, `before_agent_finalize`, `agent_end`) require `plugins.entries.<id>.hooks.allowConversationAccess=true`.
|
||||
|
||||
@@ -269,6 +280,8 @@ openclaw plugins deps --json
|
||||
|
||||
Use `--repair` when a packaged install reports missing bundled runtime dependencies during Gateway startup or `plugins doctor`. Repair installs only missing enabled bundled-plugin deps with lifecycle scripts disabled. Use `--prune` to remove stale unknown external runtime-dependency roots left behind by older packaged layouts.
|
||||
|
||||
For the full plan, staging, and repair lifecycle, see [Plugin dependency resolution](/plugins/dependency-resolution).
|
||||
|
||||
### Uninstall
|
||||
|
||||
```bash
|
||||
@@ -319,10 +332,13 @@ Updates apply to tracked plugin installs in the managed plugin index and tracked
|
||||
|
||||
```bash
|
||||
openclaw plugins inspect <id>
|
||||
openclaw plugins inspect <id> --runtime
|
||||
openclaw plugins inspect <id> --json
|
||||
```
|
||||
|
||||
Deep introspection for a single plugin. Shows identity, load status, source, registered capabilities, hooks, tools, commands, services, gateway methods, HTTP routes, policy flags, diagnostics, install metadata, bundle capabilities, and any detected MCP or LSP server support.
|
||||
Inspect shows identity, load status, source, manifest capabilities, policy flags, diagnostics, install metadata, bundle capabilities, and any detected MCP or LSP server support without importing plugin runtime by default. Add `--runtime` to load the plugin module and include registered hooks, tools, commands, services, gateway methods, and HTTP routes. Runtime inspection fails with a repair hint when bundled runtime dependencies are missing; use `openclaw plugins deps --repair` to repair them explicitly.
|
||||
|
||||
Plugin-owned CLI commands are installed as root `openclaw` command groups. After `inspect --runtime` shows a command under `cliCommands`, run it as `openclaw <command> ...`; for example a plugin that registers `demo-git` can be verified with `openclaw demo-git ping`.
|
||||
|
||||
Each plugin is classified by what it actually registers at runtime:
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
summary: "CLI reference for `openclaw proxy`, the local debug proxy and capture inspector"
|
||||
summary: "CLI reference for `openclaw proxy`, including operator-managed proxy validation and the local debug proxy capture inspector"
|
||||
read_when:
|
||||
- You need to validate operator-managed proxy routing before deployment
|
||||
- You need to capture OpenClaw transport traffic locally for debugging
|
||||
- You want to inspect debug proxy sessions, blobs, or built-in query presets
|
||||
title: "Proxy"
|
||||
@@ -8,18 +9,21 @@ title: "Proxy"
|
||||
|
||||
# `openclaw proxy`
|
||||
|
||||
Run the local explicit debug proxy and inspect captured traffic.
|
||||
Validate operator-managed proxy routing, or run the local explicit debug proxy
|
||||
and inspect captured traffic.
|
||||
|
||||
This is a debugging command for transport-level investigation. It can start a
|
||||
local proxy, run a child command with capture enabled, list capture sessions,
|
||||
query common traffic patterns, read captured blobs, and purge local capture
|
||||
data.
|
||||
Use `validate` to preflight an operator-managed forward proxy before enabling
|
||||
OpenClaw proxy routing. The other commands are debugging tools for
|
||||
transport-level investigation: they can start a local proxy, run a child command
|
||||
with capture enabled, list capture sessions, query common traffic patterns, read
|
||||
captured blobs, and purge local capture data.
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
openclaw proxy start [--host <host>] [--port <port>]
|
||||
openclaw proxy run [--host <host>] [--port <port>] -- <cmd...>
|
||||
openclaw proxy validate [--json] [--proxy-url <url>] [--allowed-url <url>] [--denied-url <url>] [--timeout-ms <ms>]
|
||||
openclaw proxy coverage
|
||||
openclaw proxy sessions [--limit <count>]
|
||||
openclaw proxy query --preset <name> [--session <id>]
|
||||
@@ -27,6 +31,28 @@ openclaw proxy blob --id <blobId>
|
||||
openclaw proxy purge
|
||||
```
|
||||
|
||||
## Validate
|
||||
|
||||
`openclaw proxy validate` checks the effective operator-managed proxy URL from
|
||||
`--proxy-url`, config, or `OPENCLAW_PROXY_URL`. It reports a config problem when
|
||||
no proxy is enabled and configured; use `--proxy-url` for a one-off preflight
|
||||
before changing config. By default it verifies that a public destination succeeds
|
||||
through the proxy and that the proxy cannot reach a temporary loopback canary.
|
||||
Custom denied destinations are fail-closed: HTTP responses and ambiguous
|
||||
transport failures both fail unless you can verify a deployment-specific denial
|
||||
signal separately.
|
||||
|
||||
Options:
|
||||
|
||||
- `--json`: print machine-readable JSON.
|
||||
- `--proxy-url <url>`: validate this proxy URL instead of config or env.
|
||||
- `--allowed-url <url>`: add a destination expected to succeed through the proxy. Repeat to check multiple destinations.
|
||||
- `--denied-url <url>`: add a destination expected to be blocked by the proxy. Repeat to check multiple destinations.
|
||||
- `--timeout-ms <ms>`: per-request timeout in milliseconds.
|
||||
|
||||
See [Network Proxy](/security/network-proxy) for deployment guidance and denial
|
||||
semantics.
|
||||
|
||||
## Query presets
|
||||
|
||||
`openclaw proxy query --preset <name>` accepts:
|
||||
@@ -42,9 +68,11 @@ openclaw proxy purge
|
||||
|
||||
- `start` defaults to `127.0.0.1` unless `--host` is set.
|
||||
- `run` starts a local debug proxy and then runs the command after `--`.
|
||||
- `validate` exits with code 1 when proxy config or destination checks fail.
|
||||
- Captures are local debugging data; use `openclaw proxy purge` when finished.
|
||||
|
||||
## Related
|
||||
|
||||
- [CLI reference](/cli)
|
||||
- [Network Proxy](/security/network-proxy)
|
||||
- [Trusted proxy auth](/gateway/trusted-proxy-auth)
|
||||
|
||||
@@ -25,6 +25,8 @@ openclaw security audit --fix
|
||||
openclaw security audit --json
|
||||
```
|
||||
|
||||
Plain `security audit` stays on the cold config/filesystem/read-only path. It does not discover plugin runtime security collectors by default, so routine audits do not load every installed plugin runtime. Use `--deep` to include best-effort live Gateway probes and plugin-owned security audit collectors; explicit internal callers may also opt into those plugin-owned collectors when they already have an appropriate runtime scope.
|
||||
|
||||
The audit warns when multiple DM senders share the main session and recommends **secure DM mode**: `session.dmScope="per-channel-peer"` (or `per-account-channel-peer` for multi-account channels) for shared inboxes.
|
||||
This is for cooperative/shared inbox hardening. A single Gateway shared by mutually untrusted/adversarial operators is not a recommended setup; split trust boundaries with separate gateways (or separate OS users/hosts).
|
||||
It also emits `security.trust_model.multi_user_heuristic` when config suggests likely shared-user ingress (for example open DM/group policy, configured group targets, or wildcard sender rules), and reminds you that OpenClaw is a personal-assistant trust model by default.
|
||||
|
||||
@@ -82,7 +82,11 @@ install method aligned:
|
||||
- `beta` → prefers npm dist-tag `beta`, but falls back to `latest` when beta is
|
||||
missing or older than the current stable release.
|
||||
|
||||
The Gateway core auto-updater (when enabled via config) reuses this same update path.
|
||||
The Gateway core auto-updater (when enabled via config) launches the CLI update path
|
||||
outside the live Gateway request handler. Control-plane `update.run` package-manager
|
||||
updates force a non-deferred, no-cooldown update restart after the package swap,
|
||||
because the old Gateway process may still have in-memory chunks that point at
|
||||
files removed by the new package.
|
||||
|
||||
For package-manager installs, `openclaw update` resolves the target package
|
||||
version before invoking the package manager. npm global installs use a staged
|
||||
@@ -151,7 +155,7 @@ If an exact pinned npm plugin update resolves to an artifact whose integrity dif
|
||||
<Note>
|
||||
Post-update plugin sync failures fail the update result and stop restart follow-up work. Fix the plugin install or update error, then rerun `openclaw update`.
|
||||
|
||||
When the updated Gateway starts, enabled bundled plugin runtime dependencies are staged before plugin activation. Update-triggered restarts drain any active runtime-dependency staging before closing the Gateway, so service-manager restarts do not interrupt an in-flight npm install.
|
||||
When the updated Gateway starts, enabled bundled plugin runtime dependencies are staged before plugin activation. Package-manager `update.run` restarts bypass the normal idle deferral and restart cooldown after the package tree has been swapped, so the old process cannot keep lazy-loading removed chunks. Service-manager restarts still drain runtime-dependency staging before closing the Gateway.
|
||||
|
||||
If pnpm bootstrap still fails, the updater stops early with a package-manager-specific error instead of trying `npm run build` inside the checkout.
|
||||
</Note>
|
||||
|
||||
@@ -10,6 +10,11 @@ title: "Voicecall"
|
||||
|
||||
`voicecall` is a plugin-provided command. It only appears if the voice-call plugin is installed and enabled.
|
||||
|
||||
When the Gateway is running, operational commands (`call`, `start`,
|
||||
`continue`, `speak`, `dtmf`, `end`, and `status`) are sent to that Gateway's
|
||||
voice-call runtime. If no Gateway is reachable, they fall back to a standalone
|
||||
CLI runtime.
|
||||
|
||||
Primary doc:
|
||||
|
||||
- Voice-call plugin: [Voice Call](/plugins/voice-call)
|
||||
@@ -19,6 +24,7 @@ Primary doc:
|
||||
```bash
|
||||
openclaw voicecall setup
|
||||
openclaw voicecall smoke
|
||||
openclaw voicecall status --json
|
||||
openclaw voicecall status --call-id <id>
|
||||
openclaw voicecall call --to "+15555550123" --message "Hello" --mode notify
|
||||
openclaw voicecall continue --call-id <id> --message "Any questions?"
|
||||
@@ -33,6 +39,9 @@ scripts:
|
||||
openclaw voicecall setup --json
|
||||
```
|
||||
|
||||
`status` prints active calls as JSON by default. Pass `--call-id <id>` to inspect
|
||||
one call.
|
||||
|
||||
For external providers (`twilio`, `telnyx`, `plivo`), setup must resolve a public
|
||||
webhook URL from `publicUrl`, a tunnel, or Tailscale exposure. A loopback/private
|
||||
serve fallback is rejected because carriers cannot reach it.
|
||||
|
||||
@@ -32,6 +32,7 @@ wired end-to-end.
|
||||
- resolves model + auth profile and builds the pi session
|
||||
- subscribes to pi events and streams assistant/tool deltas
|
||||
- enforces timeout -> aborts run if exceeded
|
||||
- for Codex app-server turns, aborts an accepted turn that stops producing app-server progress before a terminal event
|
||||
- returns payloads + usage metadata
|
||||
4. `subscribeEmbeddedPiSession` bridges pi-agent-core events to OpenClaw `agent` stream:
|
||||
- tool events => `stream: "tool"`
|
||||
|
||||
@@ -108,6 +108,7 @@ These live under `~/.openclaw/` and should NOT be committed to the workspace rep
|
||||
|
||||
- `~/.openclaw/openclaw.json` (config)
|
||||
- `~/.openclaw/agents/<agentId>/agent/auth-profiles.json` (model auth profiles: OAuth + API keys)
|
||||
- `~/.openclaw/agents/<agentId>/agent/codex-home/` (per-agent Codex runtime account, config, skills, plugins, and native thread state)
|
||||
- `~/.openclaw/credentials/` (channel/provider state plus legacy OAuth import data)
|
||||
- `~/.openclaw/agents/<agentId>/sessions/` (session transcripts + metadata)
|
||||
- `~/.openclaw/skills/` (managed skills)
|
||||
|
||||
@@ -60,11 +60,15 @@ When it finds a high-confidence candidate, OpenClaw stores a commitment with:
|
||||
- the original channel and delivery target
|
||||
- a due window
|
||||
- a short suggested check-in
|
||||
- enough source context for heartbeat to decide whether to send it
|
||||
- non-instructional metadata for heartbeat to decide whether to send it
|
||||
|
||||
Delivery happens through heartbeat. When a commitment becomes due, heartbeat
|
||||
adds the commitment to the heartbeat turn for the same agent and channel scope.
|
||||
The model can send one natural check-in or reply `HEARTBEAT_OK` to dismiss it.
|
||||
If heartbeat is configured with `target: "none"`, due commitments remain
|
||||
internal and do not send external check-ins. Commitment delivery prompts do not
|
||||
replay the original conversation text, and due commitment heartbeat turns run
|
||||
without OpenClaw tools.
|
||||
|
||||
OpenClaw never delivers an inferred commitment immediately after writing it.
|
||||
The due time is clamped to at least one heartbeat interval after the commitment
|
||||
|
||||
@@ -197,6 +197,17 @@ Required members:
|
||||
<ParamField path="systemPromptAddition" type="string">
|
||||
Prepended to the system prompt.
|
||||
</ParamField>
|
||||
<ParamField path="promptAuthority" type='"assembled" | "preassembly_may_overflow"'>
|
||||
Controls which token estimate the runner uses for preemptive overflow
|
||||
prechecks. Defaults to `"assembled"`, which means only the assembled
|
||||
prompt's estimate is checked — appropriate for engines that return a
|
||||
windowed, self-contained context. Set to `"preassembly_may_overflow"` only
|
||||
when your assembled view can hide overflow risk in the underlying
|
||||
transcript; the runner then takes the maximum of the assembled estimate
|
||||
and the pre-assembly (unwindowed) session-history estimate when deciding
|
||||
whether to preemptively compact. Either way, the messages you return are
|
||||
still what the model sees — `promptAuthority` only affects the precheck.
|
||||
</ParamField>
|
||||
|
||||
`compact` returns a `CompactResult`. When compaction rotates the active
|
||||
transcript, `result.sessionId` and `result.sessionFile` identify the successor
|
||||
|
||||
@@ -33,8 +33,9 @@ For multi-endpoint setups, `provider` can also be a custom
|
||||
`models.providers.<id>` entry, such as `ollama-5080`, when that provider sets
|
||||
`api: "ollama"` or another embedding adapter owner.
|
||||
|
||||
For local embeddings with no API key, install the optional `node-llama-cpp`
|
||||
runtime package next to OpenClaw and use `provider: "local"`.
|
||||
For local embeddings with no API key, set `provider: "local"`. Packaged
|
||||
installs retain the native `node-llama-cpp` runtime in OpenClaw's managed plugin
|
||||
runtime-deps tree; run `openclaw doctor --fix` if that tree needs repair.
|
||||
|
||||
Some OpenAI-compatible embedding endpoints require asymmetric labels such as
|
||||
`input_type: "query"` for searches and `input_type: "document"` or `"passage"`
|
||||
|
||||
@@ -93,8 +93,11 @@ OpenClaw keeps that boundary explicit:
|
||||
|
||||
OpenClaw separates the **prompt body** from the **command body**:
|
||||
|
||||
- `Body`: prompt text sent to the agent. This may include channel envelopes and
|
||||
optional history wrappers.
|
||||
- `BodyForAgent`: primary model-facing text for the current message. Channel
|
||||
plugins should keep this focused on the sender's current prompt-bearing text.
|
||||
- `Body`: legacy prompt fallback. This may include channel envelopes and
|
||||
optional history wrappers, but current channels should not rely on it as the
|
||||
primary model input when `BodyForAgent` is available.
|
||||
- `CommandBody`: raw user text for directive/command parsing.
|
||||
- `RawBody`: legacy alias for `CommandBody` (kept for compatibility).
|
||||
|
||||
@@ -114,6 +117,8 @@ already in the session transcript.
|
||||
Directive stripping only applies to the **current message** section so history
|
||||
remains intact. Channels that wrap history should set `CommandBody` (or
|
||||
`RawBody`) to the original message text and keep `Body` as the combined prompt.
|
||||
Structured history, reply, forwarded, and channel metadata are rendered as
|
||||
user-role untrusted context blocks during prompt assembly.
|
||||
History buffers are configurable via `messages.groupChat.historyLimit` (global
|
||||
default) and per-channel overrides like `channels.slack.historyLimit` or
|
||||
`channels.telegram.accounts.<id>.historyLimit` (set `0` to disable).
|
||||
|
||||
@@ -25,30 +25,33 @@ resources.
|
||||
|
||||
`@openclaw/sdk` ships with:
|
||||
|
||||
| Surface | Status | What it does |
|
||||
| ------------------------- | ------- | ---------------------------------------------------------------------------- |
|
||||
| `OpenClaw` | Ready | Main client entry point. Owns transport, connection, requests, and events. |
|
||||
| `GatewayClientTransport` | Ready | WebSocket transport backed by the Gateway client. |
|
||||
| `oc.agents` | Ready | Lists, creates, updates, deletes, and gets agent handles. |
|
||||
| `Agent.run()` | Ready | Starts a Gateway `agent` run and returns a `Run`. |
|
||||
| `oc.runs` | Ready | Creates, gets, waits for, cancels, and streams runs. |
|
||||
| `Run.events()` | Ready | Streams normalized per-run events with replay for fast runs. |
|
||||
| `Run.wait()` | Ready | Calls `agent.wait` and returns a stable `RunResult`. |
|
||||
| `Run.cancel()` | Ready | Calls `sessions.abort` by run id, with session key when available. |
|
||||
| `oc.sessions` | Ready | Creates, resolves, sends to, patches, compacts, and gets session handles. |
|
||||
| `Session.send()` | Ready | Calls `sessions.send` and returns a `Run`. |
|
||||
| `oc.models` | Ready | Calls `models.list` and the current `models.authStatus` status RPC. |
|
||||
| `oc.tools` | Partial | Lists tool catalog and effective tools; direct tool invocation is not wired. |
|
||||
| `oc.approvals` | Ready | Lists and resolves exec approvals through Gateway approval RPCs. |
|
||||
| `oc.rawEvents()` | Ready | Exposes raw Gateway events for advanced consumers. |
|
||||
| `normalizeGatewayEvent()` | Ready | Converts raw Gateway events into the stable SDK event shape. |
|
||||
| Surface | Status | What it does |
|
||||
| ------------------------- | ------ | -------------------------------------------------------------------------- |
|
||||
| `OpenClaw` | Ready | Main client entry point. Owns transport, connection, requests, and events. |
|
||||
| `GatewayClientTransport` | Ready | WebSocket transport backed by the Gateway client. |
|
||||
| `oc.agents` | Ready | Lists, creates, updates, deletes, and gets agent handles. |
|
||||
| `Agent.run()` | Ready | Starts a Gateway `agent` run and returns a `Run`. |
|
||||
| `oc.runs` | Ready | Creates, gets, waits for, cancels, and streams runs. |
|
||||
| `Run.events()` | Ready | Streams normalized per-run events with replay for fast runs. |
|
||||
| `Run.wait()` | Ready | Calls `agent.wait` and returns a stable `RunResult`. |
|
||||
| `Run.cancel()` | Ready | Calls `sessions.abort` by run id, with session key when available. |
|
||||
| `oc.sessions` | Ready | Creates, resolves, sends to, patches, compacts, and gets session handles. |
|
||||
| `Session.send()` | Ready | Calls `sessions.send` and returns a `Run`. |
|
||||
| `oc.models` | Ready | Calls `models.list` and the current `models.authStatus` status RPC. |
|
||||
| `oc.tools` | Ready | Lists, scopes, and invokes Gateway tools through the policy pipeline. |
|
||||
| `oc.artifacts` | Ready | Lists, gets, and downloads Gateway transcript artifacts. |
|
||||
| `oc.approvals` | Ready | Lists and resolves exec approvals through Gateway approval RPCs. |
|
||||
| `oc.rawEvents()` | Ready | Exposes raw Gateway events for advanced consumers. |
|
||||
| `normalizeGatewayEvent()` | Ready | Converts raw Gateway events into the stable SDK event shape. |
|
||||
|
||||
The SDK also exports the core types used by those surfaces:
|
||||
`AgentRunParams`, `RunResult`, `RunStatus`, `OpenClawEvent`,
|
||||
`OpenClawEventType`, `GatewayEvent`, `OpenClawTransport`,
|
||||
`GatewayRequestOptions`, `SessionCreateParams`, `SessionSendParams`,
|
||||
`RuntimeSelection`, `EnvironmentSelection`, `WorkspaceSelection`,
|
||||
`ApprovalMode`, and related result types.
|
||||
`ArtifactSummary`, `ArtifactQuery`, `ArtifactsListResult`,
|
||||
`ArtifactsGetResult`, `ArtifactsDownloadResult`, `RuntimeSelection`,
|
||||
`EnvironmentSelection`, `WorkspaceSelection`, `ApprovalMode`, and related
|
||||
result types.
|
||||
|
||||
## Connect To A Gateway
|
||||
|
||||
@@ -204,7 +207,7 @@ for await (const event of run.events()) {
|
||||
For app-wide streams, use `oc.events()`. For raw Gateway frames, use
|
||||
`oc.rawEvents()`.
|
||||
|
||||
## Models, Tools, And Approvals
|
||||
## Models, Tools, Artifacts, And Approvals
|
||||
|
||||
Model helpers map to current Gateway methods:
|
||||
|
||||
@@ -213,11 +216,34 @@ await oc.models.list();
|
||||
await oc.models.status({ probe: false }); // calls models.authStatus
|
||||
```
|
||||
|
||||
Tool helpers expose the Gateway catalog and effective tool view:
|
||||
Tool helpers expose the Gateway catalog, effective tool view, and direct
|
||||
Gateway tool invocation. `oc.tools.invoke()` returns a typed envelope instead
|
||||
of throwing for policy or approval refusals.
|
||||
|
||||
```typescript
|
||||
await oc.tools.list();
|
||||
await oc.tools.effective({ sessionKey: "main" });
|
||||
await oc.tools.invoke("tool-name", {
|
||||
args: { input: "value" },
|
||||
sessionKey: "main",
|
||||
confirm: false,
|
||||
idempotencyKey: "tool-call-1",
|
||||
});
|
||||
```
|
||||
|
||||
Artifact helpers expose the Gateway artifact projection for session, run, or
|
||||
task context. Each call requires one explicit `sessionKey`, `runId`, or
|
||||
`taskId` scope:
|
||||
|
||||
```typescript
|
||||
const { artifacts } = await oc.artifacts.list({ sessionKey: "main" });
|
||||
const first = artifacts[0];
|
||||
|
||||
if (first) {
|
||||
const { artifact } = await oc.artifacts.get(first.id, { sessionKey: "main" });
|
||||
const download = await oc.artifacts.download(artifact.id, { sessionKey: "main" });
|
||||
console.log(download.encoding, download.url);
|
||||
}
|
||||
```
|
||||
|
||||
Approval helpers use the exec approval RPCs:
|
||||
@@ -238,12 +264,6 @@ await oc.tasks.list();
|
||||
await oc.tasks.get("task-id");
|
||||
await oc.tasks.cancel("task-id");
|
||||
|
||||
await oc.tools.invoke("tool-name", {});
|
||||
|
||||
await oc.artifacts.list();
|
||||
await oc.artifacts.get("artifact-id");
|
||||
await oc.artifacts.download("artifact-id");
|
||||
|
||||
await oc.environments.list();
|
||||
await oc.environments.create({});
|
||||
await oc.environments.status("environment-id");
|
||||
|
||||
@@ -114,6 +114,7 @@ keys.
|
||||
|
||||
- If commands seem stuck, enable verbose logs and look for “queued for …ms” lines to confirm the queue is draining.
|
||||
- If you need queue depth, enable verbose logs and watch for queue timing lines.
|
||||
- Codex app-server runs that accept a turn and then stop emitting progress are interrupted by the Codex adapter so the active session lane can release instead of waiting for the outer run timeout.
|
||||
- When diagnostics are enabled, sessions that remain in `processing` past `diagnostics.stuckSessionWarnMs` log a stuck-session warning. Active embedded runs, active reply operations, and active lane tasks remain warning-only by default; stale startup bookkeeping with no active session work can release the affected session lane so queued work drains.
|
||||
|
||||
## Related
|
||||
|
||||
@@ -125,7 +125,7 @@ to `"enforce"` for automatic cleanup:
|
||||
}
|
||||
```
|
||||
|
||||
For production-sized `maxEntries` limits, Gateway runtime writes use a small high-water buffer and clean back down to the configured cap in batches. This avoids running full store cleanup on every isolated cron session. `openclaw sessions cleanup --enforce` applies the cap immediately.
|
||||
For production-sized `maxEntries` limits, Gateway runtime writes use a small high-water buffer and clean back down to the configured cap in batches. Session store reads do not prune or cap entries during Gateway startup. This avoids running full store cleanup on every startup or isolated cron session. `openclaw sessions cleanup --enforce` applies the cap immediately.
|
||||
|
||||
Preview with `openclaw sessions cleanup --dry-run`.
|
||||
|
||||
|
||||
@@ -1182,6 +1182,7 @@
|
||||
"tools/plugin",
|
||||
"plugins/community",
|
||||
"plugins/bundles",
|
||||
"plugins/dependency-resolution",
|
||||
"plugins/codex-harness",
|
||||
"plugins/codex-computer-use",
|
||||
"plugins/google-meet",
|
||||
@@ -1708,7 +1709,13 @@
|
||||
},
|
||||
{
|
||||
"group": "Release and CI",
|
||||
"pages": ["reference/RELEASING", "reference/test", "ci", "help/scripts"]
|
||||
"pages": [
|
||||
"reference/RELEASING",
|
||||
"reference/full-release-validation",
|
||||
"reference/test",
|
||||
"ci",
|
||||
"help/scripts"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -67,6 +67,20 @@ Disables automatic creation of workspace bootstrap files (`AGENTS.md`, `SOUL.md`
|
||||
}
|
||||
```
|
||||
|
||||
### `agents.defaults.skipOptionalBootstrapFiles`
|
||||
|
||||
Skips creation of selected optional workspace files while still writing required bootstrap files. Valid values: `SOUL.md`, `USER.md`, `HEARTBEAT.md`, and `IDENTITY.md`.
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
defaults: {
|
||||
skipOptionalBootstrapFiles: ["SOUL.md", "USER.md"],
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### `agents.defaults.contextInjection`
|
||||
|
||||
Controls when workspace bootstrap files are injected into the system prompt. Default: `"always"`.
|
||||
@@ -560,6 +574,7 @@ Periodic heartbeat runs.
|
||||
identifierPolicy: "strict", // strict | off | custom
|
||||
identifierInstructions: "Preserve deployment IDs, ticket IDs, and host:port pairs exactly.", // used when identifierPolicy=custom
|
||||
qualityGuard: { enabled: true, maxRetries: 1 },
|
||||
midTurnPrecheck: { enabled: false }, // optional Pi tool-loop pressure check
|
||||
postCompactionSections: ["Session Startup", "Red Lines"], // [] disables reinjection
|
||||
model: "openrouter/anthropic/claude-sonnet-4-6", // optional compaction-only model override
|
||||
truncateAfterCompaction: true, // rotate to a smaller successor JSONL after compaction
|
||||
@@ -585,6 +600,7 @@ Periodic heartbeat runs.
|
||||
- `identifierPolicy`: `strict` (default), `off`, or `custom`. `strict` prepends built-in opaque identifier retention guidance during compaction summarization.
|
||||
- `identifierInstructions`: optional custom identifier-preservation text used when `identifierPolicy=custom`.
|
||||
- `qualityGuard`: retry-on-malformed-output checks for safeguard summaries. Enabled by default in safeguard mode; set `enabled: false` to skip the audit.
|
||||
- `midTurnPrecheck`: optional Pi tool-loop pressure check. When `enabled: true`, OpenClaw checks context pressure after tool results are appended and before the next model call. If the context no longer fits, it aborts the current attempt before submitting the prompt and reuses the existing precheck recovery path to truncate tool results or compact and retry. Works with both `default` and `safeguard` compaction modes. Default: disabled.
|
||||
- `postCompactionSections`: optional AGENTS.md H2/H3 section names to re-inject after compaction. Defaults to `["Session Startup", "Red Lines"]`; set `[]` to disable reinjection. When unset or explicitly set to that default pair, older `Every Session`/`Safety` headings are also accepted as a legacy fallback.
|
||||
- `model`: optional `provider/model-id` override for compaction summarization only. Use this when the main session should keep one model but compaction summaries should run on another; when unset, compaction uses the session's primary model.
|
||||
- `maxActiveTranscriptBytes`: optional byte threshold (`number` or strings like `"20mb"`) that triggers normal local compaction before a run when the active JSONL grows past the threshold. Requires `truncateAfterCompaction` so successful compaction can rotate to a smaller successor transcript. Disabled when unset or `0`.
|
||||
@@ -906,13 +922,15 @@ noVNC observer access uses VNC auth by default and OpenClaw emits a short-lived
|
||||
|
||||
Browser sandboxing and `sandbox.docker.binds` are Docker-only.
|
||||
|
||||
Build images:
|
||||
Build images (from a source checkout):
|
||||
|
||||
```bash
|
||||
scripts/sandbox-setup.sh # main sandbox image
|
||||
scripts/sandbox-browser-setup.sh # optional browser image
|
||||
```
|
||||
|
||||
For npm installs without a source checkout, see [Sandboxing § Images and setup](/gateway/sandboxing#images-and-setup) for inline `docker build` commands.
|
||||
|
||||
### `agents.list` (per-agent overrides)
|
||||
|
||||
Use `agents.list[].tts` to give an agent its own TTS provider, voice, model,
|
||||
|
||||
@@ -297,6 +297,8 @@ WhatsApp runs through the gateway's web channel (Baileys Web). It starts automat
|
||||
],
|
||||
daveEncryption: true,
|
||||
decryptionFailureTolerance: 24,
|
||||
connectTimeoutMs: 30000,
|
||||
reconnectGraceMs: 15000,
|
||||
tts: {
|
||||
provider: "openai",
|
||||
openai: { voice: "alloy" },
|
||||
@@ -336,9 +338,11 @@ WhatsApp runs through the gateway's web channel (Baileys Web). It starts automat
|
||||
- `spawnSubagentSessions`: opt-in switch for `sessions_spawn({ thread: true })` auto thread creation/binding
|
||||
- Top-level `bindings[]` entries with `type: "acp"` configure persistent ACP bindings for channels and threads (use channel/thread id in `match.peer.id`). Field semantics are shared in [ACP Agents](/tools/acp-agents#channel-specific-settings).
|
||||
- `channels.discord.ui.components.accentColor` sets the accent color for Discord components v2 containers.
|
||||
- `channels.discord.voice` enables Discord voice channel conversations and optional auto-join + LLM + TTS overrides.
|
||||
- `channels.discord.voice` enables Discord voice channel conversations and optional auto-join + LLM + TTS overrides. Text-only Discord configs leave voice off by default; set `channels.discord.voice.enabled=true` to opt in.
|
||||
- `channels.discord.voice.model` optionally overrides the LLM model used for Discord voice channel responses.
|
||||
- `channels.discord.voice.daveEncryption` and `channels.discord.voice.decryptionFailureTolerance` pass through to `@discordjs/voice` DAVE options (`true` and `24` by default).
|
||||
- `channels.discord.voice.connectTimeoutMs` controls the initial `@discordjs/voice` Ready wait for `/vc join` and auto-join attempts (`30000` by default).
|
||||
- `channels.discord.voice.reconnectGraceMs` controls how long a disconnected voice session may take to enter reconnect signalling before OpenClaw destroys it (`15000` by default).
|
||||
- OpenClaw additionally attempts voice receive recovery by leaving/rejoining a voice session after repeated decrypt failures.
|
||||
- `channels.discord.streaming` is the canonical stream mode key. Legacy `streamMode` and boolean `streaming` values are auto-migrated.
|
||||
- `channels.discord.autoPresence` maps runtime availability to bot presence (healthy => online, degraded => idle, exhausted => dnd) and allows optional status text overrides.
|
||||
@@ -772,6 +776,10 @@ Group messages default to **require mention** (metadata mention or safe regex pa
|
||||
|
||||
Visible replies are controlled separately. Group/channel rooms default to `messages.groupChat.visibleReplies: "message_tool"`: OpenClaw still processes the turn, but normal final replies stay private and visible room output requires `message(action=send)`. Set `"automatic"` only when you want the legacy behavior where normal replies are posted back to the room. To apply the same tool-only visible-reply behavior to direct chats too, set `messages.visibleReplies: "message_tool"`.
|
||||
|
||||
If the message tool is unavailable under the active tool policy, OpenClaw falls back to automatic visible replies instead of silently suppressing the response. `openclaw doctor` warns about this mismatch.
|
||||
|
||||
The gateway hot-reloads `messages` config after the file is saved. Restart only when file watching or config reload is disabled in the deployment.
|
||||
|
||||
**Mention types:**
|
||||
|
||||
- **Metadata mentions**: Native platform @-mentions. Ignored in WhatsApp self-chat mode.
|
||||
|
||||
@@ -201,7 +201,7 @@ Configures inbound media understanding (image/audio/video):
|
||||
media: {
|
||||
concurrency: 2,
|
||||
asyncCompletion: {
|
||||
directSend: false, // opt-in: send finished async music/video directly to the channel
|
||||
directSend: false, // opt-in: send finished async video directly to the channel
|
||||
},
|
||||
audio: {
|
||||
enabled: true,
|
||||
@@ -254,7 +254,7 @@ Configures inbound media understanding (image/audio/video):
|
||||
|
||||
**Async completion fields:**
|
||||
|
||||
- `asyncCompletion.directSend`: when `true`, completed async `music_generate` and `video_generate` tasks try direct channel delivery first. Default: `false` (legacy requester-session wake/model-delivery path).
|
||||
- `asyncCompletion.directSend`: when `true`, completed async media tasks that support direct completion delivery try direct channel delivery first. Default: `false` (requester-session wake/model-delivery path). Today this applies to async `video_generate`; async `music_generate` completions stay requester-session mediated even when this is enabled.
|
||||
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
@@ -333,7 +333,7 @@ cannot roll back unrelated user settings.
|
||||
}
|
||||
```
|
||||
|
||||
Build the image first: `scripts/sandbox-setup.sh`
|
||||
Build the image first — from a source checkout run `scripts/sandbox-setup.sh`, or from an npm install see the inline `docker build` command in [Sandboxing § Images and setup](/gateway/sandboxing#images-and-setup).
|
||||
|
||||
See [Sandboxing](/gateway/sandboxing) for the full guide and [full reference](/gateway/config-agents#agentsdefaultssandbox) for all options.
|
||||
|
||||
|
||||
@@ -83,6 +83,7 @@ cat ~/.openclaw/openclaw.json
|
||||
- OpenCode provider override warnings (`models.providers.opencode` / `models.providers.opencode-go`).
|
||||
- Codex OAuth shadowing warnings (`models.providers.openai-codex`).
|
||||
- OAuth TLS prerequisites check for OpenAI Codex OAuth profiles.
|
||||
- Plugin/tool allowlist warnings when `plugins.allow` is restrictive but tool policy still asks for wildcard or plugin-owned tools.
|
||||
- Legacy on-disk state migration (sessions/agent dir/WhatsApp auth).
|
||||
- Legacy plugin manifest contract key migration (`speechProviders`, `realtimeTranscriptionProviders`, `realtimeVoiceProviders`, `mediaUnderstandingProviders`, `imageGenerationProviders`, `videoGenerationProviders`, `webFetchProviders`, `webSearchProviders` → `contracts`).
|
||||
- Legacy cron store migration (`jobId`, `schedule.cron`, top-level delivery/payload fields, payload `provider`, simple `notify: true` webhook fallback jobs).
|
||||
@@ -93,6 +94,7 @@ cat ~/.openclaw/openclaw.json
|
||||
<Accordion title="State and integrity">
|
||||
- Session lock file inspection and stale lock cleanup.
|
||||
- Session transcript repair for duplicated prompt-rewrite branches created by affected 2026.4.24 builds.
|
||||
- Wedged subagent restart-recovery tombstone detection, with `--fix` support for clearing stale aborted recovery flags so startup does not keep treating the child as restart-aborted.
|
||||
- State integrity and permissions checks (sessions, transcripts, state dir).
|
||||
- Config file permission checks (chmod 600) when running locally.
|
||||
- Model auth health: checks OAuth expiry, can refresh expiring tokens, and reports auth-profile cooldown/disabled states.
|
||||
@@ -163,6 +165,11 @@ That stages grounded durable candidates into the short-term dreaming store while
|
||||
|
||||
That includes legacy Talk flat fields. Current public Talk config is `talk.provider` + `talk.providers.<provider>`. Doctor rewrites old `talk.voiceId` / `talk.voiceAliases` / `talk.modelId` / `talk.outputFormat` / `talk.apiKey` shapes into the provider map.
|
||||
|
||||
Doctor also warns when `plugins.allow` is non-empty and tool policy uses
|
||||
wildcard or plugin-owned tool entries. `tools.allow: ["*"]` only matches tools
|
||||
from plugins that actually load; it does not bypass the exclusive plugin
|
||||
allowlist.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="2. Legacy config key migrations">
|
||||
When the config contains deprecated keys, other commands refuse to run and ask you to run `openclaw doctor`.
|
||||
@@ -335,7 +342,7 @@ That stages grounded durable candidates into the short-term dreaming store while
|
||||
<Accordion title="7b. Bundled plugin runtime deps">
|
||||
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`, configured `models.providers.*` / agent model refs, or a default-enabled bundled plugin without provider ownership. 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.
|
||||
|
||||
During doctor repair, bundled runtime-dependency npm installs report spinner progress in TTY sessions and periodic line progress in piped/headless output. The Gateway and local CLI can also repair active bundled plugin runtime dependencies on demand before importing a bundled plugin. These installs are scoped to the plugin runtime install root, run with scripts disabled, do not write a package lock, and are guarded by an install-root lock so concurrent CLI or Gateway starts do not mutate the same `node_modules` tree at the same time.
|
||||
During doctor repair, bundled runtime-dependency npm installs report spinner progress in TTY sessions and periodic line progress in piped/headless output. Gateway startup and config reload enter plugin-plan mode before importing bundled plugin runtime modules; normal runtime imports are verify-only and do not spawn package-manager repair. These installs are scoped to the plugin runtime install root, run with scripts disabled, do not write a package lock, and are guarded by an install-root lock so concurrent CLI or Gateway starts do not mutate the same `node_modules` tree at the same time. Stale legacy locks from killed Docker/container starts are reclaimed when their owner metadata cannot prove a current process incarnation and the lock files are old.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="8. Gateway service migrations and cleanup hints">
|
||||
|
||||
@@ -28,7 +28,7 @@ title: "Gateway lock"
|
||||
## Operational notes
|
||||
|
||||
- If the port is occupied by _another_ process, the error is the same; free the port or choose another with `openclaw gateway --port <port>`.
|
||||
- Under a service supervisor, a new gateway process that sees an existing healthy `/healthz` responder exits successfully and leaves that process in control. If the existing process never becomes healthy, retries are bounded and startup fails with a clear lock error instead of looping forever.
|
||||
- Under a service supervisor, a new gateway process that sees an existing healthy `/healthz` responder leaves that process in control. On systemd, the duplicate starter exits with code 78 so the default `RestartPreventExitStatus=78` stops `Restart=always` from looping on a lock or `EADDRINUSE` conflict. If the existing process never becomes healthy, retries are bounded and startup fails with a clear lock error instead of looping forever.
|
||||
- The macOS app still maintains its own lightweight PID guard before spawning the gateway; the runtime lock is enforced by the lock file plus HTTP/WebSocket bind.
|
||||
|
||||
## Related
|
||||
|
||||
@@ -319,7 +319,7 @@ Compatibility notes for stricter OpenAI-compatible backends:
|
||||
OpenClaw process RSS/heap snapshot in diagnostics. For LM Studio/Ollama
|
||||
memory pressure, match that timestamp against the server log or macOS crash /
|
||||
jetsam log to confirm whether the model server was killed.
|
||||
- OpenClaw warns when the detected context window is below **32k** and blocks below **16k**. If you hit that preflight, raise the server/model context limit or choose a larger model.
|
||||
- OpenClaw derives context-window preflight thresholds from the detected model window, or from the uncapped model window when `agents.defaults.contextTokens` lowers the effective window. It warns below 20% with an **8k** floor. Hard blocks use the 10% threshold with a **4k** floor, capped to the effective context window so oversized model metadata cannot reject an otherwise valid user cap. If you hit that preflight, raise the server/model context limit or choose a larger model.
|
||||
- Context errors? Lower `contextWindow` or raise your server limit.
|
||||
- OpenAI-compatible server returns `messages[].content ... expected a string`?
|
||||
Add `compat.requiresStringContent: true` on that model entry.
|
||||
|
||||
@@ -63,7 +63,7 @@ masked before JSONL lines or messages are written to disk.
|
||||
- `logging.redactPatterns`: array of regex strings (overrides defaults)
|
||||
- Use raw regex strings (auto `gi`), or `/pattern/flags` if you need custom flags.
|
||||
- Matches are masked by keeping the first 6 + last 4 chars (length >= 18), otherwise `***`.
|
||||
- Defaults cover common key assignments, CLI flags, JSON fields, bearer headers, PEM blocks, and popular token prefixes.
|
||||
- Defaults cover common key assignments, CLI flags, JSON fields, bearer headers, PEM blocks, popular token prefixes, and payment credential field names such as card number, CVC/CVV, shared payment token, and payment credential.
|
||||
|
||||
Some safety boundaries always redact regardless of `logging.redactSensitive`.
|
||||
That includes Control UI tool-call events, `sessions_history` tool output,
|
||||
|
||||
@@ -378,7 +378,7 @@ enumeration of `src/gateway/server-methods/*.ts`.
|
||||
- `config.apply` validates + replaces the full config payload.
|
||||
- `config.schema` returns the live config schema payload used by Control UI and CLI tooling: schema, `uiHints`, version, and generation metadata, including plugin + channel schema metadata when the runtime can load it. The schema includes field `title` / `description` metadata derived from the same labels and help text used by the UI, including nested object, wildcard, array-item, and `anyOf` / `oneOf` / `allOf` composition branches when matching field documentation exists.
|
||||
- `config.schema.lookup` returns a path-scoped lookup payload for one config path: normalized path, a shallow schema node, matched hint + `hintPath`, and immediate child summaries for UI/CLI drill-down. Lookup schema nodes keep the user-facing docs and common validation fields (`title`, `description`, `type`, `enum`, `const`, `format`, `pattern`, numeric/string/array/object bounds, and flags like `additionalProperties`, `deprecated`, `readOnly`, `writeOnly`). Child summaries expose `key`, normalized `path`, `type`, `required`, `hasChildren`, plus the matched `hint` / `hintPath`.
|
||||
- `update.run` runs the gateway update flow and schedules a restart only when the update itself succeeded.
|
||||
- `update.run` runs the gateway update flow and schedules a restart only when the update itself succeeded. Package-manager updates force a non-deferred, no-cooldown update restart after the package swap so the old Gateway process does not keep lazy-loading from a replaced `dist` tree.
|
||||
- `update.status` returns the latest cached update restart sentinel, including the post-restart running version when available.
|
||||
- `wizard.start`, `wizard.next`, `wizard.status`, and `wizard.cancel` expose the onboarding wizard over WS RPC.
|
||||
|
||||
@@ -388,6 +388,7 @@ enumeration of `src/gateway/server-methods/*.ts`.
|
||||
- `agents.list` returns configured agent entries, including effective model and runtime metadata.
|
||||
- `agents.create`, `agents.update`, and `agents.delete` manage agent records and workspace wiring.
|
||||
- `agents.files.list`, `agents.files.get`, and `agents.files.set` manage the bootstrap workspace files exposed for an agent.
|
||||
- `artifacts.list`, `artifacts.get`, and `artifacts.download` expose transcript-derived artifact summaries and downloads for an explicit `sessionKey`, `runId`, or `taskId` scope. Run and task queries resolve the owning session server-side and only return transcript media with matching provenance; unsafe or local URL sources return unsupported downloads instead of fetching server-side.
|
||||
- `agent.identity.get` returns the effective assistant identity for an agent or session.
|
||||
- `agent.wait` waits for a run to finish and returns the terminal snapshot when available.
|
||||
|
||||
@@ -442,7 +443,7 @@ enumeration of `src/gateway/server-methods/*.ts`.
|
||||
|
||||
<Accordion title="Automation, skills, and tools">
|
||||
- Automation: `wake` schedules an immediate or next-heartbeat wake text injection; `cron.list`, `cron.status`, `cron.add`, `cron.update`, `cron.remove`, `cron.run`, `cron.runs` manage scheduled work.
|
||||
- Skills and tools: `commands.list`, `skills.*`, `tools.catalog`, `tools.effective`.
|
||||
- Skills and tools: `commands.list`, `skills.*`, `tools.catalog`, `tools.effective`, `tools.invoke`.
|
||||
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
@@ -500,6 +501,15 @@ enumeration of `src/gateway/server-methods/*.ts`.
|
||||
caller-supplied auth or delivery context.
|
||||
- The response is session-scoped and reflects what the active conversation can use right now,
|
||||
including core, plugin, and channel tools.
|
||||
- Operators may call `tools.invoke` (`operator.write`) to invoke one available tool through the
|
||||
same gateway policy path as `/tools/invoke`.
|
||||
- `name` is required. `args`, `sessionKey`, `agentId`, `confirm`, and
|
||||
`idempotencyKey` are optional.
|
||||
- If both `sessionKey` and `agentId` are present, the resolved session agent must match
|
||||
`agentId`.
|
||||
- The response is an SDK-facing envelope with `ok`, `toolName`, optional `output`, and typed
|
||||
`error` fields. Approval or policy refusals return `ok:false` in the payload rather than
|
||||
bypassing the gateway tool policy pipeline.
|
||||
- Operators may call `skills.status` (`operator.read`) to fetch the visible
|
||||
skill inventory for an agent.
|
||||
- `agentId` is optional; omit it to read the default agent workspace.
|
||||
|
||||
@@ -363,31 +363,66 @@ Example (read-only source + an extra data directory):
|
||||
|
||||
Default Docker image: `openclaw-sandbox:bookworm-slim`
|
||||
|
||||
<Note>
|
||||
**Source checkout vs npm install**
|
||||
|
||||
The `scripts/sandbox-setup.sh`, `scripts/sandbox-common-setup.sh`, and `scripts/sandbox-browser-setup.sh` helper scripts are only available when running from a [source checkout](https://github.com/openclaw/openclaw). They are not included in the npm package.
|
||||
|
||||
If you installed OpenClaw via `npm install -g openclaw`, use the inline `docker build` commands shown below instead.
|
||||
</Note>
|
||||
|
||||
<Steps>
|
||||
<Step title="Build the default image">
|
||||
From a source checkout:
|
||||
|
||||
```bash
|
||||
scripts/sandbox-setup.sh
|
||||
```
|
||||
|
||||
From an npm install (no source checkout needed):
|
||||
|
||||
```bash
|
||||
docker build -t openclaw-sandbox:bookworm-slim - <<'DOCKERFILE'
|
||||
FROM debian:bookworm-slim
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
bash ca-certificates curl git jq python3 ripgrep \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
RUN useradd --create-home --shell /bin/bash sandbox
|
||||
USER sandbox
|
||||
WORKDIR /home/sandbox
|
||||
CMD ["sleep", "infinity"]
|
||||
DOCKERFILE
|
||||
```
|
||||
|
||||
The default image does **not** include Node. If a skill needs Node (or other runtimes), either bake a custom image or install via `sandbox.docker.setupCommand` (requires network egress + writable root + root user).
|
||||
|
||||
OpenClaw does not silently substitute plain `debian:bookworm-slim` when `openclaw-sandbox:bookworm-slim` is missing. Sandbox runs that target the default image fail fast with a build instruction until you run `scripts/sandbox-setup.sh`, because the bundled image carries `python3` for sandbox write/edit helpers.
|
||||
OpenClaw does not silently substitute plain `debian:bookworm-slim` when `openclaw-sandbox:bookworm-slim` is missing. Sandbox runs that target the default image fail fast with a build instruction until you build it, because the bundled image carries `python3` for sandbox write/edit helpers.
|
||||
|
||||
</Step>
|
||||
<Step title="Optional: build the common image">
|
||||
For a more functional sandbox image with common tooling (for example `curl`, `jq`, `nodejs`, `python3`, `git`):
|
||||
|
||||
From a source checkout:
|
||||
|
||||
```bash
|
||||
scripts/sandbox-common-setup.sh
|
||||
```
|
||||
|
||||
From an npm install, build the default image first (see above), then build the common image on top using the [`Dockerfile.sandbox-common`](https://github.com/openclaw/openclaw/blob/main/Dockerfile.sandbox-common) from the repository.
|
||||
|
||||
Then set `agents.defaults.sandbox.docker.image` to `openclaw-sandbox-common:bookworm-slim`.
|
||||
|
||||
</Step>
|
||||
<Step title="Optional: build the sandbox browser image">
|
||||
From a source checkout:
|
||||
|
||||
```bash
|
||||
scripts/sandbox-browser-setup.sh
|
||||
```
|
||||
|
||||
From an npm install, build using the [`Dockerfile.sandbox-browser`](https://github.com/openclaw/openclaw/blob/main/Dockerfile.sandbox-browser) from the repository.
|
||||
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
|
||||
@@ -236,6 +236,7 @@ Use this when auditing access or deciding what to back up:
|
||||
- `~/.openclaw/credentials/<channel>-allowFrom.json` (default account)
|
||||
- `~/.openclaw/credentials/<channel>-<accountId>-allowFrom.json` (non-default accounts)
|
||||
- **Model auth profiles**: `~/.openclaw/agents/<agentId>/agent/auth-profiles.json`
|
||||
- **Codex runtime state**: `~/.openclaw/agents/<agentId>/agent/codex-home/`
|
||||
- **File-backed secrets payload (optional)**: `~/.openclaw/secrets.json`
|
||||
- **Legacy OAuth import**: `~/.openclaw/credentials/oauth.json`
|
||||
|
||||
@@ -965,6 +966,7 @@ Assume anything under `~/.openclaw/` (or `$OPENCLAW_STATE_DIR/`) may contain sec
|
||||
- `openclaw.json`: config may include tokens (gateway, remote gateway), provider settings, and allowlists.
|
||||
- `credentials/**`: channel credentials (example: WhatsApp creds), pairing allowlists, legacy OAuth imports.
|
||||
- `agents/<agentId>/agent/auth-profiles.json`: API keys, token profiles, OAuth tokens, and optional `keyRef`/`tokenRef`.
|
||||
- `agents/<agentId>/agent/codex-home/**`: per-agent Codex app-server account, config, skills, plugins, native thread state, and diagnostics.
|
||||
- `secrets.json` (optional): file-backed secret payload used by `file` SecretRef providers (`secrets.providers`).
|
||||
- `agents/<agentId>/agent/auth.json`: legacy compatibility file. Static `api_key` entries are scrubbed when discovered.
|
||||
- `agents/<agentId>/sessions/**`: session transcripts (`*.jsonl`) + routing metadata (`sessions.json`) that can contain private messages and tool output.
|
||||
|
||||
@@ -341,6 +341,7 @@ Look for:
|
||||
- `.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.
|
||||
- `Rejected validation details:` → the recovery log or main-agent notice includes the schema path that caused the restore, such as `agents.defaults.execution` or `gateway.auth.password.source`.
|
||||
- `missing-meta-vs-last-good`, `gateway-mode-missing-vs-last-good`, or `size-drop-vs-last-good:*` → startup treated the current file as clobbered because it lost fields or size compared with the last-known-good backup.
|
||||
- `Config last-known-good promotion skipped` → the candidate contained redacted secret placeholders such as `***`.
|
||||
|
||||
|
||||
@@ -600,10 +600,10 @@ These Docker runners split into two buckets:
|
||||
`OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=90000`. Override those env vars when you
|
||||
explicitly want the larger exhaustive scan.
|
||||
- `test:docker:all` builds the live Docker image once via `test:docker:live-build`, packs OpenClaw once as an npm tarball through `scripts/package-openclaw-for-docker.mjs`, then builds/reuses two `scripts/e2e/Dockerfile` images. The bare image is only the Node/Git runner for install/update/plugin-dependency lanes; those lanes mount the prebuilt tarball. The functional image installs the same tarball into `/app` for built-app functionality lanes. Docker lane definitions live in `scripts/lib/docker-e2e-scenarios.mjs`; planner logic lives in `scripts/lib/docker-e2e-plan.mjs`; `scripts/test-docker-all.mjs` executes the selected plan. The aggregate uses a weighted local scheduler: `OPENCLAW_DOCKER_ALL_PARALLELISM` controls process slots, while resource caps keep heavy live, npm-install, and multi-service lanes from all starting at once. If a single lane is heavier than the active caps, the scheduler can still start it when the pool is empty and then keeps it running alone until capacity is available again. Defaults are 10 slots, `OPENCLAW_DOCKER_ALL_LIVE_LIMIT=9`, `OPENCLAW_DOCKER_ALL_NPM_LIMIT=10`, and `OPENCLAW_DOCKER_ALL_SERVICE_LIMIT=7`; tune `OPENCLAW_DOCKER_ALL_WEIGHT_LIMIT` or `OPENCLAW_DOCKER_ALL_DOCKER_LIMIT` only when the Docker host has more headroom. The runner performs a Docker preflight by default, removes stale OpenClaw E2E containers, prints status every 30 seconds, stores successful lane timings in `.artifacts/docker-tests/lane-timings.json`, and uses those timings to start longer lanes first on later runs. Use `OPENCLAW_DOCKER_ALL_DRY_RUN=1` to print the weighted lane manifest without building or running Docker, or `node scripts/test-docker-all.mjs --plan-json` to print the CI plan for selected lanes, package/image needs, and credentials.
|
||||
- `Package Acceptance` is the GitHub-native package gate for "does this installable tarball work as a product?" It resolves one candidate package from `source=npm`, `source=ref`, `source=url`, or `source=artifact`, uploads it as `package-under-test`, then runs the reusable Docker E2E lanes against that exact tarball instead of repacking the selected ref. `workflow_ref` selects the trusted workflow/harness scripts, while `package_ref` selects the source commit/branch/tag to pack when `source=ref`; this lets current acceptance logic validate older trusted commits. Profiles are ordered by breadth: `smoke` is quick install/channel/agent plus gateway/config, `package` is the package/update/plugin contract and the default native replacement for most Parallels package/update coverage, `product` adds MCP channels, cron/subagent cleanup, OpenAI web search, and OpenWebUI, and `full` runs the release-path Docker chunks with OpenWebUI. Release validation runs a custom package delta (`bundled-channel-deps-compat plugins-offline`) plus Telegram package QA because the release-path Docker chunks already cover the overlapping package/update/plugin lanes. Targeted GitHub Docker rerun commands generated from artifacts include prior package artifact and prepared image inputs when available, so failed lanes can avoid rebuilding the package and images.
|
||||
- `Package Acceptance` is the GitHub-native package gate for "does this installable tarball work as a product?" It resolves one candidate package from `source=npm`, `source=ref`, `source=url`, or `source=artifact`, uploads it as `package-under-test`, then runs the reusable Docker E2E lanes against that exact tarball instead of repacking the selected ref. `workflow_ref` selects the trusted workflow/harness scripts, while `package_ref` selects the source commit/branch/tag to pack when `source=ref`; this lets current acceptance logic validate older trusted commits. Profiles are ordered by breadth: `smoke` is quick install/channel/agent plus gateway/config, `package` is the package/update/plugin contract plus the keyless upgrade-survivor fixture, the published-baseline upgrade survivor lane, and the default native replacement for most Parallels package/update coverage, `product` adds MCP channels, cron/subagent cleanup, OpenAI web search, and OpenWebUI, and `full` runs the release-path Docker chunks with OpenWebUI. For `published-upgrade-survivor`, Package Acceptance always uses `package-under-test` as the candidate and `published_upgrade_survivor_baseline` as the fallback published baseline, defaulting to `openclaw@latest`; set `published_upgrade_survivor_baselines=release-history` to shard the lane across a deduped matrix of the latest six stable releases, `2026.4.23`, and the latest stable release before `2026-03-15`. The published lane configures its baseline with a baked `openclaw config set` command recipe, then records recipe steps in the lane summary. Release validation runs a custom package delta (`bundled-channel-deps-compat plugins-offline`) plus Telegram package QA because the release-path Docker chunks already cover the overlapping package/update/plugin lanes. Targeted GitHub Docker rerun commands generated from artifacts include prior package artifact, prepared image inputs, and the published upgrade-survivor baseline list when available, so failed lanes can avoid rebuilding the package and images.
|
||||
- Build and release checks run `scripts/check-cli-bootstrap-imports.mjs` after tsdown. The guard walks the static built graph from `dist/entry.js` and `dist/cli/run-main.js` and fails if pre-dispatch startup imports package dependencies such as Commander, prompt UI, undici, or logging before command dispatch; it also keeps the bundled gateway run chunk under budget and rejects static imports of known cold gateway paths. Packaged CLI smoke also covers root help, onboard help, doctor help, status, config schema, and a model-list command.
|
||||
- Package Acceptance legacy compatibility is capped at `2026.4.25` (`2026.4.25-beta.*` included). Through that cutoff, the harness tolerates only shipped-package metadata gaps: omitted private QA inventory entries, missing `gateway install --wrapper`, missing patch files in the tarball-derived git fixture, missing persisted `update.channel`, legacy plugin install-record locations, missing marketplace install-record persistence, and config metadata migration during `plugins update`. For packages after `2026.4.25`, those paths are strict failures.
|
||||
- Container smoke runners: `test:docker:openwebui`, `test:docker:onboard`, `test:docker:npm-onboard-channel-agent`, `test:docker:update-channel-switch`, `test:docker:session-runtime-context`, `test:docker:agents-delete-shared-workspace`, `test:docker:gateway-network`, `test:docker:browser-cdp-snapshot`, `test:docker:mcp-channels`, `test:docker:pi-bundle-mcp-tools`, `test:docker:cron-mcp-cleanup`, `test:docker:plugins`, `test:docker:plugin-update`, and `test:docker:config-reload` boot one or more real containers and verify higher-level integration paths.
|
||||
- Container smoke runners: `test:docker:openwebui`, `test:docker:onboard`, `test:docker:npm-onboard-channel-agent`, `test:docker:update-channel-switch`, `test:docker:upgrade-survivor`, `test:docker:published-upgrade-survivor`, `test:docker:session-runtime-context`, `test:docker:agents-delete-shared-workspace`, `test:docker:gateway-network`, `test:docker:browser-cdp-snapshot`, `test:docker:mcp-channels`, `test:docker:pi-bundle-mcp-tools`, `test:docker:cron-mcp-cleanup`, `test:docker:plugins`, `test:docker:plugin-update`, and `test:docker:config-reload` boot one or more real containers and verify higher-level integration paths.
|
||||
|
||||
The live-model Docker runners also bind-mount only the needed CLI auth homes (or all supported ones when the run is not narrowed), then copy them into the container home before the run so external-CLI OAuth can refresh tokens without mutating the host auth store:
|
||||
|
||||
@@ -617,6 +617,8 @@ The live-model Docker runners also bind-mount only the needed CLI auth homes (or
|
||||
- Onboarding wizard (TTY, full scaffolding): `pnpm test:docker:onboard` (script: `scripts/e2e/onboard-docker.sh`)
|
||||
- Npm tarball onboarding/channel/agent smoke: `pnpm test:docker:npm-onboard-channel-agent` installs the packed OpenClaw tarball globally in Docker, configures OpenAI via env-ref onboarding plus Telegram by default, verifies doctor repairs activated plugin runtime deps, and runs one mocked OpenAI agent turn. Reuse a prebuilt tarball with `OPENCLAW_CURRENT_PACKAGE_TGZ=/path/to/openclaw-*.tgz`, skip the host rebuild with `OPENCLAW_NPM_ONBOARD_HOST_BUILD=0`, or switch channel with `OPENCLAW_NPM_ONBOARD_CHANNEL=discord`.
|
||||
- Update channel switch smoke: `pnpm test:docker:update-channel-switch` installs the packed OpenClaw tarball globally in Docker, switches from package `stable` to git `dev`, verifies the persisted channel and plugin post-update work, then switches back to package `stable` and checks update status.
|
||||
- Upgrade survivor smoke: `pnpm test:docker:upgrade-survivor` installs the packed OpenClaw tarball over a dirty old-user fixture with agents, channel config, plugin allowlists, stale plugin runtime-deps state, and existing workspace/session files. It runs package update plus non-interactive doctor without live provider or channel keys, then starts a loopback Gateway and checks config/state preservation plus startup/status budgets.
|
||||
- Published upgrade survivor smoke: `pnpm test:docker:published-upgrade-survivor` installs `openclaw@latest` by default, seeds realistic existing-user files, configures that baseline with a baked command recipe, validates the resulting config, updates that published install to the candidate tarball, runs non-interactive doctor, writes `.artifacts/upgrade-survivor/summary.json`, then starts a loopback Gateway and checks configured intents, state preservation, startup, `/healthz`, `/readyz`, and RPC status budgets. Override one baseline with `OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC`, ask the aggregate scheduler to expand exact baselines with `OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPECS`, and expand issue-shaped fixtures with `OPENCLAW_UPGRADE_SURVIVOR_SCENARIOS` such as `reported-issues`; Package Acceptance exposes those as `published_upgrade_survivor_baseline`, `published_upgrade_survivor_baselines`, and `published_upgrade_survivor_scenarios`.
|
||||
- Session runtime context smoke: `pnpm test:docker:session-runtime-context` verifies hidden runtime context transcript persistence plus doctor repair of affected duplicated prompt-rewrite branches.
|
||||
- Bun global install smoke: `bash scripts/e2e/bun-global-install-smoke.sh` packs the current tree, installs it with `bun install -g` in an isolated home, and verifies `openclaw infer image providers --json` returns bundled image providers instead of hanging. Reuse a prebuilt tarball with `OPENCLAW_BUN_GLOBAL_SMOKE_PACKAGE_TGZ=/path/to/openclaw-*.tgz`, skip the host build with `OPENCLAW_BUN_GLOBAL_SMOKE_HOST_BUILD=0`, or copy `dist/` from a built Docker image with `OPENCLAW_BUN_GLOBAL_SMOKE_DIST_IMAGE=openclaw-dockerfile-smoke:local`.
|
||||
- Installer Docker smoke: `bash scripts/test-install-sh-docker.sh` shares one npm cache across its root, update, and direct-npm containers. Update smoke defaults to npm `latest` as the stable baseline before upgrading to the candidate tarball. Override with `OPENCLAW_INSTALL_SMOKE_UPDATE_BASELINE=2026.4.22` locally, or with the Install Smoke workflow's `update_baseline_version` input on GitHub. Non-root installer checks keep an isolated npm cache so root-owned cache entries do not mask user-local install behavior. Set `OPENCLAW_INSTALL_SMOKE_NPM_CACHE_DIR=/path/to/cache` to reuse the root/update/direct-npm cache across local reruns.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user