mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-12 17:23:20 +08:00
Compare commits
378 Commits
fix/matter
...
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 |
@@ -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.
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
79
.github/workflows/package-acceptance.yml
vendored
79
.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:
|
||||
@@ -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
|
||||
|
||||
|
||||
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.
|
||||
|
||||
|
||||
124
CHANGELOG.md
124
CHANGELOG.md
@@ -2,23 +2,128 @@
|
||||
|
||||
Docs: https://docs.openclaw.ai
|
||||
|
||||
## Unreleased
|
||||
## 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
|
||||
|
||||
- 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. Fixes #75283. Thanks @brokemac79.
|
||||
- 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.
|
||||
@@ -42,6 +147,13 @@ Docs: https://docs.openclaw.ai
|
||||
- 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
|
||||
|
||||
@@ -84,6 +196,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### 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.
|
||||
@@ -155,6 +268,7 @@ Docs: https://docs.openclaw.ai
|
||||
- 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.
|
||||
@@ -316,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.
|
||||
@@ -375,6 +490,7 @@ Docs: https://docs.openclaw.ai
|
||||
- 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
|
||||
|
||||
@@ -406,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.
|
||||
@@ -565,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.
|
||||
- Group/channel chats (all channels): 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; \
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"
|
||||
@@ -3826,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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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,6 +166,8 @@ 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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -75,6 +75,10 @@ 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"
|
||||
|
||||
@@ -155,7 +159,7 @@ public enum GatewayTLSStore {
|
||||
}
|
||||
}
|
||||
|
||||
public final class GatewayTLSPinningSession: NSObject, WebSocketSessioning, URLSessionDelegate, GatewayTLSFailureProviding, @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?
|
||||
@@ -170,6 +174,10 @@ 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() }
|
||||
|
||||
@@ -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"
|
||||
@@ -3826,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 {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
f2f5dc47ab9572fa5f80eb01b5a176edb04ca91c7a25bea3b9ea8e19dd21904b config-baseline.json
|
||||
d81f9cadab9762a4b542795ed1f01f27e374f9811cf176f08cbbb7a20b044c15 config-baseline.core.json
|
||||
92712871defa92eeda8161b516db85574681f2b70678b940508a808b987aeae2 config-baseline.channel.json
|
||||
6005cf9f6e8c9f25ef97207b5eee29ae0e506cf910cdeca77fc9894ad1755b1f 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 @@
|
||||
851a39b442a4a15e78d27d8a3e1ee66ff61a061356d412051e205f6c07f54c34 plugin-sdk-api-baseline.json
|
||||
d3106b731a3a13f7dddaa0b1916f223c1757fa8d1df3476914f70502c9532c2f plugin-sdk-api-baseline.jsonl
|
||||
37787172adf7a55a32097599b4bf5729fc7138c8743c6f4c9d58fc8d01df72a1 plugin-sdk-api-baseline.json
|
||||
0ec4957528477832085c638a5f7f691c878ba199f3e81f330f162c27cfd9ebf4 plugin-sdk-api-baseline.jsonl
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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/`.
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
|
||||
33
docs/ci.md
33
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
|
||||
|
||||
@@ -176,7 +188,7 @@ Keep `workflow_ref` and `package_ref` separate. `workflow_ref` is the trusted wo
|
||||
|
||||
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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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`
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -25,24 +25,24 @@ 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.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. |
|
||||
| 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`,
|
||||
@@ -216,11 +216,19 @@ 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
|
||||
@@ -256,8 +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.environments.list();
|
||||
await oc.environments.create({});
|
||||
await oc.environments.status("environment-id");
|
||||
|
||||
@@ -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"`.
|
||||
@@ -908,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,8 @@ 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:**
|
||||
|
||||
@@ -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).
|
||||
@@ -164,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`.
|
||||
@@ -336,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">
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -443,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>
|
||||
@@ -501,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>
|
||||
|
||||
|
||||
@@ -600,7 +600,7 @@ These Docker runners split into two buckets:
|
||||
`OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=90000`. Override those env vars when you
|
||||
explicitly want the larger exhaustive scan.
|
||||
- `test:docker:all` builds the live Docker image once via `test:docker:live-build`, 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 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. 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: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.
|
||||
@@ -618,7 +618,7 @@ The live-model Docker runners also bind-mount only the needed CLI auth homes (or
|
||||
- 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 over the same dirty old-user fixture, updates that published install to the candidate tarball, runs non-interactive doctor, then starts a loopback Gateway and checks the same config/state preservation plus startup/status budgets. Override the baseline with `OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC`.
|
||||
- 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.
|
||||
|
||||
@@ -202,9 +202,11 @@ This is idempotent and safe to run multiple times.
|
||||
# Check sandbox image
|
||||
sudo docker images | grep openclaw-sandbox
|
||||
|
||||
# Build sandbox image if missing
|
||||
# Build sandbox image if missing (requires source checkout)
|
||||
cd /opt/openclaw/openclaw
|
||||
sudo -u openclaw ./scripts/sandbox-setup.sh
|
||||
# For npm installs without a source checkout, see
|
||||
# https://docs.openclaw.ai/gateway/sandboxing#images-and-setup
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
@@ -452,18 +452,21 @@ For full configuration, images, security notes, and multi-agent profiles, see:
|
||||
}
|
||||
```
|
||||
|
||||
Build the default sandbox image:
|
||||
Build the default sandbox image (from a source checkout):
|
||||
|
||||
```bash
|
||||
scripts/sandbox-setup.sh
|
||||
```
|
||||
|
||||
For npm installs without a source checkout, see [Sandboxing § Images and setup](/gateway/sandboxing#images-and-setup) for inline `docker build` commands.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Image missing or sandbox container not starting">
|
||||
Build the sandbox image with
|
||||
[`scripts/sandbox-setup.sh`](https://github.com/openclaw/openclaw/blob/main/scripts/sandbox-setup.sh)
|
||||
(source checkout) or the inline `docker build` command from [Sandboxing § Images and setup](/gateway/sandboxing#images-and-setup) (npm install),
|
||||
or set `agents.defaults.sandbox.docker.image` to your custom image.
|
||||
Containers are auto-created per session on demand.
|
||||
</Accordion>
|
||||
|
||||
@@ -168,6 +168,13 @@ The auto-updater is off by default. Enable it in `~/.openclaw/openclaw.json`:
|
||||
The gateway also logs an update hint on startup (disable with `update.checkOnStart: false`).
|
||||
For downgrade or incident recovery, set `OPENCLAW_NO_AUTO_UPDATE=1` in the gateway environment to block automatic applies even when `update.auto.enabled` is configured. Startup update hints can still run unless `update.checkOnStart` is also disabled.
|
||||
|
||||
Package-manager updates requested through the live Gateway control-plane handler
|
||||
force a non-deferred, no-cooldown update restart after the package swap. That
|
||||
avoids leaving an old in-memory process around long enough to lazy-load chunks
|
||||
from a package tree that has already been replaced. Shell `openclaw update`
|
||||
remains the preferred path for supervised installs because it can stop and
|
||||
restart the service around the update.
|
||||
|
||||
## After updating
|
||||
|
||||
<Steps>
|
||||
|
||||
@@ -220,6 +220,10 @@ masked before the line or message is written to disk. Redaction is best-effort:
|
||||
it applies to text-bearing message content and log strings, not every
|
||||
identifier or binary payload field.
|
||||
|
||||
The built-in defaults cover common API credentials and payment-credential field
|
||||
names such as card number, CVC/CVV, shared payment token, and payment credential
|
||||
when they appear as JSON fields, URL parameters, CLI flags, or assignments.
|
||||
|
||||
`logging.redactSensitive: "off"` only disables this general log/transcript
|
||||
policy. OpenClaw still redacts safety-boundary payloads that can be shown to UI
|
||||
clients, support bundles, diagnostics observers, approval prompts, or agent
|
||||
|
||||
@@ -11,8 +11,9 @@ title: "Menu bar"
|
||||
|
||||
- We surface the current agent work state in the menu bar icon and in the first status row of the menu.
|
||||
- Health status is hidden while work is active; it returns when all sessions are idle.
|
||||
- The “Nodes” block in the menu lists **devices** only (paired nodes via `node.list`), not client/presence entries.
|
||||
- A “Usage” section appears under Context when provider usage snapshots are available.
|
||||
- A root “Context” submenu contains recent sessions instead of expanding them directly in the root menu.
|
||||
- The “Nodes” block in the root menu lists **devices** only (paired nodes via `node.list`), not client/presence entries.
|
||||
- A root “Usage” section appears below Context when provider usage snapshots are available, followed by usage-cost details when available.
|
||||
|
||||
## State model
|
||||
|
||||
@@ -45,6 +46,14 @@ title: "Menu bar"
|
||||
- `workingOther`: badge with glyph, muted tint, no scurry.
|
||||
- `overridden`: uses the chosen glyph/tint regardless of activity.
|
||||
|
||||
## Context submenu
|
||||
|
||||
- The root menu shows one “Context” row with a session count/status and opens a submenu.
|
||||
- The Context submenu header shows the active session count for the last 24 hours.
|
||||
- Each session row keeps its token bar, age, preview, thinking/verbose, reset, compact, and delete actions.
|
||||
- Loading, disconnected, and session-load error messages appear inside the Context submenu.
|
||||
- Provider usage and usage-cost details stay root-level below Context so they remain glanceable without opening the submenu.
|
||||
|
||||
## Status row text (menu)
|
||||
|
||||
- While work is active: `<Session role> · <activity label>`
|
||||
|
||||
@@ -253,6 +253,47 @@ Users enable optional tools in config:
|
||||
- Use `optional: true` for tools with side effects or extra binary requirements
|
||||
- Users can enable all tools from a plugin by adding the plugin id to `tools.allow`
|
||||
|
||||
## Registering CLI commands
|
||||
|
||||
Plugins can add root `openclaw` command groups with `api.registerCli`. Provide
|
||||
`descriptors` for every top-level command root so OpenClaw can show and route
|
||||
the command without eagerly loading every plugin runtime.
|
||||
|
||||
```typescript
|
||||
register(api) {
|
||||
api.registerCli(
|
||||
({ program }) => {
|
||||
const demo = program
|
||||
.command("demo-plugin")
|
||||
.description("Run demo plugin commands");
|
||||
|
||||
demo
|
||||
.command("ping")
|
||||
.description("Check that the plugin CLI is executable")
|
||||
.action(() => {
|
||||
console.log("demo-plugin:pong");
|
||||
});
|
||||
},
|
||||
{
|
||||
descriptors: [
|
||||
{
|
||||
name: "demo-plugin",
|
||||
description: "Run demo plugin commands",
|
||||
hasSubcommands: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
After install, verify the runtime registration and execute the command:
|
||||
|
||||
```bash
|
||||
openclaw plugins inspect demo-plugin --runtime --json
|
||||
openclaw demo-plugin ping
|
||||
```
|
||||
|
||||
## Import conventions
|
||||
|
||||
Always import from focused `openclaw/plugin-sdk/<subpath>` paths:
|
||||
|
||||
@@ -15,6 +15,13 @@ discovery, native thread resume, native compaction, and app-server execution.
|
||||
OpenClaw still owns chat channels, session files, model selection, tools,
|
||||
approvals, media delivery, and the visible transcript mirror.
|
||||
|
||||
When a source chat turn runs through the Codex harness, visible replies default
|
||||
to the OpenClaw `message` tool if the deployment has not explicitly configured
|
||||
`messages.visibleReplies`. The agent can still finish its Codex turn privately;
|
||||
it only posts to the channel when it calls `message(action="send")`. Set
|
||||
`messages.visibleReplies: "automatic"` to keep direct-chat final replies on the
|
||||
legacy automatic delivery path.
|
||||
|
||||
If you are trying to orient yourself, start with
|
||||
[Agent runtimes](/concepts/agent-runtimes). The short version is:
|
||||
`openai/gpt-5.5` is the model ref, `codex` is the runtime, and Telegram,
|
||||
@@ -579,6 +586,19 @@ If a deployment needs additional environment isolation, add those variables to
|
||||
|
||||
`appServer.clearEnv` only affects the spawned Codex app-server child process.
|
||||
|
||||
Codex dynamic tools default to the `native-first` profile. In that mode,
|
||||
OpenClaw does not expose dynamic tools that duplicate Codex-native workspace
|
||||
operations: `read`, `write`, `edit`, `apply_patch`, `exec`, `process`, and
|
||||
`update_plan`. OpenClaw integration tools such as messaging, sessions, media,
|
||||
cron, browser, nodes, gateway, and `web_search` remain available.
|
||||
|
||||
Supported top-level Codex plugin fields:
|
||||
|
||||
| Field | Default | Meaning |
|
||||
| -------------------------- | ---------------- | ----------------------------------------------------------------------------------------- |
|
||||
| `codexDynamicToolsProfile` | `"native-first"` | Use `"openclaw-compat"` to expose the full OpenClaw dynamic tool set to Codex app-server. |
|
||||
| `codexDynamicToolsExclude` | `[]` | Additional OpenClaw dynamic tool names to omit from Codex app-server turns. |
|
||||
|
||||
Supported `appServer` fields:
|
||||
|
||||
| Field | Default | Meaning |
|
||||
|
||||
214
docs/plugins/dependency-resolution.md
Normal file
214
docs/plugins/dependency-resolution.md
Normal file
@@ -0,0 +1,214 @@
|
||||
---
|
||||
summary: "How OpenClaw plans, stages, and repairs bundled plugin runtime dependencies"
|
||||
read_when:
|
||||
- You are debugging bundled plugin runtime dependency repair
|
||||
- You are changing plugin startup, doctor, or package-manager install behavior
|
||||
- You are maintaining packaged OpenClaw installs or bundled plugin manifests
|
||||
title: "Plugin dependency resolution"
|
||||
sidebarTitle: "Dependencies"
|
||||
---
|
||||
|
||||
OpenClaw does not install every bundled plugin dependency tree at package install
|
||||
time. It first derives an effective plugin plan from config and plugin metadata,
|
||||
then stages runtime dependencies only for bundled OpenClaw-owned plugins that
|
||||
the plan can actually load.
|
||||
|
||||
This page covers packaged runtime dependencies for bundled OpenClaw plugins.
|
||||
Third-party plugins and custom plugin paths still use explicit plugin
|
||||
installation commands such as `openclaw plugins install` and
|
||||
`openclaw plugins update`.
|
||||
|
||||
## Responsibility split
|
||||
|
||||
OpenClaw owns the plan and policy:
|
||||
|
||||
- which plugins are active for this config
|
||||
- which dependency roots are writable or read-only
|
||||
- when repair is allowed
|
||||
- which plugin ids are staged for startup
|
||||
- final checks before importing plugin runtime modules
|
||||
|
||||
The package manager owns dependency convergence:
|
||||
|
||||
- package graph resolution
|
||||
- production, optional, and peer dependency handling
|
||||
- `node_modules` layout
|
||||
- package integrity
|
||||
- lock and install metadata
|
||||
|
||||
In practice, OpenClaw should decide what needs to exist. `pnpm` or `npm` should
|
||||
make the filesystem match that decision.
|
||||
|
||||
OpenClaw also owns the per-install-root coordination lock. Package managers
|
||||
protect their own install transaction, but they do not serialize OpenClaw's
|
||||
manifest writes, isolated-stage copy/rename, final validation, or plugin import
|
||||
against another Gateway, doctor, or CLI process touching the same runtime
|
||||
dependency root.
|
||||
|
||||
## Effective plugin plan
|
||||
|
||||
The effective plugin plan is derived from config plus discovered plugin
|
||||
metadata. These inputs can activate bundled plugin runtime dependencies:
|
||||
|
||||
- `plugins.entries.<id>.enabled`
|
||||
- `plugins.allow`, `plugins.deny`, and `plugins.enabled`
|
||||
- legacy channel config such as `channels.telegram.enabled`
|
||||
- configured providers, models, or CLI backend references that require a plugin
|
||||
- bundled manifest defaults such as `enabledByDefault`
|
||||
- the installed plugin index and bundled manifest metadata
|
||||
|
||||
Explicit disablement wins. A disabled plugin, denied plugin id, disabled plugin
|
||||
system, or disabled channel does not trigger runtime dependency repair. Persisted
|
||||
auth state alone also does not activate a bundled channel or provider.
|
||||
|
||||
The plugin plan is the stable input. The generated dependency materialization is
|
||||
an output of that plan.
|
||||
|
||||
## Startup flow
|
||||
|
||||
Gateway startup parses config and builds the startup plugin lookup table before
|
||||
plugin runtime modules are loaded. Startup then stages runtime dependencies only
|
||||
for the `startupPluginIds` selected by that plan.
|
||||
|
||||
For packaged installs, dependency staging is allowed before plugin import. After
|
||||
staging, the runtime loader imports startup plugins with install repair disabled;
|
||||
at that point missing dependency materialization is treated as a load failure,
|
||||
not another repair loop.
|
||||
|
||||
When startup dependency staging is deferred behind the HTTP bind, Gateway
|
||||
readiness stays blocked on the `plugin-runtime-deps` reason until the selected
|
||||
startup plugin dependencies are materialized and the startup plugin runtime has
|
||||
loaded.
|
||||
|
||||
## When repair runs
|
||||
|
||||
Runtime dependency repair should run when one of these is true:
|
||||
|
||||
- the effective plugin plan changed and adds bundled plugins that need runtime
|
||||
dependencies
|
||||
- the generated dependency manifest no longer matches the effective plan
|
||||
- expected installed package sentinels are missing or incomplete
|
||||
- `openclaw doctor --fix` or `openclaw plugins deps --repair` was requested
|
||||
|
||||
Runtime dependency repair should not run just because OpenClaw started. A normal
|
||||
startup with an unchanged plan and complete dependency materialization should
|
||||
skip package-manager work.
|
||||
|
||||
Commands that edit config, enable plugins, or repair doctor findings can enter
|
||||
plugin plan mode once, materialize the newly required bundled dependencies, then
|
||||
return to the normal command flow. Local `openclaw onboard` and
|
||||
`openclaw configure` do this automatically after they successfully write config,
|
||||
so the next Gateway run does not discover missing bundled plugin packages after
|
||||
startup has already begun. Remote onboarding/configure stays read-only for local
|
||||
runtime deps.
|
||||
|
||||
## Hot reload rule
|
||||
|
||||
Hot reload paths that can change active plugins must go back through plugin plan
|
||||
mode before loading plugin runtime. The reload should compare the new effective
|
||||
plugin plan with the previous one, stage missing dependencies for newly active
|
||||
bundled plugins, then load or restart the affected runtime.
|
||||
|
||||
If a config reload does not change the effective plugin plan, it should not
|
||||
repair bundled runtime dependencies.
|
||||
|
||||
## Package manager execution
|
||||
|
||||
OpenClaw writes a generated install manifest for the selected bundled runtime
|
||||
dependencies and runs the package manager in the runtime dependency install
|
||||
root. It prefers `pnpm` when available and falls back to the Node-bundled `npm`
|
||||
runner.
|
||||
|
||||
The `pnpm` path uses production dependencies, disables lifecycle scripts, ignores
|
||||
the workspace, and keeps the store inside the install root:
|
||||
|
||||
```bash
|
||||
pnpm install \
|
||||
--prod \
|
||||
--ignore-scripts \
|
||||
--ignore-workspace \
|
||||
--config.frozen-lockfile=false \
|
||||
--config.minimum-release-age=0 \
|
||||
--config.store-dir=<install-root>/.openclaw-pnpm-store \
|
||||
--config.node-linker=hoisted \
|
||||
--config.virtual-store-dir=.pnpm
|
||||
```
|
||||
|
||||
The `npm` fallback uses the safe npm install wrapper with production
|
||||
dependencies, lifecycle scripts disabled, workspace mode disabled, audit
|
||||
disabled, fund output disabled, legacy peer dependency behavior, and package-lock
|
||||
output enabled for the generated install root.
|
||||
|
||||
After install, OpenClaw validates the staged dependency tree before making it
|
||||
visible to the runtime dependency root. Isolated staging is copied into the
|
||||
runtime dependency root and validated again.
|
||||
|
||||
The whole repair/materialization section is guarded by an install-root lock.
|
||||
Current lock owners record PID, process start-time when available, and creation
|
||||
time. Legacy locks without process start-time or creation-time evidence are only
|
||||
reclaimed by filesystem age, so recycled Docker PID 1 locks recover without
|
||||
expiring normal long-running current installs by age alone.
|
||||
|
||||
## Install roots
|
||||
|
||||
Packaged installs must not mutate read-only package directories. OpenClaw can
|
||||
read dependency roots from packaged layers, but writes generated runtime
|
||||
dependencies to a writable stage such as:
|
||||
|
||||
- `OPENCLAW_PLUGIN_STAGE_DIR`
|
||||
- `$STATE_DIRECTORY`
|
||||
- `~/.openclaw/plugin-runtime-deps`
|
||||
- `/var/lib/openclaw/plugin-runtime-deps` in container-style installs
|
||||
|
||||
The writable root is the final materialization target. Older read-only roots are
|
||||
kept as compatibility layers only when needed.
|
||||
|
||||
When a packaged OpenClaw update changes the versioned writable root but the
|
||||
selected bundled-plugin dependency plan is still satisfied by a previous staged
|
||||
root, repair reuses that previous `node_modules` tree instead of running the
|
||||
package manager again. The new versioned root still gets its own current package
|
||||
runtime mirror, so plugin code comes from the current OpenClaw package while
|
||||
unchanged dependency trees are shared across updates. Reuse skips previous roots
|
||||
with an active OpenClaw runtime-dependency lock, so a new root does not link to a
|
||||
dependency tree that another Gateway, doctor, or CLI process is currently
|
||||
repairing.
|
||||
|
||||
## Doctor and CLI commands
|
||||
|
||||
Use `plugins deps` to inspect or repair bundled plugin runtime dependency
|
||||
materialization:
|
||||
|
||||
```bash
|
||||
openclaw plugins deps
|
||||
openclaw plugins deps --json
|
||||
openclaw plugins deps --repair
|
||||
openclaw plugins deps --prune
|
||||
```
|
||||
|
||||
Use doctor when the dependency state is part of broader install health:
|
||||
|
||||
```bash
|
||||
openclaw doctor
|
||||
openclaw doctor --fix
|
||||
```
|
||||
|
||||
`plugins deps` and doctor operate on OpenClaw-owned bundled plugin runtime
|
||||
dependencies selected by the effective plugin plan. They are not third-party
|
||||
plugin install or update commands.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If a packaged install reports missing bundled runtime dependencies:
|
||||
|
||||
1. Run `openclaw plugins deps --json` to inspect the selected plan and missing
|
||||
packages.
|
||||
2. Run `openclaw plugins deps --repair` or `openclaw doctor --fix` to repair the
|
||||
writable dependency stage.
|
||||
3. If the install root is read-only, set `OPENCLAW_PLUGIN_STAGE_DIR` to a
|
||||
writable path and rerun repair.
|
||||
4. Restart Gateway after repair if the missing dependency blocked startup plugin
|
||||
loading.
|
||||
|
||||
In source checkouts, the workspace install usually provides bundled plugin
|
||||
dependencies. Run `pnpm install` for source dependency repair instead of using
|
||||
packaged runtime dependency repair as the first step.
|
||||
@@ -85,11 +85,22 @@ openclaw googlemeet setup --transport chrome-node --mode transcribe
|
||||
```
|
||||
|
||||
When Twilio delegation is configured, setup also reports whether the
|
||||
`voice-call` plugin and Twilio credentials are ready. Treat any `ok: false`
|
||||
check as a blocker for the checked transport and mode before asking an agent to
|
||||
join. Use `openclaw googlemeet setup --json` for scripts or machine-readable
|
||||
output. Use `--transport chrome`, `--transport chrome-node`, or `--transport twilio`
|
||||
to preflight a specific transport before an agent tries it.
|
||||
`voice-call` plugin, Twilio credentials, and public webhook exposure are ready.
|
||||
Treat any `ok: false` check as a blocker for the checked transport and mode
|
||||
before asking an agent to join. Use `openclaw googlemeet setup --json` for
|
||||
scripts or machine-readable output. Use `--transport chrome`,
|
||||
`--transport chrome-node`, or `--transport twilio` to preflight a specific
|
||||
transport before an agent tries it.
|
||||
|
||||
For Twilio, always preflight the transport explicitly when the default transport
|
||||
is Chrome:
|
||||
|
||||
```bash
|
||||
openclaw googlemeet setup --transport twilio
|
||||
```
|
||||
|
||||
That catches missing `voice-call` wiring, Twilio credentials, or unreachable
|
||||
webhook exposure before the agent tries to dial the meeting.
|
||||
|
||||
Join a meeting:
|
||||
|
||||
@@ -158,7 +169,12 @@ and will not talk back into the meeting. Chrome joins in this mode also avoid
|
||||
OpenClaw's microphone/camera permission grant and avoid the Meet **Use
|
||||
microphone** path. If Meet shows an audio-choice interstitial, automation tries
|
||||
the no-microphone path and otherwise reports a manual action instead of opening
|
||||
the local microphone.
|
||||
the local microphone. In transcribe mode, managed Chrome transports also install
|
||||
a best-effort Meet caption observer. `googlemeet status --json` and
|
||||
`googlemeet doctor` surface `captioning`, `captionsEnabledAttempted`,
|
||||
`transcriptLines`, `lastCaptionAt`, `lastCaptionSpeaker`, `lastCaptionText`,
|
||||
and a short `recentTranscript` tail so operators can tell whether the browser
|
||||
joined the call and whether Meet captions are producing text.
|
||||
|
||||
During realtime sessions, `google_meet` status includes browser and audio bridge
|
||||
health such as `inCall`, `manualActionRequired`, `providerConnected`,
|
||||
@@ -439,7 +455,8 @@ openclaw googlemeet setup
|
||||
```
|
||||
|
||||
When Twilio delegation is wired, `googlemeet setup` includes successful
|
||||
`twilio-voice-call-plugin` and `twilio-voice-call-credentials` checks.
|
||||
`twilio-voice-call-plugin`, `twilio-voice-call-credentials`, and
|
||||
`twilio-voice-call-webhook` checks.
|
||||
|
||||
```bash
|
||||
openclaw googlemeet join https://meet.google.com/abc-defg-hij \
|
||||
@@ -912,6 +929,16 @@ Defaults:
|
||||
and writing audio in `chrome.audioFormat`
|
||||
- `chrome.audioOutputCommand`: SoX command reading audio in `chrome.audioFormat`
|
||||
and writing to CoreAudio `BlackHole 2ch`
|
||||
- `chrome.bargeInInputCommand`: optional local microphone command that writes
|
||||
signed 16-bit little-endian mono PCM for human barge-in detection while
|
||||
assistant playback is active. This currently applies to the Gateway-hosted
|
||||
`chrome` command-pair bridge.
|
||||
- `chrome.bargeInRmsThreshold: 650`: RMS level that counts as a human
|
||||
interruption on `chrome.bargeInInputCommand`
|
||||
- `chrome.bargeInPeakThreshold: 2500`: peak level that counts as a human
|
||||
interruption on `chrome.bargeInInputCommand`
|
||||
- `chrome.bargeInCooldownMs: 900`: minimum delay between repeated human
|
||||
interruption clears
|
||||
- `realtime.provider: "openai"`
|
||||
- `realtime.toolPolicy: "safe-read-only"`
|
||||
- `realtime.instructions`: brief spoken replies, with
|
||||
@@ -934,6 +961,24 @@ Optional overrides:
|
||||
chrome: {
|
||||
guestName: "OpenClaw Agent",
|
||||
waitForInCallMs: 30000,
|
||||
bargeInInputCommand: [
|
||||
"sox",
|
||||
"-q",
|
||||
"-t",
|
||||
"coreaudio",
|
||||
"External Microphone",
|
||||
"-r",
|
||||
"24000",
|
||||
"-c",
|
||||
"1",
|
||||
"-b",
|
||||
"16",
|
||||
"-e",
|
||||
"signed-integer",
|
||||
"-t",
|
||||
"raw",
|
||||
"-",
|
||||
],
|
||||
},
|
||||
chromeNode: {
|
||||
node: "parallels-macos",
|
||||
@@ -969,7 +1014,9 @@ Twilio-only config:
|
||||
```
|
||||
|
||||
`voiceCall.enabled` defaults to `true`; with Twilio transport it delegates the
|
||||
actual PSTN call and DTMF to the Voice Call plugin. If `voice-call` is not
|
||||
actual PSTN call, DTMF, and intro greeting to the Voice Call plugin. Voice Call
|
||||
plays the DTMF sequence before opening the realtime media stream, then uses the
|
||||
saved intro text as the initial realtime greeting. If `voice-call` is not
|
||||
enabled, Google Meet can still validate and record the dial plan, but it cannot
|
||||
place the Twilio call.
|
||||
|
||||
@@ -1014,6 +1061,8 @@ a session ended.
|
||||
not send the intro/test phrase into the audio bridge.
|
||||
- `providerConnected` / `realtimeReady`: realtime voice bridge state
|
||||
- `lastInputAt` / `lastOutputAt`: last audio seen from or sent to the bridge
|
||||
- `lastSuppressedInputAt` / `suppressedInputBytes`: loopback input ignored while
|
||||
assistant playback is active
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -1115,10 +1164,12 @@ openclaw googlemeet join https://meet.google.com/abc-defg-hij \
|
||||
|
||||
Expected Twilio state:
|
||||
|
||||
- `googlemeet setup` includes green `twilio-voice-call-plugin` and
|
||||
`twilio-voice-call-credentials` checks.
|
||||
- `googlemeet setup` includes green `twilio-voice-call-plugin`,
|
||||
`twilio-voice-call-credentials`, and `twilio-voice-call-webhook` checks.
|
||||
- `voicecall` is available in the CLI after Gateway reload.
|
||||
- The returned session has `transport: "twilio"` and a `twilio.voiceCallId`.
|
||||
- `openclaw logs --follow` shows DTMF TwiML served before realtime TwiML, then a
|
||||
realtime bridge with the initial greeting queued.
|
||||
- `googlemeet leave <sessionId>` hangs up the delegated voice call.
|
||||
|
||||
## Troubleshooting
|
||||
@@ -1248,9 +1299,15 @@ openclaw googlemeet doctor
|
||||
```
|
||||
|
||||
Use `mode: "realtime"` for listen/talk-back. `mode: "transcribe"` intentionally
|
||||
does not start the duplex realtime voice bridge. `googlemeet test-speech`
|
||||
always checks the realtime path and reports whether bridge output bytes were
|
||||
observed for that invocation. If `speechOutputVerified` is false and
|
||||
does not start the duplex realtime voice bridge. For observe-only debugging,
|
||||
run `openclaw googlemeet status --json <session-id>` after participants speak
|
||||
and check `captioning`, `transcriptLines`, and `lastCaptionText`. If `inCall` is
|
||||
true but `transcriptLines` stays at `0`, Meet captions may be disabled, no one
|
||||
has spoken since the observer was installed, the Meet UI changed, or live
|
||||
captions are unavailable for the meeting language/account.
|
||||
|
||||
`googlemeet test-speech` always checks the realtime path and reports whether
|
||||
bridge output bytes were observed for that invocation. If `speechOutputVerified` is false and
|
||||
`speechOutputTimedOut` is true, the realtime provider may have accepted the
|
||||
utterance but OpenClaw did not see new output bytes reach the Chrome audio
|
||||
bridge.
|
||||
@@ -1267,7 +1324,7 @@ Also verify:
|
||||
`googlemeet doctor [session-id]` prints the session, node, in-call state,
|
||||
manual action reason, realtime provider connection, `realtimeReady`, audio
|
||||
input/output activity, last audio timestamps, byte counters, and browser URL.
|
||||
Use `googlemeet status [session-id]` when you need the raw JSON. Use
|
||||
Use `googlemeet status [session-id] --json` when you need the raw JSON. Use
|
||||
`googlemeet doctor --oauth` when you need to verify Google Meet OAuth refresh
|
||||
without exposing tokens; add `--meeting` or `--create-space` when you need a
|
||||
Google Meet API proof as well.
|
||||
@@ -1303,10 +1360,57 @@ export TWILIO_AUTH_TOKEN=...
|
||||
export TWILIO_FROM_NUMBER=+15550001234
|
||||
```
|
||||
|
||||
`twilio-voice-call-webhook` fails when `voice-call` has no public webhook
|
||||
exposure, or when `publicUrl` points at loopback or private network space.
|
||||
Set `plugins.entries.voice-call.config.publicUrl` to the public provider URL or
|
||||
configure a `voice-call` tunnel/Tailscale exposure.
|
||||
|
||||
Loopback and private URLs are not valid for carrier callbacks. Do not use
|
||||
`localhost`, `127.0.0.1`, `0.0.0.0`, `10.x`, `172.16.x`-`172.31.x`,
|
||||
`192.168.x`, `169.254.x`, `fc00::/7`, or `fd00::/8` as `publicUrl`.
|
||||
|
||||
For a stable public URL:
|
||||
|
||||
```json5
|
||||
{
|
||||
plugins: {
|
||||
entries: {
|
||||
"voice-call": {
|
||||
enabled: true,
|
||||
config: {
|
||||
provider: "twilio",
|
||||
fromNumber: "+15550001234",
|
||||
publicUrl: "https://voice.example.com/voice/webhook",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
For local development, use a tunnel or Tailscale exposure instead of a private
|
||||
host URL:
|
||||
|
||||
```json5
|
||||
{
|
||||
plugins: {
|
||||
entries: {
|
||||
"voice-call": {
|
||||
config: {
|
||||
tunnel: { provider: "ngrok" },
|
||||
// or
|
||||
tailscale: { mode: "funnel", path: "/voice/webhook" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Then restart or reload the Gateway and run:
|
||||
|
||||
```bash
|
||||
openclaw googlemeet setup
|
||||
openclaw googlemeet setup --transport twilio
|
||||
openclaw voicecall setup
|
||||
openclaw voicecall smoke
|
||||
```
|
||||
@@ -1339,6 +1443,34 @@ openclaw googlemeet join https://meet.google.com/abc-defg-hij \
|
||||
Use leading `w` or commas in `--dtmf-sequence` if the provider needs a pause
|
||||
before entering the PIN.
|
||||
|
||||
If the phone call is created but the Meet roster never shows the dial-in
|
||||
participant:
|
||||
|
||||
- Run `openclaw googlemeet doctor <session-id>` to confirm the delegated Twilio
|
||||
call ID, whether DTMF was queued, and whether the intro greeting was requested.
|
||||
- Run `openclaw voicecall status --call-id <id>` and confirm the call is still
|
||||
active.
|
||||
- Run `openclaw voicecall tail` and check that Twilio webhooks are arriving at
|
||||
the Gateway.
|
||||
- Run `openclaw logs --follow` and look for the Twilio Meet sequence: Google
|
||||
Meet delegates the join, Voice Call stores pre-connect DTMF TwiML, serves
|
||||
that initial TwiML, then serves realtime TwiML and starts the realtime bridge
|
||||
with `initialGreeting=queued`.
|
||||
- Re-run `openclaw googlemeet setup --transport twilio`; a green setup check is
|
||||
required but does not prove the meeting PIN sequence is correct.
|
||||
- Confirm the dial-in number belongs to the same Meet invitation and region as
|
||||
the PIN.
|
||||
- Increase the leading pauses in `--dtmf-sequence` if Meet answers slowly, for
|
||||
example `wwww123456#`.
|
||||
- If the participant joins but you do not hear the greeting, check
|
||||
`openclaw logs --follow` for realtime TwiML, realtime bridge startup, and
|
||||
`initialGreeting=queued`. The greeting is generated from the initial
|
||||
`voicecall.start` message after the realtime bridge connects.
|
||||
|
||||
If webhooks do not arrive, debug the Voice Call plugin first: the provider must
|
||||
reach `plugins.entries.voice-call.config.publicUrl` or the configured tunnel.
|
||||
See [Voice call troubleshooting](/plugins/voice-call#troubleshooting).
|
||||
|
||||
## Notes
|
||||
|
||||
Google Meet's official media API is receive-oriented, so speaking into a Meet
|
||||
@@ -1359,6 +1491,14 @@ For clean duplex audio, route Meet output and Meet microphone through separate
|
||||
virtual devices or a Loopback-style virtual device graph. A single shared
|
||||
BlackHole device can echo other participants back into the call.
|
||||
|
||||
With the command-pair Chrome bridge, `chrome.bargeInInputCommand` can listen to a
|
||||
separate local microphone and clear assistant playback when the human starts
|
||||
talking. This keeps human speech ahead of assistant output even when the shared
|
||||
BlackHole loopback input is temporarily suppressed during assistant playback.
|
||||
Like `chrome.audioInputCommand` and `chrome.audioOutputCommand`, it is an
|
||||
operator-configured local command. Use an explicit trusted command path or
|
||||
argument list, and do not point it at scripts from untrusted locations.
|
||||
|
||||
`googlemeet speak` triggers the active realtime audio bridge for a Chrome
|
||||
session. `googlemeet leave` stops that bridge. For Twilio sessions delegated
|
||||
through the Voice Call plugin, `leave` also hangs up the underlying voice call.
|
||||
|
||||
@@ -593,6 +593,7 @@ API key auth, and dynamic model resolution.
|
||||
connect: async () => {},
|
||||
sendAudio: () => {},
|
||||
setMediaTimestamp: () => {},
|
||||
handleBargeIn: () => {},
|
||||
submitToolResult: () => {},
|
||||
acknowledgeMark: () => {},
|
||||
close: () => {},
|
||||
@@ -600,6 +601,10 @@ API key auth, and dynamic model resolution.
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
Implement `handleBargeIn` when a transport can detect that a human is
|
||||
interrupting assistant playback and the provider supports truncating or
|
||||
clearing the active audio response.
|
||||
</Tab>
|
||||
<Tab title="Media understanding">
|
||||
```typescript
|
||||
|
||||
@@ -96,7 +96,7 @@ skips starting the runtime. Commands, RPC calls, and agent tools still
|
||||
return the exact missing provider configuration when used.
|
||||
|
||||
<Note>
|
||||
Voice-call credentials accept SecretRefs. `plugins.entries.voice-call.config.twilio.authToken` and `plugins.entries.voice-call.config.tts.providers.*.apiKey` resolve through the standard SecretRef surface; see [SecretRef credential surface](/reference/secretref-credential-surface).
|
||||
Voice-call credentials accept SecretRefs. `plugins.entries.voice-call.config.twilio.authToken`, `plugins.entries.voice-call.config.realtime.providers.*.apiKey`, `plugins.entries.voice-call.config.streaming.providers.*.apiKey`, and `plugins.entries.voice-call.config.tts.providers.*.apiKey` resolve through the standard SecretRef surface; see [SecretRef credential surface](/reference/secretref-credential-surface).
|
||||
</Note>
|
||||
|
||||
```json5
|
||||
@@ -210,6 +210,7 @@ Current runtime behaviour:
|
||||
- Bundled realtime voice providers: Google Gemini Live (`google`) and OpenAI (`openai`), registered by their provider plugins.
|
||||
- Provider-owned raw config lives under `realtime.providers.<providerId>`.
|
||||
- Voice Call exposes the shared `openclaw_agent_consult` realtime tool by default. The realtime model can call it when the caller asks for deeper reasoning, current information, or normal OpenClaw tools.
|
||||
- `realtime.fastContext.enabled` is default-off. When enabled, Voice Call first searches indexed memory/session context for the consult question and returns those snippets to the realtime model within `realtime.fastContext.timeoutMs` before falling back to the full consult agent only if `realtime.fastContext.fallbackToConsult` is true.
|
||||
- If `realtime.provider` points at an unregistered provider, or no realtime voice provider is registered at all, Voice Call logs a warning and skips realtime media instead of failing the whole plugin.
|
||||
- Consult session keys reuse the existing voice session when available, then fall back to the caller/callee phone number so follow-up consult calls keep context during the call.
|
||||
|
||||
@@ -297,6 +298,7 @@ Current runtime behavior:
|
||||
- `streaming.provider` is optional. If unset, Voice Call uses the first registered realtime transcription provider.
|
||||
- Bundled realtime transcription providers: Deepgram (`deepgram`), ElevenLabs (`elevenlabs`), Mistral (`mistral`), OpenAI (`openai`), and xAI (`xai`), registered by their provider plugins.
|
||||
- Provider-owned raw config lives under `streaming.providers.<providerId>`.
|
||||
- After Twilio sends an accepted stream `start` message, Voice Call registers the stream immediately, queues inbound media through the transcription provider while the provider connects, and starts the initial greeting only after realtime transcription is ready.
|
||||
- If `streaming.provider` points at an unregistered provider, or none is registered, Voice Call logs a warning and skips media streaming instead of failing the whole plugin.
|
||||
|
||||
### Streaming provider examples
|
||||
@@ -609,6 +611,11 @@ openclaw voicecall latency # summarize turn latency from lo
|
||||
openclaw voicecall expose --mode funnel
|
||||
```
|
||||
|
||||
When the Gateway is already running, operational `voicecall` commands delegate
|
||||
to the Gateway-owned voice-call runtime so the CLI does not bind a second
|
||||
webhook server. If no Gateway is reachable, the commands fall back to a
|
||||
standalone CLI runtime.
|
||||
|
||||
`latency` reads `calls.jsonl` from the default voice-call storage path.
|
||||
Use `--file <path>` to point at a different log and `--last <n>` to limit
|
||||
analysis to the last N records (default 200). Output includes p50/p90/p99
|
||||
@@ -618,27 +625,189 @@ for turn latency and listen-wait times.
|
||||
|
||||
Tool name: `voice_call`.
|
||||
|
||||
| Action | Args |
|
||||
| --------------- | ------------------------- |
|
||||
| `initiate_call` | `message`, `to?`, `mode?` |
|
||||
| `continue_call` | `callId`, `message` |
|
||||
| `speak_to_user` | `callId`, `message` |
|
||||
| `send_dtmf` | `callId`, `digits` |
|
||||
| `end_call` | `callId` |
|
||||
| `get_status` | `callId` |
|
||||
| Action | Args |
|
||||
| --------------- | ------------------------------------------ |
|
||||
| `initiate_call` | `message`, `to?`, `mode?`, `dtmfSequence?` |
|
||||
| `continue_call` | `callId`, `message` |
|
||||
| `speak_to_user` | `callId`, `message` |
|
||||
| `send_dtmf` | `callId`, `digits` |
|
||||
| `end_call` | `callId` |
|
||||
| `get_status` | `callId` |
|
||||
|
||||
This repo ships a matching skill doc at `skills/voice-call/SKILL.md`.
|
||||
|
||||
## Gateway RPC
|
||||
|
||||
| Method | Args |
|
||||
| -------------------- | ------------------------- |
|
||||
| `voicecall.initiate` | `to?`, `message`, `mode?` |
|
||||
| `voicecall.continue` | `callId`, `message` |
|
||||
| `voicecall.speak` | `callId`, `message` |
|
||||
| `voicecall.dtmf` | `callId`, `digits` |
|
||||
| `voicecall.end` | `callId` |
|
||||
| `voicecall.status` | `callId` |
|
||||
| Method | Args |
|
||||
| -------------------- | ------------------------------------------ |
|
||||
| `voicecall.initiate` | `to?`, `message`, `mode?`, `dtmfSequence?` |
|
||||
| `voicecall.continue` | `callId`, `message` |
|
||||
| `voicecall.speak` | `callId`, `message` |
|
||||
| `voicecall.dtmf` | `callId`, `digits` |
|
||||
| `voicecall.end` | `callId` |
|
||||
| `voicecall.status` | `callId` |
|
||||
|
||||
`dtmfSequence` is only valid with `mode: "conversation"`. Notify-mode calls
|
||||
should use `voicecall.dtmf` after the call exists if they need post-connect
|
||||
digits.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Setup fails webhook exposure
|
||||
|
||||
Run setup from the same environment that runs the Gateway:
|
||||
|
||||
```bash
|
||||
openclaw voicecall setup
|
||||
openclaw voicecall setup --json
|
||||
```
|
||||
|
||||
For `twilio`, `telnyx`, and `plivo`, `webhook-exposure` must be green. A
|
||||
configured `publicUrl` still fails when it points at local or private network
|
||||
space, because the carrier cannot call back into those addresses. Do not use
|
||||
`localhost`, `127.0.0.1`, `0.0.0.0`, `10.x`, `172.16.x`-`172.31.x`,
|
||||
`192.168.x`, `169.254.x`, `fc00::/7`, or `fd00::/8` as `publicUrl`.
|
||||
|
||||
Twilio notify-mode outbound calls send their initial `<Say>` TwiML directly in
|
||||
the create-call request, so the first spoken message does not depend on Twilio
|
||||
fetching webhook TwiML. A public webhook is still required for status callbacks,
|
||||
conversation calls, pre-connect DTMF, realtime streams, and post-connect call
|
||||
control.
|
||||
|
||||
Use one public exposure path:
|
||||
|
||||
```json5
|
||||
{
|
||||
plugins: {
|
||||
entries: {
|
||||
"voice-call": {
|
||||
config: {
|
||||
publicUrl: "https://voice.example.com/voice/webhook",
|
||||
// or
|
||||
tunnel: { provider: "ngrok" },
|
||||
// or
|
||||
tailscale: { mode: "funnel", path: "/voice/webhook" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
After changing config, restart or reload the Gateway, then run:
|
||||
|
||||
```bash
|
||||
openclaw voicecall setup
|
||||
openclaw voicecall smoke
|
||||
```
|
||||
|
||||
`voicecall smoke` is a dry run unless you pass `--yes`.
|
||||
|
||||
### Provider credentials fail
|
||||
|
||||
Check the selected provider and the required credential fields:
|
||||
|
||||
- Twilio: `twilio.accountSid`, `twilio.authToken`, and `fromNumber`, or
|
||||
`TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN`, and `TWILIO_FROM_NUMBER`.
|
||||
- Telnyx: `telnyx.apiKey`, `telnyx.connectionId`, `telnyx.publicKey`, and
|
||||
`fromNumber`.
|
||||
- Plivo: `plivo.authId`, `plivo.authToken`, and `fromNumber`.
|
||||
|
||||
Credentials must exist on the Gateway host. Editing a local shell profile does
|
||||
not affect an already running Gateway until it restarts or reloads its
|
||||
environment.
|
||||
|
||||
### Calls start but provider webhooks do not arrive
|
||||
|
||||
Confirm the provider console points at the exact public webhook URL:
|
||||
|
||||
```text
|
||||
https://voice.example.com/voice/webhook
|
||||
```
|
||||
|
||||
Then inspect runtime state:
|
||||
|
||||
```bash
|
||||
openclaw voicecall status --call-id <id>
|
||||
openclaw voicecall tail
|
||||
openclaw logs --follow
|
||||
```
|
||||
|
||||
Common causes:
|
||||
|
||||
- `publicUrl` points at a different path than `serve.path`.
|
||||
- The tunnel URL changed after the Gateway started.
|
||||
- A proxy forwards the request but strips or rewrites host/proto headers.
|
||||
- Firewall or DNS routes the public hostname somewhere other than the Gateway.
|
||||
- The Gateway was restarted without the Voice Call plugin enabled.
|
||||
|
||||
When a reverse proxy or tunnel is in front of the Gateway, set
|
||||
`webhookSecurity.allowedHosts` to the public hostname, or use
|
||||
`webhookSecurity.trustedProxyIPs` for a known proxy address. Use
|
||||
`webhookSecurity.trustForwardingHeaders` only when the proxy boundary is under
|
||||
your control.
|
||||
|
||||
### Signature verification fails
|
||||
|
||||
Provider signatures are checked against the public URL OpenClaw reconstructs
|
||||
from the incoming request. If signatures fail:
|
||||
|
||||
- Confirm the provider webhook URL exactly matches `publicUrl`, including
|
||||
scheme, host, and path.
|
||||
- For ngrok free-tier URLs, update `publicUrl` when the tunnel hostname changes.
|
||||
- Ensure the proxy preserves the original host and proto headers, or configure
|
||||
`webhookSecurity.allowedHosts`.
|
||||
- Do not enable `skipSignatureVerification` outside local testing.
|
||||
|
||||
### Google Meet Twilio joins fail
|
||||
|
||||
Google Meet uses this plugin for Twilio dial-in joins. First verify Voice Call:
|
||||
|
||||
```bash
|
||||
openclaw voicecall setup
|
||||
openclaw voicecall smoke --to "+15555550123"
|
||||
```
|
||||
|
||||
Then verify the Google Meet transport explicitly:
|
||||
|
||||
```bash
|
||||
openclaw googlemeet setup --transport twilio
|
||||
```
|
||||
|
||||
If Voice Call is green but the Meet participant never joins, check the Meet
|
||||
dial-in number, PIN, and `--dtmf-sequence`. The phone call can be healthy while
|
||||
the meeting rejects or ignores an incorrect DTMF sequence.
|
||||
|
||||
Google Meet passes the Meet DTMF sequence and intro text to `voicecall.start`.
|
||||
For Twilio calls, Voice Call serves the DTMF TwiML first, redirects back to the
|
||||
webhook, then opens the realtime media stream so the saved intro is generated
|
||||
after the phone participant has joined the meeting.
|
||||
|
||||
Use `openclaw logs --follow` for the live phase trace. A healthy Twilio Meet
|
||||
join logs this order:
|
||||
|
||||
- Google Meet delegates the Twilio join to Voice Call.
|
||||
- Voice Call stores pre-connect DTMF TwiML.
|
||||
- Twilio initial TwiML is consumed and served before realtime handling.
|
||||
- Voice Call serves realtime TwiML for the Twilio call.
|
||||
- The realtime bridge starts with the initial greeting queued.
|
||||
|
||||
`openclaw voicecall tail` still shows persisted call records; it is useful for
|
||||
call state and transcripts, but not every webhook/realtime transition appears
|
||||
there.
|
||||
|
||||
### Realtime call has no speech
|
||||
|
||||
Confirm only one audio mode is enabled. `realtime.enabled` and
|
||||
`streaming.enabled` cannot both be true.
|
||||
|
||||
For realtime Twilio calls, also verify:
|
||||
|
||||
- A realtime provider plugin is loaded and registered.
|
||||
- `realtime.provider` is unset or names a registered provider.
|
||||
- The provider API key is available to the Gateway process.
|
||||
- `openclaw logs --follow` shows realtime TwiML served, the realtime bridge
|
||||
started, and the initial greeting queued.
|
||||
|
||||
## Related
|
||||
|
||||
|
||||
@@ -55,8 +55,9 @@ to Groq through its OpenAI-compatible API.
|
||||
|
||||
## Built-in catalog
|
||||
|
||||
Groq's model catalog changes frequently. Run `openclaw models list | grep groq`
|
||||
to see currently available models, or check
|
||||
OpenClaw ships a manifest-backed Groq catalog for fast provider-filtered model
|
||||
listing. Run `openclaw models list --all --provider groq` to see the bundled
|
||||
rows, or check
|
||||
[console.groq.com/docs/models](https://console.groq.com/docs/models).
|
||||
|
||||
| Model | Notes |
|
||||
@@ -67,8 +68,8 @@ to see currently available models, or check
|
||||
| **Mixtral 8x7B** | MoE architecture, strong reasoning |
|
||||
|
||||
<Tip>
|
||||
Use `openclaw models list --provider groq` for the most up-to-date list of
|
||||
models available on your account.
|
||||
Use `openclaw models list --all --provider groq` for the manifest-backed Groq
|
||||
rows known to this OpenClaw version.
|
||||
</Tip>
|
||||
|
||||
## Reasoning models
|
||||
|
||||
@@ -103,7 +103,7 @@ openclaw models set venice/claude-opus-4-6
|
||||
List all available models:
|
||||
|
||||
```bash
|
||||
openclaw models list | grep venice
|
||||
openclaw models list --all --provider venice
|
||||
```
|
||||
|
||||
You can also run `openclaw configure`, select **Model/auth**, and choose **Venice AI**.
|
||||
@@ -189,7 +189,7 @@ DeepSeek provider's thinking controls.
|
||||
|
||||
## Model discovery
|
||||
|
||||
OpenClaw automatically discovers models from the Venice API when `VENICE_API_KEY` is set. If the API is unreachable, it falls back to a static catalog.
|
||||
OpenClaw ships a manifest-backed Venice seed catalog for read-only model listing. Runtime refresh can still discover models from the Venice API, and falls back to the manifest catalog if the API is unreachable.
|
||||
|
||||
The `/models` endpoint is public (no auth needed for listing), but inference requires a valid API key.
|
||||
|
||||
|
||||
@@ -34,9 +34,9 @@ with a Z.AI API key.
|
||||
}
|
||||
```
|
||||
</Step>
|
||||
<Step title="Verify the model is available">
|
||||
<Step title="Verify the model is listed">
|
||||
```bash
|
||||
openclaw models list --provider zai
|
||||
openclaw models list --all --provider zai
|
||||
```
|
||||
</Step>
|
||||
</Steps>
|
||||
@@ -70,9 +70,9 @@ with a Z.AI API key.
|
||||
}
|
||||
```
|
||||
</Step>
|
||||
<Step title="Verify the model is available">
|
||||
<Step title="Verify the model is listed">
|
||||
```bash
|
||||
openclaw models list --provider zai
|
||||
openclaw models list --all --provider zai
|
||||
```
|
||||
</Step>
|
||||
</Steps>
|
||||
@@ -82,7 +82,14 @@ with a Z.AI API key.
|
||||
|
||||
## Built-in catalog
|
||||
|
||||
OpenClaw currently seeds the bundled `zai` provider with:
|
||||
OpenClaw ships the bundled `zai` provider catalog in the plugin manifest, so read-only
|
||||
listing can show known GLM rows without loading provider runtime:
|
||||
|
||||
```bash
|
||||
openclaw models list --all --provider zai
|
||||
```
|
||||
|
||||
The manifest-backed catalog currently includes:
|
||||
|
||||
| Model ref | Notes |
|
||||
| -------------------- | ------------- |
|
||||
|
||||
@@ -117,8 +117,11 @@ the maintainer-only release runbook.
|
||||
Actions run. The workflow resolves the candidate to
|
||||
`package-under-test`, reuses the Docker E2E release scheduler against that
|
||||
tarball, and can run Telegram QA against the same tarball with
|
||||
`telegram_mode=mock-openai` or `telegram_mode=live-frontier`.
|
||||
Example: `gh workflow run package-acceptance.yml --ref main -f workflow_ref=main -f source=npm -f package_spec=openclaw@beta -f suite_profile=product -f telegram_mode=mock-openai`
|
||||
`telegram_mode=mock-openai` or `telegram_mode=live-frontier`. When the
|
||||
selected Docker lanes include `published-upgrade-survivor`, the package
|
||||
artifact is the candidate and `published_upgrade_survivor_baseline` selects
|
||||
the published baseline.
|
||||
Example: `gh workflow run package-acceptance.yml --ref main -f workflow_ref=main -f source=npm -f package_spec=openclaw@beta -f suite_profile=product -f published_upgrade_survivor_baseline=openclaw@2026.4.26 -f telegram_mode=mock-openai`
|
||||
Common profiles:
|
||||
- `smoke`: install/channel/agent, gateway network, and config reload lanes
|
||||
- `package`: artifact-native package/update/plugin lanes without OpenWebUI or live ClawHub
|
||||
@@ -240,7 +243,7 @@ gh workflow run full-release-validation.yml \
|
||||
-f ref=release/YYYY.M.D \
|
||||
-f provider=openai \
|
||||
-f mode=both \
|
||||
-f release_profile=full \
|
||||
-f release_profile=stable \
|
||||
-f evidence_package_spec=openclaw@YYYY.M.D-beta.N
|
||||
```
|
||||
|
||||
@@ -255,6 +258,9 @@ summary shows `normal_ci` and `release_checks` as successful, and any optional
|
||||
`npm_telegram` child is either successful or intentionally skipped. The final
|
||||
verifier summary includes slowest-job tables for each child run, so the release
|
||||
manager can see the current critical path without downloading logs.
|
||||
See [Full release validation](/reference/full-release-validation) for the
|
||||
complete stage matrix, exact workflow job names, stable versus full profile
|
||||
differences, artifacts, and focused rerun handles.
|
||||
Child workflows are dispatched from the trusted ref that runs `Full Release
|
||||
Validation`, normally `--ref main`, even when the target `ref` points at an
|
||||
older release branch or tag. There is no separate Full Release Validation
|
||||
@@ -454,7 +460,8 @@ gh workflow run package-acceptance.yml \
|
||||
-f workflow_ref=main \
|
||||
-f source=npm \
|
||||
-f package_spec=openclaw@beta \
|
||||
-f suite_profile=product
|
||||
-f suite_profile=product \
|
||||
-f published_upgrade_survivor_baseline=openclaw@2026.4.26
|
||||
```
|
||||
|
||||
Common package profiles:
|
||||
|
||||
164
docs/reference/full-release-validation.md
Normal file
164
docs/reference/full-release-validation.md
Normal file
@@ -0,0 +1,164 @@
|
||||
---
|
||||
summary: "Full Release Validation stages, child workflows, release profiles, rerun handles, and evidence"
|
||||
title: "Full release validation"
|
||||
read_when:
|
||||
- Running or rerunning Full Release Validation
|
||||
- Comparing stable and full release validation profiles
|
||||
- Debugging release validation stage failures
|
||||
---
|
||||
|
||||
`Full Release Validation` is the release umbrella. It is the single manual
|
||||
entrypoint for pre-release proof, but most work happens in child workflows so a
|
||||
failed box can be rerun without restarting the whole release.
|
||||
|
||||
Run it from a trusted workflow ref, normally `main`, and pass the release branch,
|
||||
tag, or full commit SHA as `ref`:
|
||||
|
||||
```bash
|
||||
gh workflow run full-release-validation.yml \
|
||||
--ref main \
|
||||
-f ref=release/YYYY.M.D \
|
||||
-f provider=openai \
|
||||
-f mode=both \
|
||||
-f release_profile=stable
|
||||
```
|
||||
|
||||
Child workflows use the trusted workflow ref for the harness and the input
|
||||
`ref` for the candidate under test. That keeps new validation logic available
|
||||
when validating an older release branch or tag.
|
||||
|
||||
## Top-level stages
|
||||
|
||||
| Stage | Details |
|
||||
| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Target resolution | **Job:** `Resolve target ref`<br />**Child workflow:** none<br />**Proves:** resolves the release branch, tag, or full commit SHA and records selected inputs.<br />**Rerun:** rerun the umbrella if this fails. |
|
||||
| Vitest and normal CI | **Job:** `Run normal full CI`<br />**Child workflow:** `CI`<br />**Proves:** manual full CI graph against the target ref, including Linux Node lanes, bundled plugin shards, channel contracts, Node 22 compatibility, `check`, `check-additional`, build smoke, docs checks, Python skills, Windows, macOS, Control UI i18n, and Android via the umbrella.<br />**Rerun:** `rerun_group=ci`. |
|
||||
| Plugin prerelease | **Job:** `Run plugin prerelease validation`<br />**Child workflow:** `Plugin Prerelease`<br />**Proves:** release-only plugin static checks, agentic plugin coverage, full extension batch shards, and plugin prerelease Docker lanes.<br />**Rerun:** `rerun_group=plugin-prerelease`. |
|
||||
| Release checks | **Job:** `Run release/live/Docker/QA validation`<br />**Child workflow:** `OpenClaw Release Checks`<br />**Proves:** install smoke, cross-OS package checks, live/E2E suites, Docker release-path chunks, Package Acceptance, QA Lab parity, live Matrix, and live Telegram.<br />**Rerun:** `rerun_group=release-checks` or a narrower release-checks handle. |
|
||||
| Post-publish Telegram | **Job:** `Run post-publish Telegram E2E`<br />**Child workflow:** `NPM Telegram Beta E2E`<br />**Proves:** optional published-package Telegram proof when `npm_telegram_package_spec` is set.<br />**Rerun:** `rerun_group=npm-telegram`. |
|
||||
| Umbrella verifier | **Job:** `Verify full validation`<br />**Child workflow:** none<br />**Proves:** re-checks recorded child run conclusions and appends slowest-job tables from child workflows.<br />**Rerun:** rerun only this job after rerunning a failed child to green. |
|
||||
|
||||
For `ref=main` and `rerun_group=all`, a newer umbrella supersedes an older one.
|
||||
When the parent is cancelled, its monitor cancels any child workflow it already
|
||||
dispatched. Release branch and tag validation runs do not cancel each other by
|
||||
default.
|
||||
|
||||
## Release checks stages
|
||||
|
||||
`OpenClaw Release Checks` is the largest child workflow. It resolves the target
|
||||
once and prepares a shared `release-package-under-test` artifact when package
|
||||
or Docker-facing stages need it.
|
||||
|
||||
| Stage | Details |
|
||||
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Release target | **Job:** `Resolve target ref`<br />**Backing workflow:** none<br />**Tests:** selected ref, optional expected SHA, profile, rerun group, and focused live suite filter.<br />**Rerun:** `rerun_group=release-checks`. |
|
||||
| Package artifact | **Job:** `Prepare release package artifact`<br />**Backing workflow:** none<br />**Tests:** packs or resolves one candidate tarball and uploads `release-package-under-test` for downstream package-facing checks.<br />**Rerun:** the affected package, cross-OS, or live/E2E group. |
|
||||
| Install smoke | **Job:** `Run install smoke`<br />**Backing workflow:** `Install Smoke`<br />**Tests:** full install path with root Dockerfile smoke image reuse, QR package install, root and gateway Docker smokes, installer Docker tests, Bun global install image-provider smoke, and fast bundled-plugin Docker E2E.<br />**Rerun:** `rerun_group=install-smoke`. |
|
||||
| Cross-OS | **Job:** `cross_os_release_checks`<br />**Backing workflow:** `OpenClaw Cross-OS Release Checks (Reusable)`<br />**Tests:** fresh and upgrade lanes on Linux, Windows, and macOS for the selected provider and mode, using the candidate tarball plus a baseline package.<br />**Rerun:** `rerun_group=cross-os`. |
|
||||
| Repo and live E2E | **Job:** `Run repo/live E2E validation`<br />**Backing workflow:** `OpenClaw Live And E2E Checks (Reusable)`<br />**Tests:** repository E2E, live cache, OpenAI websocket streaming, native live provider and plugin shards, and Docker-backed live model/backend/gateway harnesses selected by `release_profile`.<br />**Rerun:** `rerun_group=live-e2e`, optionally with `live_suite_filter`. |
|
||||
| Docker release path | **Job:** `Run Docker release-path validation`<br />**Backing workflow:** `OpenClaw Live And E2E Checks (Reusable)`<br />**Tests:** release-path Docker chunks against the shared package artifact.<br />**Rerun:** `rerun_group=live-e2e`. |
|
||||
| Package Acceptance | **Job:** `Run package acceptance`<br />**Backing workflow:** `Package Acceptance`<br />**Tests:** artifact-native bundled-channel dependency compatibility, offline plugin package fixtures, and mock-OpenAI Telegram package acceptance against the same tarball.<br />**Rerun:** `rerun_group=package`. |
|
||||
| QA parity | **Job:** `Run QA Lab parity lane` and `Run QA Lab parity report`<br />**Backing workflow:** direct jobs<br />**Tests:** candidate and baseline agentic parity packs, then the parity report.<br />**Rerun:** `rerun_group=qa-parity` or `rerun_group=qa`. |
|
||||
| QA live Matrix | **Job:** `Run QA Lab live Matrix lane`<br />**Backing workflow:** direct job<br />**Tests:** fast live Matrix QA profile in the `qa-live-shared` environment.<br />**Rerun:** `rerun_group=qa-live` or `rerun_group=qa`. |
|
||||
| QA live Telegram | **Job:** `Run QA Lab live Telegram lane`<br />**Backing workflow:** direct job<br />**Tests:** live Telegram QA with Convex CI credential leases.<br />**Rerun:** `rerun_group=qa-live` or `rerun_group=qa`. |
|
||||
| Release verifier | **Job:** `Verify release checks`<br />**Backing workflow:** none<br />**Tests:** required release-check jobs for the selected rerun group.<br />**Rerun:** rerun after focused child jobs pass. |
|
||||
|
||||
## Docker release-path chunks
|
||||
|
||||
The Docker release-path stage runs these chunks when `live_suite_filter` is
|
||||
empty:
|
||||
|
||||
| Chunk | Coverage |
|
||||
| ------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
|
||||
| `core` | Core Docker release-path smoke lanes. |
|
||||
| `package-update-openai` | OpenAI package install and update behavior. |
|
||||
| `package-update-anthropic` | Anthropic package install and update behavior. |
|
||||
| `package-update-core` | Provider-neutral package and update behavior. |
|
||||
| `plugins-runtime-plugins` | Plugin runtime lanes that exercise plugin behavior. |
|
||||
| `plugins-runtime-services` | Service-backed plugin runtime lanes; includes OpenWebUI when requested. |
|
||||
| `plugins-runtime-install-a` through `plugins-runtime-install-h` | Plugin install/runtime batches split for parallel release validation. |
|
||||
| `bundled-channels-core` | Bundled channel Docker behavior. |
|
||||
| `bundled-channels-update-a`, `bundled-channels-update-discord`, `bundled-channels-update-b` | Bundled channel update behavior. |
|
||||
| `bundled-channels-contracts` | Bundled channel contract checks in the Docker release path. |
|
||||
|
||||
Use targeted `docker_lanes=<lane[,lane]>` on the reusable live/E2E workflow when
|
||||
only one Docker lane failed. The release artifacts include per-lane rerun
|
||||
commands with package artifact and image reuse inputs when available.
|
||||
|
||||
## Release profiles
|
||||
|
||||
`release_profile` only controls live/provider breadth inside release checks. It
|
||||
does not remove normal full CI, Plugin Prerelease, install smoke, package
|
||||
acceptance, QA Lab, or Docker release-path chunks.
|
||||
|
||||
| Profile | Intended use | Included live/provider coverage |
|
||||
| --------- | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `minimum` | Fastest release-critical smoke. | OpenAI/core live path, Docker live models for OpenAI, native gateway core, native OpenAI gateway profile, native OpenAI plugin, and Docker live gateway OpenAI. |
|
||||
| `stable` | Default release approval profile. | `minimum` plus Anthropic, Google, MiniMax, backend, native live test harness, Docker live CLI backend, Docker ACP bind, Docker Codex harness, and an OpenCode Go smoke shard. |
|
||||
| `full` | Broad advisory sweep. | `stable` plus advisory providers, plugin live shards, and media live shards. |
|
||||
|
||||
## Full-only additions
|
||||
|
||||
These suites are skipped by `stable` and included by `full`:
|
||||
|
||||
| Area | Full-only coverage |
|
||||
| -------------------------------- | ------------------------------------------------------------------------------- |
|
||||
| Docker live models | OpenCode Go, OpenRouter, xAI, Z.ai, and Fireworks. |
|
||||
| Docker live gateway | Advisory shard for DeepSeek, Fireworks, OpenCode Go, OpenRouter, xAI, and Z.ai. |
|
||||
| Native gateway provider profiles | Fireworks, DeepSeek, full OpenCode Go model shards, OpenRouter, xAI, and Z.ai. |
|
||||
| Native plugin live shards | Plugins A-K, L-N, O-Z other, Moonshot, and xAI. |
|
||||
| Native media live shards | Audio, Google music, MiniMax music, and video groups A-D. |
|
||||
|
||||
`stable` includes `native-live-src-gateway-profiles-opencode-go-smoke`; `full`
|
||||
uses the broader OpenCode Go model shards instead.
|
||||
|
||||
## Focused reruns
|
||||
|
||||
Use `rerun_group` to avoid repeating unrelated release boxes:
|
||||
|
||||
| Handle | Scope |
|
||||
| ------------------- | ------------------------------------------------- |
|
||||
| `all` | All Full Release Validation stages. |
|
||||
| `ci` | Manual full CI child only. |
|
||||
| `plugin-prerelease` | Plugin Prerelease child only. |
|
||||
| `release-checks` | All OpenClaw Release Checks stages. |
|
||||
| `install-smoke` | Install Smoke through release checks. |
|
||||
| `cross-os` | Cross-OS release checks. |
|
||||
| `live-e2e` | Repo/live E2E and Docker release-path validation. |
|
||||
| `package` | Package Acceptance. |
|
||||
| `qa` | QA parity plus QA live lanes. |
|
||||
| `qa-parity` | QA parity lanes and report only. |
|
||||
| `qa-live` | QA live Matrix and Telegram only. |
|
||||
| `npm-telegram` | Optional post-publish Telegram E2E only. |
|
||||
|
||||
Use `live_suite_filter` with `rerun_group=live-e2e` when one live suite failed.
|
||||
Valid filter ids are defined in the reusable live/E2E workflow, including
|
||||
`docker-live-models`, `live-gateway-docker`,
|
||||
`live-gateway-anthropic-docker`, `live-gateway-google-docker`,
|
||||
`live-gateway-minimax-docker`, `live-gateway-advisory-docker`,
|
||||
`live-cli-backend-docker`, `live-acp-bind-docker`, and
|
||||
`live-codex-harness-docker`.
|
||||
|
||||
## Evidence to keep
|
||||
|
||||
Keep the `Full Release Validation` summary as the release-level index. It links
|
||||
child run ids and includes slowest-job tables. For failures, inspect the child
|
||||
workflow first, then rerun the smallest matching handle above.
|
||||
|
||||
Useful artifacts:
|
||||
|
||||
- `release-package-under-test` from `OpenClaw Release Checks`
|
||||
- Docker release-path artifacts under `.artifacts/docker-tests/`
|
||||
- Package Acceptance `package-under-test` and Docker acceptance artifacts
|
||||
- Cross-OS release-check artifacts for each OS and suite
|
||||
- QA parity, Matrix, and Telegram artifacts
|
||||
|
||||
## Workflow files
|
||||
|
||||
- `.github/workflows/full-release-validation.yml`
|
||||
- `.github/workflows/openclaw-release-checks.yml`
|
||||
- `.github/workflows/openclaw-live-and-e2e-checks-reusable.yml`
|
||||
- `.github/workflows/plugin-prerelease.yml`
|
||||
- `.github/workflows/install-smoke.yml`
|
||||
- `.github/workflows/openclaw-cross-os-release-checks-reusable.yml`
|
||||
- `.github/workflows/package-acceptance.yml`
|
||||
@@ -50,6 +50,8 @@ Scope intent:
|
||||
- `plugins.entries.firecrawl.config.webSearch.apiKey`
|
||||
- `plugins.entries.minimax.config.webSearch.apiKey`
|
||||
- `plugins.entries.tavily.config.webSearch.apiKey`
|
||||
- `plugins.entries.voice-call.config.realtime.providers.*.apiKey`
|
||||
- `plugins.entries.voice-call.config.streaming.providers.*.apiKey`
|
||||
- `plugins.entries.voice-call.config.tts.providers.*.apiKey`
|
||||
- `plugins.entries.voice-call.config.twilio.authToken`
|
||||
- `tools.web.search.apiKey`
|
||||
|
||||
@@ -589,6 +589,20 @@
|
||||
"secretShape": "secret_input",
|
||||
"optIn": true
|
||||
},
|
||||
{
|
||||
"id": "plugins.entries.voice-call.config.realtime.providers.*.apiKey",
|
||||
"configFile": "openclaw.json",
|
||||
"path": "plugins.entries.voice-call.config.realtime.providers.*.apiKey",
|
||||
"secretShape": "secret_input",
|
||||
"optIn": true
|
||||
},
|
||||
{
|
||||
"id": "plugins.entries.voice-call.config.streaming.providers.*.apiKey",
|
||||
"configFile": "openclaw.json",
|
||||
"path": "plugins.entries.voice-call.config.streaming.providers.*.apiKey",
|
||||
"secretShape": "secret_input",
|
||||
"optIn": true
|
||||
},
|
||||
{
|
||||
"id": "plugins.entries.voice-call.config.tts.providers.*.apiKey",
|
||||
"configFile": "openclaw.json",
|
||||
|
||||
@@ -79,7 +79,7 @@ Session persistence has automatic maintenance controls (`session.maintenance`) f
|
||||
- `maxDiskBytes`: optional sessions-directory budget
|
||||
- `highWaterBytes`: optional target after cleanup (default `80%` of `maxDiskBytes`)
|
||||
|
||||
Normal Gateway writes batch `maxEntries` cleanup for production-sized caps, so a store may briefly exceed the configured cap before the next high-water cleanup rewrites it back down. `openclaw sessions cleanup --enforce` still applies the configured cap immediately.
|
||||
Normal Gateway writes batch `maxEntries` cleanup for production-sized caps, so a store may briefly exceed the configured cap before the next high-water cleanup rewrites it back down. Session store reads do not prune or cap entries during Gateway startup; use writes or `openclaw sessions cleanup --enforce` for cleanup. `openclaw sessions cleanup --enforce` still applies the configured cap immediately.
|
||||
|
||||
OpenClaw no longer creates automatic `sessions.json.bak.*` rotation backups during Gateway writes. The legacy `session.maintenance.rotateBytes` key is ignored and `openclaw doctor --fix` removes it from older configs.
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ title: "Tests"
|
||||
- `pnpm test:docker:openwebui`: Starts Dockerized OpenClaw + Open WebUI, signs in through Open WebUI, checks `/api/models`, then runs a real proxied chat through `/api/chat/completions`. Requires a usable live model key (for example OpenAI in `~/.profile`), pulls an external Open WebUI image, and is not expected to be CI-stable like the normal unit/e2e suites.
|
||||
- `pnpm test:docker:mcp-channels`: Starts a seeded Gateway container and a second client container that spawns `openclaw mcp serve`, then verifies routed conversation discovery, transcript reads, attachment metadata, live event queue behavior, outbound send routing, and Claude-style channel + permission notifications over the real stdio bridge. The Claude notification assertion reads the raw stdio MCP frames directly so the smoke reflects what the bridge actually emits.
|
||||
- `pnpm test:docker:upgrade-survivor`: Installs the packed OpenClaw tarball over a dirty old-user fixture, runs package update plus non-interactive doctor without live provider or channel keys, then starts a loopback Gateway and checks that agents, channel config, plugin allowlists, workspace/session files, stale plugin runtime-deps state, startup, and RPC status survive.
|
||||
- `pnpm test:docker:published-upgrade-survivor`: Installs `openclaw@latest` by default over the dirty old-user fixture, updates that published install to the packed OpenClaw tarball, then runs the same doctor, Gateway startup, and RPC survival assertions. Override the baseline with `OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC`.
|
||||
- `pnpm test:docker:published-upgrade-survivor`: Installs `openclaw@latest` by default, seeds realistic existing-user files without live provider or channel keys, configures that baseline with a baked `openclaw config set` command recipe, updates that published install to the packed OpenClaw tarball, runs non-interactive doctor, writes `.artifacts/upgrade-survivor/summary.json`, then starts a loopback Gateway and checks that configured intents, workspace/session files, stale plugin config/runtime-deps state, startup, `/healthz`, `/readyz`, and RPC status survive or repair cleanly. Override one baseline with `OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC`, expand an exact matrix with `OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPECS`, or add scenario fixtures with `OPENCLAW_UPGRADE_SURVIVOR_SCENARIOS=reported-issues`; Package Acceptance exposes those as `published_upgrade_survivor_baseline`, `published_upgrade_survivor_baselines`, and `published_upgrade_survivor_scenarios`.
|
||||
|
||||
## Local PR gate
|
||||
|
||||
|
||||
@@ -135,13 +135,43 @@ If your cloud provider or network platform documents additional metadata hosts o
|
||||
|
||||
Validate the proxy from the same host, container, or service account that runs OpenClaw:
|
||||
|
||||
```bash
|
||||
openclaw proxy validate --proxy-url http://127.0.0.1:3128
|
||||
```
|
||||
|
||||
By default, when no custom destinations are provided, the command checks that `https://example.com/` succeeds and starts a temporary loopback canary that the proxy must not reach. The default denied check passes when the proxy returns a non-2xx denial response or blocks the canary with a transport failure; it fails if a successful response reaches the canary. If no proxy is enabled and configured, validation reports a config problem; use `--proxy-url` for a one-off preflight before changing config. Use `--allowed-url` and `--denied-url` to test deployment-specific expectations. Custom denied destinations are fail-closed: any HTTP response means the destination was reachable through the proxy, and any transport error is reported as inconclusive because OpenClaw cannot prove the proxy blocked a reachable origin. On validation failure, the command exits with code 1.
|
||||
|
||||
Use `--json` for automation. The JSON output contains the overall result, the effective proxy config source, any config errors, and each destination check. Proxy URL credentials are redacted in text and JSON output:
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"config": {
|
||||
"enabled": true,
|
||||
"proxyUrl": "http://127.0.0.1:3128/",
|
||||
"source": "override",
|
||||
"errors": []
|
||||
},
|
||||
"checks": [
|
||||
{
|
||||
"kind": "allowed",
|
||||
"url": "https://example.com/",
|
||||
"ok": true,
|
||||
"status": 200
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
You can also validate manually with `curl`:
|
||||
|
||||
```bash
|
||||
curl -x http://127.0.0.1:3128 https://example.com/
|
||||
curl -x http://127.0.0.1:3128 http://127.0.0.1/
|
||||
curl -x http://127.0.0.1:3128 http://169.254.169.254/
|
||||
```
|
||||
|
||||
The public request should succeed. The loopback and metadata requests should fail at the proxy.
|
||||
The public request should succeed. The loopback and metadata requests should be blocked by the proxy. For `openclaw proxy validate`, the built-in loopback canary can distinguish a proxy denial from a reachable origin. Custom `--denied-url` checks do not have that canary, so treat both HTTP responses and ambiguous transport failures as validation failures unless your proxy exposes a deployment-specific denial signal you can verify separately.
|
||||
|
||||
Then enable OpenClaw proxy routing:
|
||||
|
||||
|
||||
@@ -41,7 +41,8 @@ directly to existing OpenClaw channel conversations, use
|
||||
|
||||
Usually yes. Fresh installs ship the bundled `acpx` runtime plugin enabled
|
||||
by default with a plugin-local pinned `acpx` binary that OpenClaw probes
|
||||
and self-repairs on startup. Run `/acp doctor` for a readiness check.
|
||||
and self-repairs immediately after the Gateway HTTP listener is live. Run
|
||||
`/acp doctor` for a readiness check.
|
||||
|
||||
OpenClaw only teaches agents about ACP spawning when ACP is **truly
|
||||
usable**: ACP must be enabled, dispatch must not be disabled, the current
|
||||
|
||||
@@ -30,6 +30,9 @@ temporary set of OpenClaw-owned plugin packages while that migration finishes.
|
||||
# From npm
|
||||
openclaw plugins install npm:@acme/openclaw-plugin
|
||||
|
||||
# From git
|
||||
openclaw plugins install git:github.com/acme/openclaw-plugin@v1.0.0
|
||||
|
||||
# From a local directory or archive
|
||||
openclaw plugins install ./my-plugin
|
||||
openclaw plugins install ./my-plugin.tgz
|
||||
@@ -45,6 +48,20 @@ temporary set of OpenClaw-owned plugin packages while that migration finishes.
|
||||
Then configure under `plugins.entries.\<id\>.config` in your config file.
|
||||
|
||||
</Step>
|
||||
|
||||
<Step title="Verify the plugin">
|
||||
```bash
|
||||
openclaw plugins inspect <plugin-id> --runtime --json
|
||||
|
||||
# If the plugin registered a CLI root, run one command from that root.
|
||||
openclaw <plugin-command> --help
|
||||
```
|
||||
|
||||
Use `--runtime` when you need to prove registered tools, services, gateway
|
||||
methods, hooks, or plugin-owned CLI commands. Plain `inspect` is a cold
|
||||
manifest/registry check and intentionally avoids importing plugin runtime.
|
||||
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
If you prefer chat-native control, enable `commands.plugins: true` and use:
|
||||
@@ -56,8 +73,8 @@ If you prefer chat-native control, enable `commands.plugins: true` and use:
|
||||
```
|
||||
|
||||
The install path uses the same resolver as the CLI: local path/archive, explicit
|
||||
`clawhub:<pkg>`, explicit `npm:<pkg>`, or bare package spec (ClawHub first, then
|
||||
npm fallback).
|
||||
`clawhub:<pkg>`, explicit `npm:<pkg>`, explicit `git:<repo>`, or bare package
|
||||
spec (ClawHub first, then npm fallback).
|
||||
|
||||
If config is invalid, install normally fails closed and points you at
|
||||
`openclaw doctor --fix`. The only recovery exception is a narrow bundled-plugin
|
||||
@@ -93,6 +110,8 @@ repair; explicit bundled channel enablement (`channels.<id>.enabled: true`) can
|
||||
still repair that channel's plugin dependencies.
|
||||
External plugins and custom load paths must still be installed through
|
||||
`openclaw plugins install`.
|
||||
See [Plugin dependency resolution](/plugins/dependency-resolution) for the full
|
||||
planning and staging lifecycle.
|
||||
|
||||
## Plugin types
|
||||
|
||||
@@ -218,6 +237,12 @@ Looking for third-party plugins? See [Community Plugins](/plugins/community).
|
||||
| `slots` | Exclusive slot selectors (e.g. `memory`, `contextEngine`) |
|
||||
| `entries.\<id\>` | Per-plugin toggles + config |
|
||||
|
||||
`plugins.allow` is exclusive. When it is non-empty, only listed plugins can load
|
||||
or expose tools, even if `tools.allow` contains `"*"` or a specific plugin-owned
|
||||
tool name. If a tool allowlist references plugin tools, add the owning plugin ids
|
||||
to `plugins.allow` or remove `plugins.allow`; `openclaw doctor` warns about this
|
||||
shape.
|
||||
|
||||
Config changes **require a gateway restart**. If the Gateway is running with config
|
||||
watch + in-process restart enabled (the default `openclaw gateway` path), that
|
||||
restart is usually performed automatically a moment after the config write lands.
|
||||
@@ -303,7 +328,7 @@ do not run in live chat traffic, check these first:
|
||||
- Restart the live Gateway after plugin install/config/code changes. In wrapper
|
||||
containers, PID 1 may only be a supervisor; restart or signal the child
|
||||
`openclaw gateway run` process.
|
||||
- Use `openclaw plugins inspect <id> --json` to confirm hook registrations and
|
||||
- Use `openclaw plugins inspect <id> --runtime --json` to confirm hook registrations and
|
||||
diagnostics. Non-bundled conversation hooks such as `llm_input`,
|
||||
`llm_output`, `before_agent_finalize`, and `agent_end` need
|
||||
`plugins.entries.<id>.hooks.allowConversationAccess=true`.
|
||||
@@ -330,7 +355,7 @@ Debug steps:
|
||||
|
||||
- Run `openclaw plugins list --enabled --verbose` to see every enabled plugin
|
||||
and origin.
|
||||
- Run `openclaw plugins inspect <id> --json` for each suspected plugin and
|
||||
- Run `openclaw plugins inspect <id> --runtime --json` for each suspected plugin and
|
||||
compare `channels`, `channelConfigs`, `tools`, and diagnostics.
|
||||
- Run `openclaw plugins registry --refresh` after installing or removing
|
||||
plugin packages so persisted metadata reflects the current install.
|
||||
@@ -375,7 +400,8 @@ openclaw plugins list # compact inventory
|
||||
openclaw plugins list --enabled # only enabled plugins
|
||||
openclaw plugins list --verbose # per-plugin detail lines
|
||||
openclaw plugins list --json # machine-readable inventory
|
||||
openclaw plugins inspect <id> # deep detail
|
||||
openclaw plugins inspect <id> # static detail
|
||||
openclaw plugins inspect <id> --runtime # registered hooks/tools/CLI/gateway methods
|
||||
openclaw plugins inspect <id> --json # machine-readable
|
||||
openclaw plugins inspect --all # fleet-wide table
|
||||
openclaw plugins info <id> # inspect alias
|
||||
@@ -387,6 +413,8 @@ openclaw doctor --fix # repair plugin registry state
|
||||
openclaw plugins install <package> # install (ClawHub first, then npm)
|
||||
openclaw plugins install clawhub:<pkg> # install from ClawHub only
|
||||
openclaw plugins install npm:<pkg> # install from npm only
|
||||
openclaw plugins install git:<repo> # install from git
|
||||
openclaw plugins install git:<repo>@<ref> # install from git ref
|
||||
openclaw plugins install <spec> --force # overwrite existing install
|
||||
openclaw plugins install <path> # install from local path
|
||||
openclaw plugins install -l <path> # link (no copy) for dev
|
||||
@@ -402,6 +430,12 @@ openclaw plugins uninstall <id> --keep-files
|
||||
openclaw plugins marketplace list <source>
|
||||
openclaw plugins marketplace list <source> --json
|
||||
|
||||
# Verify runtime registrations after install.
|
||||
openclaw plugins inspect <id> --runtime --json
|
||||
|
||||
# Run plugin-owned CLI commands directly from the OpenClaw root CLI.
|
||||
openclaw <plugin-command> --help
|
||||
|
||||
openclaw plugins enable <id>
|
||||
openclaw plugins disable <id>
|
||||
```
|
||||
|
||||
@@ -247,7 +247,7 @@ User-invocable skills are also exposed as slash commands:
|
||||
- In multi-account channels, config-targeted `/allowlist --account <id>` and `/config set channels.<provider>.accounts.<id>...` also honor the target account's `configWrites`.
|
||||
- `/usage` controls the per-response usage footer; `/usage cost` prints a local cost summary from OpenClaw session logs.
|
||||
- `/restart` is enabled by default; set `commands.restart: false` to disable it.
|
||||
- `/plugins install <spec>` accepts the same plugin specs as `openclaw plugins install`: local path/archive, npm package, or `clawhub:<pkg>`.
|
||||
- `/plugins install <spec>` accepts the same plugin specs as `openclaw plugins install`: local path/archive, npm package, `git:<repo>`, or `clawhub:<pkg>`.
|
||||
- `/plugins enable|disable` updates plugin config and may prompt for a restart.
|
||||
|
||||
</Accordion>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"description": "OpenClaw ACP runtime backend",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/claude-agent-acp": "0.31.1",
|
||||
"@agentclientprotocol/claude-agent-acp": "0.31.4",
|
||||
"@zed-industries/codex-acp": "0.12.0",
|
||||
"acpx": "0.6.1"
|
||||
},
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { buildPluginConfigSchema } from "openclaw/plugin-sdk/core";
|
||||
import { z } from "openclaw/plugin-sdk/zod";
|
||||
import type { OpenClawPluginConfigSchema } from "../runtime-api.js";
|
||||
|
||||
export const ACPX_PERMISSION_MODES = ["approve-all", "approve-reads", "deny-all"] as const;
|
||||
export type AcpxPermissionMode = (typeof ACPX_PERMISSION_MODES)[number];
|
||||
@@ -117,7 +115,3 @@ export const AcpxPluginConfigSchema = z.strictObject({
|
||||
)
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export function createAcpxPluginConfigSchema(): OpenClawPluginConfigSchema {
|
||||
return buildPluginConfigSchema(AcpxPluginConfigSchema);
|
||||
}
|
||||
|
||||
@@ -14,15 +14,12 @@ import type {
|
||||
ResolvedAcpxPluginConfig,
|
||||
} from "./config-schema.js";
|
||||
export {
|
||||
ACPX_NON_INTERACTIVE_POLICIES,
|
||||
ACPX_PERMISSION_MODES,
|
||||
type AcpxMcpServer,
|
||||
type AcpxNonInteractivePermissionPolicy,
|
||||
type AcpxPermissionMode,
|
||||
type AcpxPluginConfig,
|
||||
type McpServerConfig,
|
||||
type ResolvedAcpxPluginConfig,
|
||||
createAcpxPluginConfigSchema,
|
||||
} from "./config-schema.js";
|
||||
|
||||
export const ACPX_PLUGIN_TOOLS_MCP_SERVER_NAME = "openclaw-plugin-tools";
|
||||
@@ -104,8 +101,6 @@ export function resolveAcpxPluginRoot(moduleUrl: string = import.meta.url): stri
|
||||
);
|
||||
}
|
||||
|
||||
export const ACPX_PLUGIN_ROOT = resolveAcpxPluginRoot();
|
||||
|
||||
const DEFAULT_PERMISSION_MODE: AcpxPermissionMode = "approve-reads";
|
||||
const DEFAULT_NON_INTERACTIVE_POLICY: AcpxNonInteractivePermissionPolicy = "fail";
|
||||
const DEFAULT_QUEUE_OWNER_TTL_SECONDS = 0.1;
|
||||
|
||||
@@ -1039,6 +1039,7 @@ describe("active-memory plugin", () => {
|
||||
"If memory_recall is unavailable, use memory_search and memory_get.",
|
||||
);
|
||||
expect(runParams?.toolsAllow).toEqual(["memory_recall", "memory_search", "memory_get"]);
|
||||
expect(runParams?.allowGatewaySubagentBinding).toBe(true);
|
||||
expect(runParams?.prompt).toContain(
|
||||
"When searching for preference or habit recall, use a permissive recall limit or memory_search threshold before deciding that no useful memory exists.",
|
||||
);
|
||||
|
||||
@@ -2138,6 +2138,7 @@ async function runRecallSubagent(params: {
|
||||
trigger: "manual",
|
||||
toolsAllow: ["memory_recall", "memory_search", "memory_get"],
|
||||
disableMessageTool: true,
|
||||
allowGatewaySubagentBinding: true,
|
||||
bootstrapContextMode: "lightweight",
|
||||
verboseLevel: "off",
|
||||
thinkLevel: params.config.thinking,
|
||||
|
||||
@@ -51,8 +51,8 @@ function isSupportedRegion(region: string): boolean {
|
||||
// Bearer token resolution
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export type MantleBearerTokenProvider = () => Promise<string>;
|
||||
export type MantleBearerTokenProviderFactory = (opts?: {
|
||||
type MantleBearerTokenProvider = () => Promise<string>;
|
||||
type MantleBearerTokenProviderFactory = (opts?: {
|
||||
region?: string;
|
||||
expiresInSeconds?: number;
|
||||
}) => MantleBearerTokenProvider;
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
"description": "OpenClaw Amazon Bedrock Mantle (OpenAI-compatible) provider plugin",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "0.91.1",
|
||||
"@anthropic-ai/sdk": "0.92.0",
|
||||
"@aws/bedrock-token-generator": "^1.1.0",
|
||||
"@mariozechner/pi-ai": "0.70.6"
|
||||
"@mariozechner/pi-ai": "0.71.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@openclaw/plugin-sdk": "workspace:*"
|
||||
|
||||
@@ -10,7 +10,7 @@ import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtim
|
||||
// Types & constants
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export type BedrockEmbeddingClient = {
|
||||
type BedrockEmbeddingClient = {
|
||||
region: string;
|
||||
model: string;
|
||||
dimensions?: number;
|
||||
@@ -162,7 +162,7 @@ async function loadCredentialProviderSdk(): Promise<AwsCredentialProviderSdk | n
|
||||
const MODEL_PREFIX_RE = /^(?:bedrock|amazon-bedrock|aws)\//;
|
||||
const REGION_RE = /bedrock-runtime\.([a-z0-9-]+)\./;
|
||||
|
||||
export function normalizeBedrockEmbeddingModel(model: string): string {
|
||||
function normalizeBedrockEmbeddingModel(model: string): string {
|
||||
const trimmed = model.trim();
|
||||
return trimmed ? trimmed.replace(MODEL_PREFIX_RE, "") : DEFAULT_BEDROCK_EMBEDDING_MODEL;
|
||||
}
|
||||
@@ -337,7 +337,7 @@ export async function createBedrockEmbeddingProvider(
|
||||
// Client resolution
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export function resolveBedrockEmbeddingClient(
|
||||
function resolveBedrockEmbeddingClient(
|
||||
options: MemoryEmbeddingProviderCreateOptions,
|
||||
): BedrockEmbeddingClient {
|
||||
const model = normalizeBedrockEmbeddingModel(options.model);
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
"description": "OpenClaw Amazon Bedrock provider plugin",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-bedrock": "3.1038.0",
|
||||
"@aws-sdk/client-bedrock-runtime": "3.1038.0",
|
||||
"@aws-sdk/credential-provider-node": "3.972.37"
|
||||
"@aws-sdk/client-bedrock": "3.1040.0",
|
||||
"@aws-sdk/client-bedrock-runtime": "3.1040.0",
|
||||
"@aws-sdk/credential-provider-node": "3.972.38"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@openclaw/plugin-sdk": "workspace:*"
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/vertex-sdk": "^0.16.0",
|
||||
"@mariozechner/pi-agent-core": "0.70.6",
|
||||
"@mariozechner/pi-ai": "0.70.6"
|
||||
"@mariozechner/pi-agent-core": "0.71.1",
|
||||
"@mariozechner/pi-ai": "0.71.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@openclaw/plugin-sdk": "workspace:*"
|
||||
|
||||
@@ -26,6 +26,7 @@ export function buildAnthropicCliBackend(): CliBackendPlugin {
|
||||
},
|
||||
bundleMcp: true,
|
||||
bundleMcpMode: "claude-config-file",
|
||||
nativeToolMode: "always-on",
|
||||
config: {
|
||||
command: "claude",
|
||||
args: [
|
||||
|
||||
@@ -165,7 +165,7 @@ function toCanonicalAnthropicModelRef(ref: string): string {
|
||||
: ref;
|
||||
}
|
||||
|
||||
export function normalizeAnthropicProviderConfig<T extends { api?: string; models?: unknown[] }>(
|
||||
function normalizeAnthropicProviderConfig<T extends { api?: string; models?: unknown[] }>(
|
||||
providerConfig: T,
|
||||
): T {
|
||||
if (
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"description": "OpenClaw Anthropic provider plugin",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@mariozechner/pi-ai": "0.70.6"
|
||||
"@mariozechner/pi-ai": "0.71.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@openclaw/plugin-sdk": "workspace:*"
|
||||
|
||||
@@ -23,7 +23,7 @@ function resolveClaudeCliSyntheticAuth() {
|
||||
};
|
||||
}
|
||||
|
||||
export const anthropicProviderDiscovery: ProviderPlugin = {
|
||||
const anthropicProviderDiscovery: ProviderPlugin = {
|
||||
id: CLAUDE_CLI_BACKEND_ID,
|
||||
label: "Claude CLI",
|
||||
docsPath: "/providers/models",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user