Compare commits

...

395 Commits

Author SHA1 Message Date
Keshav's Bot
dde017027c fix(codex): gate profiler timing and startup setup 2026-05-26 19:02:13 +01:00
Vincent Koc
2b63eb2825 fix(e2e): bound corrupt plugin update runs 2026-05-26 19:54:05 +02:00
Peter Steinberger
6930538500 ci: require codex profiles for live probes 2026-05-26 18:51:43 +01:00
Peter Steinberger
cd46057b90 docs: clarify inline comment guidance 2026-05-26 18:49:29 +01:00
Peter Steinberger
8c575bd3c8 docs: update changelog for landed sweep fixes 2026-05-26 18:41:00 +01:00
Fermin Quant
598aad4f66 fix(agents): disclose scoped session list results (#86944)
* fix(agents): disclose scoped session list results

* fix(agents): clarify scoped session count warning
2026-05-26 18:40:36 +01:00
Andy Ye
1fd8de8495 fix(telegram): treat ENETDOWN as transient network failure (#86762) 2026-05-26 18:40:31 +01:00
Vincent Koc
564e0bb5c1 fix(mac): harden package script safety 2026-05-26 19:40:16 +02:00
Vincent Koc
c867ecb136 fix(ci): kill wedged checkout fetches 2026-05-26 19:38:34 +02:00
Peter Steinberger
9fd8158c06 ci: restore codex replay live probe contract 2026-05-26 18:35:56 +01:00
Eva
7a147419db fix(codex): preserve oversized native thread reuse
Reworks the Codex app-server native thread reuse guard so OpenClaw no longer adds a user-facing token config. Token clearing now prefers Codex's reported model context window, falls back to a high internal recovery fuse, and preserves context-engine thread-bootstrap reuse while keeping byte guard behavior intact.

Verification:
- `fnm exec --using v24.15.0 -- node scripts/run-vitest.mjs run extensions/codex/src/app-server/run-attempt.test.ts extensions/codex/src/app-server/run-attempt.context-engine.test.ts --reporter=dot --pool=forks --no-file-parallelism`
- `git diff --check`
- `.agents/skills/autoreview/scripts/autoreview --mode local --base origin/main`
- Testbox `check:changed`: `tbx_01ksjm1hy7mfrc5bebzyckqdew`, GitHub Actions run https://github.com/openclaw/openclaw/actions/runs/26463150977, exit 0
- PR CI green after rerunning unrelated `checks-node-agentic-agents` flake and stuck OpenGrep scan

Co-authored-by: Eva (agent) <eva+agent-78055@100yen.org>
2026-05-26 18:33:59 +01:00
Vincent Koc
a5eee8f1c6 fix(scripts): detect timed changed gates 2026-05-26 19:19:26 +02:00
Peter Steinberger
3c6fd49d74 ci: stop waiting for nonexistent capability restart wake 2026-05-26 18:15:16 +01:00
Vincent Koc
e8f584e400 fix(e2e): route plugin update through timeout helper 2026-05-26 19:11:09 +02:00
Peter Steinberger
7e6837bc07 fix: respect root options in startup guards (#86927) 2026-05-26 18:08:51 +01:00
Peter Steinberger
0ec29289c6 fix: tighten CLI utility failure handling (#86918)
* fix: tighten cli utility failure handling

* fix: preserve completion install error cause

* fix: keep update completion refresh best effort
2026-05-26 18:08:44 +01:00
Peter Steinberger
82dae95c76 fix: preserve config and hook contracts (#86911) 2026-05-26 18:08:39 +01:00
Peter Steinberger
c147e27f5a fix: tighten small runtime parsing guards (#86909) 2026-05-26 18:08:33 +01:00
Vincent Koc
081e29595e fix(ci): kill timed tui pty test runs 2026-05-26 18:55:47 +02:00
Onur Solmaz
6c18c212e9 fix(logging): preserve env placeholders during redaction
* fix(logging): preserve env placeholders during redaction

* fix(logging): honor custom redaction patterns

* fix(logging): preserve generic env placeholders

---------

Co-authored-by: Onur Solmaz <onur@Onurs-MacBook-Pro.local>
2026-05-27 00:49:34 +08:00
lukeboyett
9e43d0327f fix(memory-core): avoid per-file watcher FD fan-out for memory directories (#86701)
Merged via squash.

Prepared head SHA: e27c28a3a1
Co-authored-by: lukeboyett <46942646+lukeboyett@users.noreply.github.com>
Co-authored-by: osolmaz <2453968+osolmaz@users.noreply.github.com>
Reviewed-by: @osolmaz
2026-05-27 00:48:22 +08:00
Peter Steinberger
5535eef6b0 fix: use current config sdk contract in feishu doctor 2026-05-26 17:45:24 +01:00
Michael Appel
84b9704ccc Validate wide-area DNS zone domains [AI] (#84136)
* fix: validate wide-area dns domains

* addressing codex review

* fix(dns-cli): throw explicit DNS-name error on invalid --domain

resolveWideAreaDiscoveryDomain catches the validation error from
normalizeWideAreaDomain and returns null, so dns setup --domain foo/bar
fell through to the "No wide-area domain configured" branch instead of
surfacing the invalid-domain diagnostic. Validate explicit CLI/config
input directly so the user-facing setup command reports the actual
problem; preserve the resolver's silent env-fallback semantics for the
background callers that depend on graceful degradation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test(gateway): lock in graceful degrade on invalid wide-area config

Drive startGatewayDiscovery through the real resolveWideAreaDiscoveryDomain
with wideAreaDiscoveryDomain: "foo/bar" so the test exercises the actual
swallow-and-return-null path. Asserts the operator-facing warning is
logged, writeWideAreaGatewayZone is never called, and startup completes
without throwing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test(gateway): type resolveWideAreaDiscoveryDomain mock to match real signature

vi.fn(() => "openclaw.internal.") inferred the mock as `() => string`, so
mockImplementationOnce(realResolver) tripped tsgo:core:test with TS2345.
Apply the same vi.fn<typeof ...>(...) pattern the file already uses for
writeWideAreaGatewayZone.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(changelog): note dns validation fix

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Agustin Rivera <agustin@rivera-web.com>
2026-05-26 09:43:58 -07:00
Peter Steinberger
27359ec417 ci: stabilize release live QA gates 2026-05-26 17:41:30 +01:00
Peter Steinberger
cf21c8abcb ci: harden live release gates 2026-05-26 17:41:30 +01:00
Peter Steinberger
c84f61cd2e ci: normalize Windows toolcache Node paths 2026-05-26 17:41:30 +01:00
Peter Steinberger
fdb7848a7c chore: remove stale codex test conversion 2026-05-26 17:40:44 +01:00
Peter Steinberger
496fd8f853 perf: cache read-only channel resolution 2026-05-26 17:40:44 +01:00
Vincent Koc
373b3bfe54 fix(test): explain missing vitest dependency 2026-05-26 18:33:50 +02:00
Vincent Koc
d5bf325126 fix(e2e): kill timed docker scenario runners 2026-05-26 18:31:38 +02:00
Peter Steinberger
645cbf6c33 fix: add transcripts tool display metadata 2026-05-26 17:28:23 +01:00
Peter Steinberger
12b81d8978 docs: update changelog for landed fixes 2026-05-26 17:22:19 +01:00
Neerav Makwana
06afc57102 fix(agents): route btw through embedded stream resolver (#86312) 2026-05-26 17:21:38 +01:00
狼哥
c7821bd2a8 fix(telegram): treat targeted bot commands as mentions (#86553) 2026-05-26 17:21:33 +01:00
Vincent Koc
9ced76a4bb fix(e2e): route doctor switch commands through timeout helper 2026-05-26 18:19:44 +02:00
Shadow
7671068daf fix(ci): evaluate duplicate proof sections 2026-05-26 11:18:42 -05:00
Peter Steinberger
ead847f606 fix: ignore other codex thread completions 2026-05-26 17:16:17 +01:00
Peter Steinberger
b7c461af7b fix(feishu): repair stale channel state
Closes #74237.
Recreates #74397 locally because the fork disallows maintainer edits.

Co-authored-by: Lightningxxl <yuanhangxurobin@gmail.com>
2026-05-26 17:10:34 +01:00
Peter Steinberger
0973a7e4e4 fix: remove stale image provider assertions 2026-05-26 17:04:09 +01:00
Peter Steinberger
d001d35ea2 fix: accept trailing fuzzy voice wake questions 2026-05-26 16:59:05 +01:00
Vincent Koc
d6fcb562f4 fix(podman): bound setup image builds 2026-05-26 17:58:42 +02:00
Vincent Koc
6118f3f615 fix(podman): kill timed container launches 2026-05-26 17:47:25 +02:00
Vincent Koc
fb853de554 fix(scripts): preserve native pnpm exec paths 2026-05-26 17:36:48 +02:00
Vincent Koc
e96cde7e14 fix(ci): bound docker pull smoke steps 2026-05-26 17:28:37 +02:00
Vincent Koc
5ef812293b fix(codex): bridge cli api-key auth into app-server 2026-05-26 17:19:50 +02:00
Peter Steinberger
0f605ee003 fix: update Discord voice to libopus-wasm 0.1.0
Updates Discord voice Opus callers to the published libopus-wasm 0.1.0 API, pins the Discord plugin dependency and lockfiles to that release, keeps the package freshness exception version-scoped, treats expected Discord receive-stream premature closes as normal stream ends, and includes routed OpenClaw transcript roots for local PR transcript discovery.\n\nProof: npm view libopus-wasm@0.1.0; pnpm install --lockfile-only --filter @openclaw/discord; Node encode/decode smoke with pkg 0.1.0 decoded=3840; node scripts/run-vitest.mjs extensions/discord/src/voice/audio.test.ts extensions/discord/src/voice/receive-recovery.test.ts; git diff --check; autoreview clean; live tmux gateway on e0fa3e3 joined Discord voice and processed realtime audio without decoder.decode or Premature close warning spam.
2026-05-26 16:17:53 +01:00
Vincent Koc
e89afa6afa fix(e2e): kill timed docker helper commands 2026-05-26 17:16:20 +02:00
Vincent Koc
dc0d4c263e fix(e2e): kill timed live docker runs 2026-05-26 17:03:57 +02:00
Vincent Koc
d54c90699f fix(ci): kill timed website installer docker steps 2026-05-26 16:51:43 +02:00
Vincent Koc
4ff5a6152c fix(scripts): trim macOS node bootstrap 2026-05-26 16:42:44 +02:00
Vincent Koc
cf6f9ad8a3 fix(ci): kill timed install smoke docker steps 2026-05-26 16:36:18 +02:00
Nimrod Gutman
19e4c37c37 feat(ios): show Talk voice mode (#86798)
Merged via squash.

Prepared head SHA: bd24da3f3b
Co-authored-by: ngutman <1540134+ngutman@users.noreply.github.com>
Co-authored-by: ngutman <1540134+ngutman@users.noreply.github.com>
Reviewed-by: @ngutman
2026-05-26 17:31:31 +03:00
Vincent Koc
35310dce8c fix(setup): kill timed image pulls when supported 2026-05-26 16:19:34 +02:00
Vincent Koc
8685dbd547 fix(test): default Vitest stall watchdog 2026-05-26 16:17:13 +02:00
Vincent Koc
d1c8f09b00 fix(ci): bound crabbox hydrate downloads 2026-05-26 15:57:01 +02:00
Marvinthebored
42ba297b0a fix(control-ui): guard stale overview usage refresh
Guard loadUsage in the Control UI overview secondary refresh so stale overview loads do not start the expensive usage.cost RPC after the user has navigated away. Active overview usage loading is preserved.

Fixes #86392.
Thanks @Marvinthebored for the report, live gateway proof, and patch.

Verification:
- CI=1 OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=120000 fnm exec --using v24.15.0 -- node scripts/run-vitest.mjs run ui/src/ui/app-settings.refresh-active-tab.node.test.ts --reporter=dot --pool=forks --no-file-parallelism
- GitHub PR checks green on d52d8d10da, including Real behavior proof and checks-node-core-ui.

Co-authored-by: Marvinthebored <262704729+Marvinthebored@users.noreply.github.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-05-26 14:54:38 +01:00
Vincent Koc
4d4e2ec256 fix(qa): require genai otel model spans (#86920) 2026-05-26 14:51:50 +01:00
Peter Steinberger
cac0b2db18 refactor: move transcripts into core
Move meeting notes into core transcripts, remove the bundled meeting-notes plugin/API, and require explicit transcripts.enabled before exposing the recording-capable tool.
2026-05-26 14:51:11 +01:00
Peter Steinberger
45feb37b13 fix(message-tool): hydrate structured reply attachments
Fix outbound message actions so structured attachments[] media participates in existing sandbox, local-root, and hydration checks. Single-attachment actions select structured attachments only when no top-level or plugin media source wins, while send collects all structured attachments. Proof: git diff --check; pnpm tsgo:core && pnpm tsgo:test:src; direct selector/hydration probe; autoreview clean.
2026-05-26 14:50:32 +01:00
Vincent Koc
ce61d224d8 fix(e2e): kill timed npm install process groups 2026-05-26 15:49:29 +02:00
Vincent Koc
c38b5033e6 fix(ci): kill timed workflow process groups 2026-05-26 15:40:44 +02:00
Vincent Koc
0cca7861c1 fix(e2e): kill timed setup process groups 2026-05-26 15:31:56 +02:00
Vincent Koc
d0dd8b8a41 fix(e2e): bound resource-sampled docker runs 2026-05-26 15:09:14 +02:00
Vincent Koc
295b5ea9ab fix(mac): fail closed on restart gateway check 2026-05-26 15:05:16 +02:00
Vincent Koc
8c7f226401 fix(e2e): time out live docker setup installs 2026-05-26 14:58:45 +02:00
Peter Steinberger
e37ac22fdd ci: resolve major node fallback versions 2026-05-26 13:47:19 +01:00
Peter Steinberger
50c7d780dc ci: add node download fallback 2026-05-26 13:47:19 +01:00
Peter Steinberger
4c6aeb9bb2 ci: use local node toolcache setup 2026-05-26 13:47:19 +01:00
Vincent Koc
9777526eaa fix(e2e): bound docker npm install phases 2026-05-26 14:46:05 +02:00
Peter Steinberger
84e4bff73b ci: restore pnpm store cache with corepack 2026-05-26 13:39:00 +01:00
Peter Steinberger
13f72e4102 ci: avoid pnpm setup action download 2026-05-26 13:39:00 +01:00
Vincent Koc
a17ac3ec9d fix(e2e): time out telegram package installs 2026-05-26 14:36:20 +02:00
Peter Steinberger
e549d0c235 ci: avoid unconditional bun action download 2026-05-26 13:31:21 +01:00
Peter Steinberger
8d6a6e9f89 ci: use unauthenticated workflow fetches 2026-05-26 13:28:32 +01:00
Peter Steinberger
df13d3a724 ci: avoid token-backed read checkouts 2026-05-26 13:28:32 +01:00
Vincent Koc
a07dc3896b fix(e2e): time out package npm installs 2026-05-26 14:18:58 +02:00
Peter Steinberger
30e59b4090 test: speed up slow CI regressions 2026-05-26 13:13:19 +01:00
Vincent Koc
dfe94ff048 fix(release): fail closed on cross-os agent turns 2026-05-26 14:06:02 +02:00
Vincent Koc
419178b9bc fix(e2e): reject corrupt plugin update false greens 2026-05-26 13:49:02 +02:00
Peter Steinberger
efebf6bfcf fix(qa): preserve corrupt auth profile files 2026-05-26 12:42:50 +01:00
Peter Steinberger
cb34175dfd fix(matrix): reject malformed integer cli values 2026-05-26 12:42:43 +01:00
Peter Steinberger
884d346999 fix(canvas): reject invalid snapshot formats 2026-05-26 12:42:36 +01:00
Peter Steinberger
13c6a3332c fix(browser): reject invalid wait load states 2026-05-26 12:42:24 +01:00
Vincent Koc
a3bb4fe814 fix(ci): time out website installer docker runs 2026-05-26 13:29:35 +02:00
adupdev
31a8fe7462 fix(discord): gate native built-in UI before owner auth
Fixes #86654
2026-05-26 12:28:32 +01:00
Vincent Koc
92fb79ee69 fix(ci): fail Testbox changed-check delegation 2026-05-26 13:24:42 +02:00
Vincent Koc
30c4489af4 fix(ci): time out install smoke docker runs 2026-05-26 13:22:20 +02:00
Vincent Koc
94a04e1aa6 ci(release): retry transient GitHub API suspension errors (#86859) 2026-05-26 12:17:38 +01:00
Vincent Koc
8307e2f762 fix(podman): time out detached launches 2026-05-26 13:12:53 +02:00
Peter Steinberger
5b49433535 Auto-scale live tool result caps (#86857)
* fix: auto-scale live tool result cap

* fix: auto-scale live tool result cap
2026-05-26 12:11:31 +01:00
Vincent Koc
c2b1d20c25 fix(podman): time out setup image pulls 2026-05-26 12:56:35 +02:00
Peter Steinberger
18ff19e043 perf: use typed arrays for audio codec loops (#86856) 2026-05-26 11:51:21 +01:00
Vincent Koc
f0599fddac fix(e2e): time out live image pulls 2026-05-26 12:48:38 +02:00
Vincent Koc
fe9f28f520 fix(mac): require dist dSYM artifacts 2026-05-26 12:46:46 +02:00
Peter Steinberger
71e7a1fd7d docs: update changelog for testbox delegation 2026-05-26 11:44:06 +01:00
Vincent Koc
92082723f7 fix(e2e): time out installer smoke containers 2026-05-26 12:36:06 +02:00
Peter Steinberger
e20b8d70a6 fix: simplify testbox changed-check delegation 2026-05-26 11:33:09 +01:00
Vincent Koc
198d0a56d3 fix(mac): require packaged app resources 2026-05-26 12:27:47 +02:00
Peter Steinberger
11512b1257 test: update docker stats helper expectations 2026-05-26 11:26:18 +01:00
Vincent Koc
d1f2eb0709 fix(e2e): time out live Docker runs 2026-05-26 12:24:13 +02:00
Vincent Koc
e8cb2b5ab3 fix(mac): remove unused codesign entitlements 2026-05-26 12:19:42 +02:00
Vincent Koc
dcf0941cd6 fix(docker): time out setup image pulls 2026-05-26 12:14:18 +02:00
Vincent Koc
da16a966c3 fix(mac): fail closed on missing staple app 2026-05-26 12:13:04 +02:00
Vincent Koc
4ebc13abe1 fix(qa-slack): preserve failure debug artifacts 2026-05-26 11:09:52 +01:00
Vincent Koc
f1ceed94db fix(e2e): time out standalone Docker smokes 2026-05-26 12:07:04 +02:00
Vincent Koc
68f877ef66 fix(mac): clean codesign entitlement temps 2026-05-26 12:06:18 +02:00
Vincent Koc
1c5b8353d6 fix(e2e): time out install smoke Docker copies 2026-05-26 11:57:32 +02:00
Vincent Koc
7aedff8fbb fix(mac): fail closed on dmg plist reads 2026-05-26 11:54:47 +02:00
Vincent Koc
f2ad94ec9a fix(e2e): route gateway network client through Docker helper 2026-05-26 11:49:17 +02:00
Vincent Koc
8e110a2122 fix(mac): fail closed on dist plist reads 2026-05-26 11:44:05 +02:00
Peter Steinberger
4c8e9da033 test(codex): widen app-server wait timeout type 2026-05-26 10:42:30 +01:00
Vincent Koc
55af31e0c6 fix(e2e): time out Docker image reuse probes 2026-05-26 11:42:02 +02:00
Peter Steinberger
4f1cd8eb00 docs: clarify compatibility defaults 2026-05-26 10:39:21 +01:00
Vincent Koc
e295c86dbc fix(e2e): route named container cleanup through helper 2026-05-26 11:32:25 +02:00
Vincent Koc
91080fde68 fix(mac): fail closed on plist stamp errors 2026-05-26 11:28:51 +02:00
Vincent Koc
4838e704a0 fix(e2e): route focused docker smokes through run helper 2026-05-26 11:24:15 +02:00
Alex Knight
21aebd5fbc fix(mattermost): tag typed text slash control commands
Tag authorized Mattermost typed text-slash control commands with CommandSource: text so existing explicit-command source-reply delivery bypasses message_tool_only suppression for /new, /reset, ACP reset, and soft-reset acknowledgement replies.

Remove the normal PR changelog edit flagged by review and keep release-note context in the PR body/squash message. Tighten the regression test to exercise the leading-space Mattermost text-post path used to bypass native slash handling and assert the normalized command body.

Local proof: node scripts/run-vitest.mjs extensions/mattermost/src/mattermost/monitor.inbound-system-event.test.ts src/auto-reply/command-turn-context.test.ts src/auto-reply/reply/source-reply-delivery-mode.test.ts src/auto-reply/reply/commands-reset-hooks.test.ts; git diff --check origin/main..HEAD; oxfmt check; autoreview clean.

CI: PR run 26443271650 passed relevant checks. Ignored check-test-types failure because the exact same extensions/codex/src/app-server/run-attempt.test.ts TS2345 failure is already present on main run 26442926352 at the PR base.

Fixes #86664.
2026-05-26 19:18:41 +10:00
Vincent Koc
29919cbec5 fix(e2e): route sampled docker runs through helpers 2026-05-26 11:14:41 +02:00
Vincent Koc
90bcec9fa4 fix(e2e): clean package docker artifacts on setup failure 2026-05-26 11:06:29 +02:00
Peter Steinberger
0e733795f4 ci(release): include performance run in validation manifest 2026-05-26 10:03:41 +01:00
Vincent Koc
99032f0354 test(e2e): harden release media memory smoke 2026-05-26 10:58:49 +02:00
Vincent Koc
f63754b314 fix(e2e): clean package onboarding artifacts 2026-05-26 10:50:23 +02:00
Vincent Koc
b34e1b32d8 fix(e2e): honor Docker harness run timeouts 2026-05-26 10:42:34 +02:00
Omar Shahine
9434228cdc fix(imessage): dedupe accounts sharing the local Messages source (#86705)
Merged via squash.

Prepared head SHA: fcfe97d7c8
Co-authored-by: omarshahine <10343873+omarshahine@users.noreply.github.com>
Co-authored-by: omarshahine <10343873+omarshahine@users.noreply.github.com>
Reviewed-by: @omarshahine
2026-05-26 01:39:12 -07:00
Vincent Koc
21000a3da7 fix(scripts): detect shell-wrapped changed gates 2026-05-26 10:24:22 +02:00
Vincent Koc
3f6b63aa1d fix(codex): preserve sandbox bootstrap path style 2026-05-26 10:21:47 +02:00
Peter Steinberger
c5530c798c perf: skip canonical session migration parses 2026-05-26 09:18:14 +01:00
Vincent Koc
d3bbfa1f5a fix(e2e): clean skill install package mounts 2026-05-26 10:10:28 +02:00
Vincent Koc
a5653c0ce9 fix(e2e): clean Codex plugin live artifacts 2026-05-26 10:02:05 +02:00
Vincent Koc
b93cee45d0 fix(e2e): clean sampled Docker logs on failure 2026-05-26 09:52:42 +02:00
Peter Steinberger
3548cff14b refactor: migrate validators to TypeBox (#86639)
* refactor: migrate validators to typebox

* fix: preserve json schema resource refs

* chore: clean schema preflight recursion

* refactor: remove lobster ajv shim

* fix: support schema array refs

* fix: validate schema dependencies

* fix: preserve schema contract checks

* fix: support same-document schema refs

* fix: preserve untyped map defaults

* fix: preserve schema default semantics

* test: avoid thenable schema literals

* test: build conditional schema key

* fix: defer resource id refs to typebox

* fix: reject invalid schema enum metadata

* fix: preserve default branch semantics

* fix: resolve schema resource refs

* fix: narrow conditional default fallback

* fix: preserve uri format validation

* fix: preserve validator compatibility

* test: avoid ajv cache lint violation

* fix: preserve typebox validation diagnostics

* fix: validate defaulted conditional schemas

* fix: normalize mcp draft schemas

* fix: preserve tuple schema defaults

* fix: resolve relative schema refs

* fix: scope typebox format semantics

* fix: align conditional format defaults

* fix: decode schema pointer refs

* fix: filter grouped secretref diagnostics

* fix: preserve default conditional compatibility

* fix: preserve nullable schema compatibility

* fix: settle defaults before conditionals

* fix: preserve default validation invariants

* fix: validate dynamic schema refs

* fix: reject malformed nullable schemas
2026-05-26 08:45:28 +01:00
Vincent Koc
b377618fae fix(e2e): clean package mount tarballs 2026-05-26 09:43:40 +02:00
Vincent Koc
437a9e9171 fix(scripts): format auth expiries on macos 2026-05-26 09:32:24 +02:00
Vincent Koc
abc7b7b331 fix(e2e): clean functional Docker build inputs 2026-05-26 09:29:57 +02:00
Omar Shahine
2e17003165 Fix iMessage image attachment roots (#86569)
* fix imessage image attachment roots

* fix media tool inbound wildcard roots

* docs(changelog): add iMessage image attachment root fix entry for #86569

---------

Co-authored-by: Omar Shahine <10343873+omarshahine@users.noreply.github.com>
2026-05-26 00:22:12 -07:00
Peter Steinberger
918472a27b chore(release): refresh plugin sdk api baseline 2026-05-26 08:21:07 +01:00
Vincent Koc
4a1d772f3d fix(e2e): fail on invalid test state payloads 2026-05-26 09:15:06 +02:00
Peter Steinberger
4beadbf951 ci(release): apply exact extension batch excludes 2026-05-26 08:08:50 +01:00
Vincent Koc
6c5b39291f fix(installer): reject invalid shell options 2026-05-26 08:51:50 +02:00
Peter Steinberger
3b023e9bdb ci(release): pass vitest batch options before roots 2026-05-26 07:50:52 +01:00
Peter Steinberger
a3cd90fb5a ci(release): exclude codex app-server integration from plugin prerelease 2026-05-26 07:36:48 +01:00
Omar Shahine
17f7ef5c0f fix(imessage): send group media via attachment command (#86770)
* fix(imessage): send group media via attachment command

* fix(imessage): preserve media rpc fallback

---------

Co-authored-by: Omar Shahine <10343873+omarshahine@users.noreply.github.com>
2026-05-25 23:31:27 -07:00
Vincent Koc
41eef4a796 test(e2e): assert release upgrade installs candidate 2026-05-26 08:27:23 +02:00
Peter Steinberger
a46556a6c2 ci(release): serialize plugin prerelease extension batch 2026-05-26 07:15:56 +01:00
Vincent Koc
81f62a689b fix(scripts): add docker e2e scheduler help 2026-05-26 08:07:36 +02:00
Peter Steinberger
083377adb8 test(codex): wait for diagnostic event locally 2026-05-26 06:53:40 +01:00
Vincent Koc
4b03e07294 test(e2e): assert release plugin uninstall removes files 2026-05-26 07:53:20 +02:00
Vincent Koc
16d137dce6 test(telegram): use platform temp path in bot harness 2026-05-26 07:49:19 +02:00
Omar Shahine
3452382cc0 fix(imessage): seed direct DM history (#86706)
* fix(imessage): seed direct DM history

* docs(imessage): clarify DM history override seeding

---------

Co-authored-by: Omar Shahine <10343873+omarshahine@users.noreply.github.com>
2026-05-25 22:38:32 -07:00
Peter Steinberger
11b1b7c888 test(codex): complete diagnostic turn explicitly 2026-05-26 06:32:54 +01:00
Vincent Koc
5c3fb1f9d1 test(scripts): make run-vitest test Windows-safe 2026-05-26 07:28:34 +02:00
Peter Steinberger
c04c03f8e9 test: restore auth regression coverage 2026-05-26 06:23:13 +01:00
Vincent Koc
505aca9ef7 fix(test): reject missing explicit vitest files 2026-05-26 07:06:55 +02:00
Vincent Koc
5174d9744e test(plugins): canonicalize plugin install assertion paths 2026-05-26 07:04:41 +02:00
clawsweeper[bot]
23e9bc8c0b fix(diagnostics): track model stream progress (#86757)
Summary:
- The PR updates diagnostics to mark streamed model chunks as run progress, keeps silent model calls abortable after the stuck-session timeout, and adds regression coverage for stream progress and recovery behavior.
- PR surface: Source +54, Tests +229. Total +283 across 6 files.
- Reproducibility: yes. at source level: current main tracks model-call start/end activity but streamed chunks ... covery keys on stale lastProgressAgeMs. I did not run a live local-provider repro in this read-only review.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(diagnostics): track model stream progress
- PR branch already contained follow-up commit before automerge: test(diagnostics): cover silent local model aborts
- PR branch already contained follow-up commit before automerge: fix(diagnostics): skip stream progress when disabled

Validation:
- ClawSweeper review passed for head fcc74d9869.
- Required merge gates passed before the squash merge.

Prepared head SHA: fcc74d9869
Review: https://github.com/openclaw/openclaw/pull/86757#issuecomment-4540111930

Co-authored-by: Onur Solmaz <2453968+osolmaz@users.noreply.github.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: osolmaz
Co-authored-by: osolmaz <2453968+osolmaz@users.noreply.github.com>
2026-05-26 04:47:11 +00:00
Andy Ye
711e963723 Preserve runtime external auth snapshots (#85558)
Summary:
- The PR adds runtime-only external OAuth provenance to auth-profile stores, updates save/merge/read paths to  ... e profiles in active snapshots while filtering disk persistence, and expands auth-profile regression tests.
- PR surface: Source +381, Tests +974. Total +1355 across 8 files.
- Reproducibility: yes. from source: current main writes the disk-filtered localStore into an existing runtime ... tches the reported credential drop path. I did not run a failing current-main repro in this read-only pass.

Automerge notes:
- PR branch already contained follow-up commit before automerge: Preserve runtime external auth snapshots

Validation:
- ClawSweeper review passed for head a73074ed45.
- Required merge gates passed before the squash merge.

Prepared head SHA: a73074ed45
Review: https://github.com/openclaw/openclaw/pull/85558#issuecomment-4523577269

Co-authored-by: Andy Ye <35905412+TurboTheTurtle@users.noreply.github.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: takhoffman
Co-authored-by: takhoffman <781889+takhoffman@users.noreply.github.com>
2026-05-26 04:41:59 +00:00
Vincent Koc
7db4b3db41 fix(test): prepare macos runner tmpdir 2026-05-26 06:24:46 +02:00
Peter Steinberger
c14c043be7 test(agents): stabilize yielded exec timeout test 2026-05-26 05:21:59 +01:00
Peter Steinberger
3bb4be23c0 test: stabilize media fallback and background timeout tests 2026-05-26 05:19:50 +01:00
Liz Zhang
72a7d6a8dc fix(whatsapp): warn once when group inbound dropped for missing channels.whatsapp.groups entry (#83833)
Merged via squash.

Prepared head SHA: 8fc5243210
Co-authored-by: zhang-liz <13132583+zhang-liz@users.noreply.github.com>
Co-authored-by: mcaxtr <7562095+mcaxtr@users.noreply.github.com>
Reviewed-by: @mcaxtr
2026-05-26 01:15:24 -03:00
Peter Steinberger
e752f9bca1 chore(release): refresh plugin sdk api baseline 2026-05-26 05:03:33 +01:00
Peter Steinberger
c43ed9e4fe test(whatsapp): stabilize media format expectations 2026-05-26 05:03:22 +01:00
Vincent Koc
1e9b6b7627 test(qqbot): make OPENCLAW_HOME media test Windows-safe 2026-05-26 05:52:05 +02:00
Vincent Koc
a9bf582684 fix(test): forward installer smoke controls 2026-05-26 05:51:05 +02:00
Peter Steinberger
21aefb877a test: align image fast path expectations 2026-05-26 04:48:20 +01:00
Peter Steinberger
c4f0682396 test: align pnpm cache workflow assertion 2026-05-26 04:41:44 +01:00
Peter Steinberger
4118a32aad test: enforce per-test ci threshold 2026-05-26 04:35:20 +01:00
Ayaan Zaidi
4fdf61753a ci(mantis): pass crabbox capacity regions 2026-05-26 09:02:11 +05:30
Peter Steinberger
bc3d6bafae ci: disable pnpm action cache on Windows 2026-05-26 04:31:33 +01:00
Gio Della-Libera
17ab9b967c fix(agents): skip wildcard catalog metadata refs (#86524)
* fix(agents): skip wildcard catalog metadata refs

* fix(models): skip wildcard configured rows
2026-05-25 20:22:32 -07:00
Vincent Koc
947febb2fb fix(test): bootstrap macos script stdin 2026-05-26 05:17:50 +02:00
Peter Steinberger
bee8ad34a0 test(codex): avoid app-server diagnostic notification race 2026-05-26 04:17:16 +01:00
clawsweeper[bot]
7fbca96a0c fix(embedded-runner): preserve provider errors on cleanup takeover (#84321)
Summary:
- The PR preserves provider-facing embedded-runner prompt errors when cleanup detects session takeover, keeps the takeover signal fatal for fallback, and adds focused regressions.
- PR surface: Source +52, Tests +92. Total +144 across 5 files.
- Reproducibility: yes. Source inspection shows current main can let cleanup takeover replace a prior prompt/p ... rror and can normalize a provider-looking takeover wrapper before fallback sees it as coordination failure.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(embedded-runner): preserve takeover during fallback
- PR branch already contained follow-up commit before automerge: fix(clawsweeper): address review for automerge-openclaw-openclaw-8405…

Validation:
- ClawSweeper review passed for head 050c779cfa.
- Required merge gates passed before the squash merge.

Prepared head SHA: 050c779cfa
Review: https://github.com/openclaw/openclaw/pull/84321#issuecomment-4492087335

Co-authored-by: abnershang <abner.shang@gmail.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: takhoffman
Co-authored-by: takhoffman <781889+takhoffman@users.noreply.github.com>
2026-05-26 03:09:26 +00:00
Marcus Castro
bcde7b138a fix(agents): handle preflight compaction no-op budgets (#86709)
* fix(auto-reply): accept no-op preflight compaction

* fix(agents): clamp compaction runtime budget

* fix(agents): clamp queued compaction budget
2026-05-26 00:02:18 -03:00
Sliverp
0d23c3b4e1 fix: make QQ Bot media paths respect OPENCLAW_HOME configuration (#85309)
* fix: make QQ Bot media paths respect `OPENCLAW_HOME` configuration

* docs(changelog): note QQ Bot OPENCLAW_HOME media fix (#83562)
2026-05-26 11:01:39 +08:00
Vincent Koc
a695c28bfb fix(tooling): skip gauntlet declaration prebuild 2026-05-26 05:01:03 +02:00
clawsweeper[bot]
c9d0464ed1 fix(control-ui): support raw edits from editable config (#86726)
Summary:
- Merged fix(control-ui): support raw edits from editable config after ClawSweeper review.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(control-ui): support raw edits from editable config

Validation:
- ClawSweeper review passed for head befbe16362.
- Required merge gates passed before the squash merge.

Prepared head SHA: befbe16362
Review: https://github.com/openclaw/openclaw/pull/86726#issuecomment-4539541885

Co-authored-by: BlackFrameAI <122847831+BlackFrameAI@users.noreply.github.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: takhoffman
Co-authored-by: takhoffman <781889+takhoffman@users.noreply.github.com>
2026-05-26 02:58:20 +00:00
Omar Shahine
5a33378f9c revert: iMessage group media attachment command (#86734)
Co-authored-by: Omar Shahine <10343873+omarshahine@users.noreply.github.com>
2026-05-25 19:47:16 -07:00
Peter Steinberger
609d70d35e fix(release): stabilize beta validation after rebase 2026-05-26 03:30:54 +01:00
Vincent Koc
4738d0a296 fix(test): measure kitchen sink gateway children 2026-05-26 04:29:42 +02:00
Marcus Castro
34d862d45d fix(whatsapp): restore ack emoji identity fallback (#86697) 2026-05-25 23:25:00 -03:00
Omar Shahine
f32273257c fix(imessage): send group media via attachment command
* fix(imessage): send chat media via attachment command

* fix(imessage): satisfy bundled lint

---------

Co-authored-by: Omar Shahine <10343873+omarshahine@users.noreply.github.com>
2026-05-25 19:24:37 -07:00
Vincent Koc
eab8d29db2 fix(test): harden plugin gauntlet proof 2026-05-26 04:23:04 +02:00
Peter Steinberger
93015982d3 fix(release): stabilize beta validation after main rebase 2026-05-26 03:06:26 +01:00
Peter Steinberger
6f57286678 refactor: use Rastermill for image processing (#86621)
* refactor: use Rastermill for image processing

* docs: clarify autoreview heartbeat patience

* refactor: use simplified rastermill api

* fix: preserve rastermill media safety boundaries

* build: update rastermill api pin

* build: use published rastermill package
2026-05-26 02:54:49 +01:00
Peter Steinberger
0c5f622f9a perf(discord): use libopus-wasm for voice opus 2026-05-26 02:53:29 +01:00
clawsweeper[bot]
3d0659433e fix(build): pin synthetic auth runtime dist entry (#86714)
Summary:
- Adds `plugins/synthetic-auth.runtime` as an explicit tsdown dist entry and adds a regression test tying PI model-discovery synthetic-auth imports to that stable entry.
- PR surface: Tests +22, Other +1. Total +23 across 2 files.
- Reproducibility: yes. as a source-reproducible package-build path: current main imports synthetic-auth from  ... y. The PR proof covers emitted production `dist/` imports, though it did not run a live scheduled cron job.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(build): pin synthetic auth runtime dist entry

Validation:
- ClawSweeper review passed for head cb99947919.
- Required merge gates passed before the squash merge.

Prepared head SHA: cb99947919
Review: https://github.com/openclaw/openclaw/pull/86714#issuecomment-4538919657

Co-authored-by: Andy Ye <35905412+TurboTheTurtle@users.noreply.github.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: takhoffman
Co-authored-by: takhoffman <781889+takhoffman@users.noreply.github.com>
2026-05-26 01:51:50 +00:00
clawsweeper[bot]
fddca995e8 fix(plugin-sdk): preserve string-const unions as flat enum for deepseek tool schemas (#86712)
Summary:
- This PR changes DeepSeek provider tool-schema normalization to convert multi-value string const unions into flat string enums, with regression coverage for pure, nullable, and single-const union cases.
- PR surface: Source +27, Tests +84. Total +111 across 2 files.
- Reproducibility: yes. source-level reproduction is high confidence: current main selects only the first non-null anyOf/oneOf variant, and the linked source PR proof shows before/after output for that exact schema shape.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(plugin-sdk): preserve string-const unions as flat enum for deepse…

Validation:
- ClawSweeper review passed for head 310d95e327.
- Required merge gates passed before the squash merge.

Prepared head SHA: 310d95e327
Review: https://github.com/openclaw/openclaw/pull/86712#issuecomment-4538892244

Co-authored-by: 1052326311 <1052326311@users.noreply.github.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: takhoffman
Co-authored-by: takhoffman <781889+takhoffman@users.noreply.github.com>
2026-05-26 01:43:06 +00:00
Vincent Koc
2e6ba44706 fix(perf): bound session transcript stat fanout 2026-05-26 03:39:45 +02:00
Vincent Koc
6984a823af fix(test): bound plugin gauntlet prebuilds 2026-05-26 03:38:09 +02:00
Peter Steinberger
743bce2c27 perf: speed up usage cost lookups 2026-05-26 02:28:30 +01:00
Alex Knight
f824e1596a Add OpenTelemetry LLM content spans (#86191)
* feat: add otel llm content spans

* fix: gate otel tool definitions separately

* fix(diagnostics): sanitize tool_call parts and truncate oversized OTEL content attributes

* fix: keep otel content truncation parseable

* fix: simplify codex model diagnostics

* fix(diagnostics): align opt-in GenAI span shape

* test(codex): align resume params after rebase

* fix(diagnostics): keep model content off shared event bus

* test(diagnostics): keep extension tests on sdk boundary

---------

Co-authored-by: Alex Knight <15041791+amknight@users.noreply.github.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-05-26 02:24:02 +01:00
Peter Steinberger
592f192bf0 chore: remove unused tracked assets 2026-05-26 02:21:58 +01:00
Peter Steinberger
010a79b5d8 fix(ui): refresh raw copy i18n baseline 2026-05-26 02:17:54 +01:00
Peter Steinberger
8f1f7901b9 fix(release): accept optional Discord voice decoder 2026-05-26 02:17:54 +01:00
Peter Steinberger
c410658725 fix(perf): tolerate passing filtered release gates 2026-05-26 02:17:54 +01:00
Peter Steinberger
e049105891 fix(release): stabilize beta validation tests 2026-05-26 02:17:54 +01:00
Vincent Koc
f2142ebf3a fix(packaging): bound dist inventory filesystem scans 2026-05-26 03:16:41 +02:00
Vincent Koc
669df88249 fix(test): remove image tool timeout slack 2026-05-26 03:04:54 +02:00
Fermin Quant
c9364f03dc fix(cron): accept opaque session target keys
Fixes #64030.

Allows cron `session:` targets to carry opaque session-store keys, including slash and backslash characters, while keeping cron job IDs on the stricter UUID/non-path contract. Adds regression coverage across cron normalization, cron service persistence, gateway cron validation, and related session target handling.

Thanks @ferminquant for the fix.

Verification:
- `git diff --check origin/main...HEAD`
- `OPENCLAW_VITEST_MAX_WORKERS=1 node scripts/run-vitest.mjs src/cron/session-target.test.ts src/cron/normalize.test.ts src/cron/service.jobs.test.ts src/cron/service/store.test.ts src/gateway/server-cron.test.ts src/gateway/server.cron.test.ts src/cron/run-log.test.ts src/gateway/protocol/cron-validators.test.ts src/agents/tools/message-tool.test.ts src/agents/tools/image-tool.custom-provider-auth.regression.test.ts --reporter dot` passed: 13 files, 347 tests.
- GitHub `checks-node-agentic-agents` reran green on `51949741a333363586ddfb4445b82116c3bcea43`.

Co-authored-by: Fermin Quant <ferminquant@hotmail.com>
2026-05-26 01:39:04 +01:00
Patrick Erichsen
24d58af560 fix: honor skill source install aliases (#84842) 2026-05-25 17:37:35 -07:00
Vincent Koc
6421808c27 fix(test): avoid message tool bundled channel loads 2026-05-26 02:29:28 +02:00
Vincent Koc
80aa6d77fc test(onboard): guard docker e2e resources 2026-05-26 02:26:28 +02:00
Peter Steinberger
d00d0a21c2 chore: bump OpenClaw to 2026.5.26 2026-05-26 01:26:00 +01:00
Peter Steinberger
321f06ad0e fix: stabilize discord voice receive recovery 2026-05-26 01:22:21 +01:00
Peter Steinberger
ee51169b20 perf: reduce session and auth cache hotpath work (#86678)
Move immutable session-store snapshot cloning/freezing off the write path and rebuild snapshots lazily on read. Resolve runtime external auth profiles once per auth-profile save instead of once per OAuth profile.

Proof: oxfmt targeted files; pnpm tsgo:core; pnpm check:test-types; node scripts/run-vitest.mjs src/config/sessions.cache.test.ts src/agents/auth-profiles.store.save.test.ts src/agents/auth-profiles/external-oauth.test.ts; autoreview clean.
2026-05-26 01:19:52 +01:00
Vincent Koc
9e93431ae9 fix(qa): stream gateway gauntlet prebuild output 2026-05-26 02:18:48 +02:00
brokemac79
56633e4f3c fix(cli): route plugin packaging recovery hints
Route invalid-config recovery output for source-only installed plugin packages to plugin packaging guidance instead of openclaw doctor --fix.

Validated with focused config/CLI/gateway/plugin tests, autoreview, Crabbox/Testbox E2E tbx_01ksgr80tnvvc13kv6t126yv78, and green PR CI on 3b3ce73d0f.

Thanks @brokemac79.
2026-05-26 01:13:20 +01:00
Deepflame
ea2496b00c perf(agents): reuse model manifest context
Reuse a lazy model manifest context across configured model resolution so common static defaults do not trigger manifest metadata loads, while keeping plugin-owned normalization available when aliases, provider rows, or OpenRouter compat paths need it.

Preserves exact alias behavior, auth-profile-suffixed alias behavior, provider inference from manifest-normalized configured refs, and existing plugin/runtime cache lifecycle rules.

Co-authored-by: Alyana <alyana@lumina.local>
2026-05-26 01:11:47 +01:00
Vincent Koc
ef8619d5f5 fix(diagnostics): expose missing telemetry signals (#86682) 2026-05-26 01:10:59 +01:00
Peter Steinberger
71e9eaab14 perf: avoid extra session snapshot cloning 2026-05-26 01:08:47 +01:00
Peter Steinberger
c59635ae97 fix: avoid compaction checkpoint transcript copies (#86666) 2026-05-26 00:59:20 +01:00
Peter Steinberger
6814525867 fix: preserve code mode failure output 2026-05-26 00:54:00 +01:00
Peter Steinberger
1514cc84cb test: avoid message tool discovery in send helper 2026-05-26 00:53:37 +01:00
Vincent Koc
6defcb0a40 fix(scripts): bound guard inventory file reads 2026-05-26 01:49:15 +02:00
Vincent Koc
60afca187d fix(test): isolate kitchen sink rpc home env 2026-05-26 01:46:51 +02:00
Kevin Lin
719ce7f96f feat(signal): support reaction approvals (#85894)
* feat(signal): support reaction approvals

* fix(signal): harden approval reaction bindings

* fix(signal): quiet native approval prompt flow

* test(prompts): refresh direct channel snapshots

* fix(signal): suppress duplicate exec approval prompts

* revert(reply): keep direct inbound metadata

* docs: add signal approval changelog

* test(prompts): restore direct channel snapshots

* fix(signal): allow defaultTo approval reactions
2026-05-25 16:44:12 -07:00
Vincent Koc
57748a66fd fix(scripts): bound source scan file reads 2026-05-26 01:43:43 +02:00
Vincent Koc
2a6b4ed3e2 test(ollama): support cloud api live smoke 2026-05-26 01:43:03 +02:00
Peter Steinberger
978a2d01da test: serialize agents tools vitest files 2026-05-26 00:42:46 +01:00
tanshanshan
3a4f2b17fc fix(auto-reply): use context-aware overflow reserve hints (#84399)
Use the effective runtime/model context when computing overflow recovery reserveTokensFloor hints, including uncataloged runtime refs, stale session windows, and heartbeat fallback cases.

Verification:
- pnpm test src/auto-reply/reply/agent-runner-execution.test.ts
- autoreview clean on final focused fixup; prior accepted findings addressed before push.
- CI passed on head e25b3e84f4 after rerunning cancelled jobs: preflight, critical quality network-runtime-boundary, security high, checks, Real behavior proof.

Co-authored-by: tanshanshan <tanshanshan@users.noreply.github.com>
2026-05-26 00:33:56 +01:00
Lellansin Huang
6c7b3f3f23 feat(gateway): forward OpenAI sampling params (#84094)
Forward OpenAI-compatible frequency_penalty, presence_penalty, and seed params through the gateway/chat-completions path while keeping Responses untouched.

Verification:
- pnpm test src/gateway/openai-http.test.ts src/agents/pi-embedded-runner/extra-params.sampling.test.ts src/agents/openai-transport-stream.test.ts
- CI passed on head 9abb9466d9 after rerunning cancelled jobs: preflight, critical quality network-runtime-boundary, security high, checks, docs, Real behavior proof.

Co-authored-by: lellansin <lellansin@gmail.com>
2026-05-26 00:33:26 +01:00
Peter Steinberger
068924e2d4 perf: cache model cost indexes
Cache configured model cost indexes for repeated session usage cost lookups while preserving in-place config mutation behavior via value-fingerprint invalidation. Raw pricing lookups now skip manifest model-id normalization as well as runtime/plugin normalization, keeping direct cost lookup off plugin metadata hot paths.

Verification:
- node scripts/run-vitest.mjs src/utils/usage-format.test.ts
- pnpm exec oxfmt --check src/utils/usage-format.ts src/utils/usage-format.test.ts
- pnpm lint --threads=8
- pnpm tsgo:core
- autoreview --mode local
- PR CI green on head 15c1e25d95
2026-05-26 00:29:55 +01:00
Peter Steinberger
5dc704361f fix: hide unsupported best effort message option 2026-05-26 00:27:57 +01:00
Peter Steinberger
bef0ba8f5a refactor: reuse realtime output activity in google meet (#86665) 2026-05-26 00:19:35 +01:00
Vincent Koc
84929e4265 fix(test): harden bundled plugin install sweep 2026-05-26 01:17:12 +02:00
Peter Steinberger
c87957db5e fix: prefer source public artifacts in source checkouts 2026-05-26 00:17:04 +01:00
Peter Steinberger
65a210553b test: type child process spawn mock 2026-05-26 00:11:40 +01:00
Vincent Koc
fe3374789f test(installer): cover rocky cli installs 2026-05-26 01:07:39 +02:00
Peter Steinberger
da831e2b8a docs: update changelog for landed fixes 2026-05-26 00:04:56 +01:00
Fermin Quant
399c692895 fix: dampen repeated device-required probes 2026-05-26 00:04:37 +01:00
Bryan Tegomoh
fc2d2d595c fix(ui): keep local file markdown links inert 2026-05-26 00:04:32 +01:00
Fermin Quant
342bde2af6 fix(update): avoid duplicate plugin smoke failures 2026-05-26 00:04:27 +01:00
Galin Iliev
d7361eff66 fix(gateway): cap retained compaction checkpoint bytes
Cap retained compaction checkpoint snapshots by total bytes per session while preserving the existing count cap.

The gateway now stats retained checkpoint snapshots inside the session-store writer before trimming, deletes older trimmed checkpoint files, and keeps the newest checkpoint available. Regression coverage uses real sparse checkpoint files to prove byte-budget cleanup.

Closes #84822.
2026-05-25 16:04:04 -07:00
Peter Steinberger
c1a026a976 fix: stabilize tests and reduce plugin memory churn 2026-05-26 00:01:30 +01:00
Peter Steinberger
1d21224de3 perf: reduce runtime metadata hotpath churn
Reduce runtime metadata hotpath churn by freezing loaded plugin metadata snapshots once and returning the memoized object without clone-on-hit. Reuse persisted package file signatures while preserving realpath containment, cache normalized Jiti alias maps by identity, and defer Discord realtime turn retention/logging until audio starts.

Verification:
- node scripts/run-vitest.mjs src/talk/turn-context-tracker.test.ts src/plugins/plugin-metadata-snapshot.memo.test.ts src/plugins/manifest-registry-installed.test.ts src/plugins/sdk-alias.test.ts src/plugins/installed-plugin-index-records.test.ts
- node scripts/run-vitest.mjs src/plugins/plugin-metadata-snapshot.memo.test.ts
- pnpm test extensions/discord/src/voice/manager.e2e.test.ts --testNamePattern "keeps realtime playback alive|interrupts realtime playback|does not interrupt realtime provider state"
- pnpm lint --threads=8
- pnpm exec oxfmt --check src/plugins/plugin-metadata-snapshot.ts src/plugins/plugin-metadata-snapshot.memo.test.ts src/plugins/manifest-registry-installed.ts src/plugins/installed-plugin-index-record-builder.ts src/plugins/sdk-alias.ts extensions/discord/src/voice/realtime.ts
- pnpm tsgo:core
- pnpm tsgo:extensions
- pnpm build
- autoreview --mode commit --commit HEAD
- PR CI green on head 7dd3e44a78
2026-05-25 23:59:45 +01:00
Peter Steinberger
a4f12699cf refactor: share realtime output activity tracking (#86661) 2026-05-25 23:51:34 +01:00
Peter Steinberger
acbdb8c373 fix(memory-wiki): bound compile page reads (#86660)
Summary
- Bound Memory Wiki compile-time page summary reads through the existing concurrency helper.
- Preserve deterministic result ordering before title sort and keep the helper in stop-on-error mode.
- Replaces #84458 because the fork branch does not allow maintainer edits and the contributor changelog entry needed removal.

Behavior addressed: Memory Wiki compile no longer starts one page-summary read per page without a bound.
Real environment tested: Local macOS source checkout, Node/pnpm repo environment.
Exact steps or command run after this patch: pnpm test extensions/memory-wiki/src/compile.test.ts; pnpm exec oxfmt --check --threads=1 extensions/memory-wiki/src/compile.ts extensions/memory-wiki/src/compile.test.ts; .agents/skills/autoreview/scripts/autoreview --mode branch --base origin/main --no-web-search --prompt "Review PR #84458 after maintainer fixup. Focus on memory-wiki compile page summary read concurrency, runTasksWithConcurrency result/error handling, ordering preservation, and test reliability."
Evidence after fix: compile.test.ts passed 10 tests; oxfmt reported clean; autoreview reported no accepted/actionable findings.
Observed result after fix: Page reads are executed through runTasksWithConcurrency with errorMode stop, successful results are consumed in input-index order, and the existing summary title sort remains deterministic.
What was not tested: Full repository suite.

Co-authored-by: zhengzuo0-ai <zheng.zuo0@gmail.com>
2026-05-25 23:49:43 +01:00
Vincent Koc
00f9809531 test(qa-matrix): use larger media coverage jpeg 2026-05-25 23:45:04 +01:00
YBoy
bec7d56b73 fix(cli): reject unknown command help roots (#81083) (thanks @YB0y)
Behavior addressed: Unknown CLI command roots now error consistently even when --help or --version is appended, while legitimate built-in help fast paths still render normally.

Real environment tested: Local OpenClaw source checkout plus GitHub workflow run-level status.

Exact steps or command run after this patch: pnpm test src/cli/run-main.exit.test.ts src/cli/argv.test.ts src/cli/argv-invocation.test.ts; pnpm exec oxfmt --check --threads=1 src/cli/run-main.ts src/cli/run-main.exit.test.ts; autoreview --mode branch --base origin/main --no-web-search.

Evidence after fix: Focused CLI test shards passed 178 tests; formatter clean; autoreview reported no accepted/actionable findings; GitHub CI run 26422344121 and CodeQL Critical Quality run 26422344090 completed successfully.

Observed result after fix: `openclaw foo --help` and `openclaw foo --version` reject before proxy/program startup, while known help fast paths remain ahead of the unknown-root guard.

What was not tested: Full local build; contributor PR body already supplied build/CLI command proof before rebase.

Co-authored-by: YB0y <brianandez6@gmail.com>
2026-05-25 23:38:43 +01:00
Peter Steinberger
68ab48b179 test: improve test profiling helpers 2026-05-25 23:36:34 +01:00
Peter Steinberger
ec7ad3b4ac perf: reduce fuzzy matching allocations 2026-05-25 23:36:07 +01:00
Peter Steinberger
1531fe2525 perf: reduce runtime cache churn 2026-05-25 23:35:06 +01:00
Peter Steinberger
0164fd5e99 refactor: reuse forced consult coordinator in discord voice (#86656) 2026-05-25 23:34:17 +01:00
Iftekhar Uddin
5e8a9a905d fix(scripts): drain codex-cli metadata stdout (#84239) (thanks @IftekharUddin)
Behavior addressed: The codex-cli metadata branch no longer calls process.exit(0) immediately after writing stdout, and it still emits exactly one unsupported-backend JSON object.

Real environment tested: Local OpenClaw source checkout on macOS with Node/tsx.

Exact steps or command run after this patch: pnpm test test/scripts/print-cli-backend-live-metadata.test.ts test/scripts/docker-build-helper.test.ts; node --import tsx scripts/print-cli-backend-live-metadata.ts codex-cli | python3 -c 'import sys,json; print(json.load(sys.stdin)["provider"])'; autoreview --mode branch --base origin/main --no-web-search.

Evidence after fix: Focused tooling test shard passed 2 files / 23 tests; direct pipe parse printed codex-cli; autoreview reported no accepted/actionable findings; PR status rollup was clean.

Observed result after fix: stdout is parseable as a single JSON payload and the normal metadata path is skipped for codex-cli.

What was not tested: Live provider metadata paths beyond the focused existing test coverage.

Co-authored-by: Iftekhar Uddin <ifuddin3@gmail.com>
2026-05-25 23:27:13 +01:00
Vincent Koc
75ac0b5ed9 fix(test): avoid discord voice tts activation tax 2026-05-26 00:19:17 +02:00
Dmitry Golubev
0f35ec29d3 fix(codex): disable native thread personality (#85891) (thanks @lastguru-net)
Behavior addressed: Native Codex app-server threads now disable Codex's built-in personality on thread/start, thread/resume, turn/start, bound conversation turns, and /btw side-thread forks so OpenClaw agent workspace identity stays authoritative.

Real environment tested: Local OpenClaw source checkout plus GitHub CI on PR #85891.

Exact steps or command run after this patch: pnpm test extensions/codex/src/app-server/thread-lifecycle.test.ts extensions/codex/src/app-server/side-question.test.ts extensions/codex/src/conversation-binding.test.ts extensions/codex/src/app-server/schema-normalization-runtime-contract.test.ts; pnpm check:docs; pnpm prompt:snapshots:check; OPENCLAW_ADDITIONAL_BOUNDARY_SHARD=1/4 OPENCLAW_ADDITIONAL_BOUNDARY_CONCURRENCY=4 node scripts/run-additional-boundary-checks.mjs.

Evidence after fix: Focused Codex test shard passed 4 files / 79 tests; docs check passed; prompt snapshots are current; CI passed all code/quality checks, with only Real behavior proof failing as unrelated proof-bot gating for this non-channel change.

Observed result after fix: App-server request snapshots and unit tests include personality: "none" on native Codex start/resume/turn/fork paths.

What was not tested: A live Codex app-server model run was not executed.

Co-authored-by: Beru <beru@lastguru.lv>
2026-05-25 23:15:03 +01:00
Peter Steinberger
fda0141a01 Refactor realtime voice turn context tracking (#86650)
* refactor: share realtime turn context tracking

* chore: track realtime voice sdk api baseline

* fix: preserve pruned realtime turn handle state
2026-05-25 23:13:27 +01:00
UB
48adcb162c test(discord): cover deliver-lambda abort-skip path via processDiscordMessage integration 2026-05-25 23:11:54 +01:00
UB
3a48366f3e fix(discord): surface silent reply-delivery skips and remove runtime.error optional-chain 2026-05-25 23:11:54 +01:00
Peter Steinberger
75c6cf2966 docs: update changelog for landed bug fixes 2026-05-25 23:08:05 +01:00
Vincent Koc
0f54221f86 test(qa-matrix): use valid media coverage jpeg 2026-05-25 23:07:11 +01:00
Sebastien Tardif
0a38932ed9 fix(gmail-watcher): strip listeners from old process after settleProcess to prevent late-exit respawn 2026-05-25 23:07:06 +01:00
Sebastien Tardif
94968c83c6 fix(gmail-watcher): prevent TDZ in settleProcess and guard exit handler against stale child respawn 2026-05-25 23:07:06 +01:00
Sebastien Tardif
2ffd7a7172 fix(hooks): stop existing Gmail watcher before re-entry to prevent leaks
renewInterval is not cleared on re-entry to startGmailWatcher,
leaking the previous timer. Each config reload adds another
interval that fires independently.

Clear existing watcher state before starting a new one.

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
2026-05-25 23:07:06 +01:00
Earl Co
7b30291cc4 fix(codex): honor yolo app-server approval policy 2026-05-25 23:06:54 +01:00
Peter Steinberger
116c600f60 fix: treat zero-rate usage cost as unknown 2026-05-25 23:06:44 +01:00
Michael Zelbel
9c79a0f8f4 fix(usage-cost): invalidate durable cache on missing-cost semantics change
Bump USAGE_COST_CACHE_VERSION 3->4 so a warm .usage-cost-cache.json written by a
pre-change build is rebuilt instead of serving stale complete-$0 totals after
upgrade (the new missing-cost branch otherwise only runs when a file is rescanned).
Add a regression test asserting an older-version cache is treated as stale for an
unpriced session.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 23:06:44 +01:00
Michael Zelbel
16702496c6 fix(usage-cost): only flag catalog-default zeros, preserve operator-configured $0
Address review: distinguish unknown pricing from an intentional free price. A
turn's all-zero cost is treated as unknown (counted toward missingCostEntries)
only when the operator did NOT explicitly configure the model's price under
models.providers -- i.e. the zero is a generated-catalog default (codex/gpt-5.x),
not a deliberate $0. Operator-configured zero-cost models keep reporting a
complete $0.

Adds resolveConfiguredModelCost() to read config-only pricing, and regression
tests for both paths (unconfigured unknown -> missing; configured free -> $0).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 23:06:44 +01:00
Michael Zelbel
6e85869161 fix(usage-cost): preserve transport-recorded positive cost for unpriced models
Only treat an unpriced (all-zero) model's turn as missing when it has no
trustworthy recorded cost (recorded cost is 0 or absent). A turn carrying a
real positive recorded cost is preserved, fixing a regression where priced
fixtures without explicit pricing config lost their recorded cost.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 23:06:44 +01:00
Michael Zelbel
1cc0a96df1 fix(usage-cost): surface unpriced-model spend as missingCostEntries instead of $0
Models that ship an all-zero cost block (e.g. codex gpt-5.5, whose Codex
backend exposes no per-token price) made usage-cost report totalCost: 0 with
missingCostEntries: 0 -- a confident, complete $0 -- so every budget/spike
safeguard keyed off totalCost was silently blind to real pay-per-token spend.

scanTranscriptFile now treats a resolved cost config with no positive per-token
rate (and no tiered pricing) as "pricing unknown": for turns that burned tokens
it drops the transport's fabricated $0 and surfaces the turn as a missing-cost
entry, mirroring the existing tiered-pricing override. Models with positive or
tiered pricing and zero-token entries are unaffected.

Verified on a real OpenClaw 2026.5.20 host (default openai/gpt-5.5, api_key):
1,780,235 tokens that previously reported missingCostEntries 0 now report 32.

Related: #85858

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 23:06:44 +01:00
Peter Steinberger
c4c80cea35 test(irc): cover transient channel join 2026-05-25 23:06:35 +01:00
Peter Steinberger
9cb1e4799c fix(irc): normalize channel route ids 2026-05-25 23:06:35 +01:00
Kaili
63dee51dfb fix(irc): store inbound channel routes as channel:#name and join before send 2026-05-25 23:06:35 +01:00
Vincent Koc
cd96542d37 fix(test): harden macos onboarding e2e 2026-05-26 00:02:41 +02:00
clawsweeper[bot]
55c9a6beea fix(agents): strip markdown code spans from IDENTITY.md values and labels (#86647)
Summary:
- The PR updates `src/agents/identity-file.ts` to normalize backtick-wrapped IDENTITY.md labels and values, and adds parser/merge regression tests in `src/agents/identity-file.test.ts`.
- PR surface: Source +8, Tests +28. Total +36 across 2 files.
- Reproducibility: yes. source-reproducible with high confidence: current main strips `*` and `_` but not back ... e unnormalized string. I did not run tests because this review was required to keep the checkout read-only.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(agents): strip markdown code spans from IDENTITY.md values and la…

Validation:
- ClawSweeper review passed for head 30c43defd6.
- Required merge gates passed before the squash merge.

Prepared head SHA: 30c43defd6
Review: https://github.com/openclaw/openclaw/pull/86647#issuecomment-4537456646

Co-authored-by: nayrosk <105997554+nayrosk@users.noreply.github.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: takhoffman
Co-authored-by: takhoffman <781889+takhoffman@users.noreply.github.com>
2026-05-25 22:00:36 +00:00
Vincent Koc
9be760fb37 test(qa): add collector-backed otel smoke 2026-05-25 23:51:17 +02:00
clawsweeper[bot]
99d96c1ff2 fix(memory-core): use CJK-aware tokenizer for dreaming dedupe (#80613) (#86645)
Summary:
- The PR extracts the CJK-aware memory tokenizer into a shared helper, routes dreaming dedupe through it, preserves MMR re-exports, and adds regression coverage for CJK and empty-token cases.
- PR surface: Source +15, Tests +96. Total +111 across 5 files.
- Reproducibility: yes. Current main has an ASCII-only tokenizeSnippet path in dreaming dedupe, and the source ... ction source bytes for the CJK failure modes; I did not run tests locally because this review is read-only.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(memory-core): use Array.toSorted for #80613 lint fix
- PR branch already contained follow-up commit before automerge: fix(memory-core): preserve dedupe identity when both snippets tokeniz…
- PR branch already contained follow-up commit before automerge: fix(memory-core): rename __testing to testing in CJK regression tests…
- PR branch already contained follow-up commit before automerge: fix(memory-core): use CJK-aware tokenizer for dreaming dedupe (#80613)

Validation:
- ClawSweeper review passed for head ca9c02734c.
- Required merge gates passed before the squash merge.

Prepared head SHA: ca9c02734c
Review: https://github.com/openclaw/openclaw/pull/86645#issuecomment-4537414471

Co-authored-by: MoerAI <friendnt@g.skku.edu>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
2026-05-25 21:50:55 +00:00
Peter Steinberger
3b0805414e ci: report memory metrics 2026-05-25 22:49:23 +01:00
Peter Steinberger
5b6d03e3e2 perf: reduce runtime cache churn
Reduce hot-path cache churn by reusing the active plugin metadata snapshot for manifest model-id normalization when safe, and by avoiding repeated JSON reparses for cached session stores while preserving clone semantics.

Verification:
- pnpm exec oxfmt --check src/plugins/manifest-model-id-normalization.ts src/plugins/manifest-model-id-normalization.test.ts src/config/sessions/store-cache.ts src/config/sessions.cache.test.ts
- node scripts/run-vitest.mjs src/config/sessions.cache.test.ts src/plugins/manifest-model-id-normalization.test.ts src/gateway/session-utils.subagent.test.ts
- pnpm tsgo:core
- autoreview clean
- PR CI green
2026-05-25 22:40:46 +01:00
Zee Zheng
0d4575a241 fix(pi-runner): flush blocks after compaction retry (#85288) (thanks @spacegeologist)
Behavior addressed: Embedded PI compaction retry now drains block replies again after the retry wait resolves, so retry-generated replies are not left behind while preserving aggregate-timeout fallback behavior.
Real environment tested: local OpenClaw focused Pi runner test shard plus contributor local live-output proof in the PR body.
Exact steps or command run after this patch: pnpm test src/agents/pi-embedded-runner/run/attempt.spawn-workspace.context-engine.test.ts src/agents/pi-embedded-runner/run/compaction-retry-aggregate-timeout.test.ts; .agents/skills/autoreview/scripts/autoreview --mode branch --base origin/main
Evidence after fix: 2 test files passed, 55 tests passed; final autoreview clean with no accepted/actionable findings.
Observed result after fix: the runner flushes before the compaction wait, waits for compaction retry, then performs a second idempotent flush when the wait resolves without timing out.
What was not tested: fresh external-channel live retry by this agent; PR retains contributor live-output proof for the delayed channel adapter path.

Thanks @spacegeologist.

Co-authored-by: zhengzuo0-ai <zheng.zuo0@gmail.com>
2026-05-25 22:27:29 +01:00
Vincent Koc
a122d804dd fix(gateway): abort stale agent runs on restart 2026-05-25 23:26:10 +02:00
Vincent Koc
4424dafe64 fix(ui): harden control e2e browser setup 2026-05-25 23:19:55 +02:00
Neerav Makwana
0f67dfd074 fix(telegram): keep overlapping DM replies deliverable (#85361) (thanks @neeravmakwana)
Behavior addressed: Telegram direct-message turns no longer drop an earlier overlapping normal reply, while authorized aborts and explicit/native/plugin/skill command turns still supersede active reply work.
Real environment tested: local OpenClaw focused Telegram test shard plus existing contributor Telegram screenshot/log proof in the PR body.
Exact steps or command run after this patch: pnpm test extensions/telegram/src/telegram-reply-fence.test.ts extensions/telegram/src/bot-message-dispatch.test.ts; .agents/skills/autoreview/scripts/autoreview --mode branch --base origin/main
Evidence after fix: 2 test files passed, 93 tests passed; final autoreview clean with no accepted/actionable findings.
Observed result after fix: overlapping normal Telegram DMs use non-interrupting reply fences and both final replies remain deliverable; direct /stop, authorized built-in commands, and explicit text/native command turns still supersede.
What was not tested: fresh live Telegram Desktop rerun by this agent; PR retains contributor screenshot/log proof and the Real behavior proof bot remains red despite proof labels.

Thanks @neeravmakwana.

Co-authored-by: Neerav Makwana <261249544+neeravmakwana@users.noreply.github.com>
2026-05-25 22:17:39 +01:00
Merlin
f4cfa012e1 fix(openai): route compaction through Codex auth provider (#86408)
* fix(openai): route compaction through codex auth provider

Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>

* fix(openai): honor default responses compaction threshold

Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>

* fix(openai): preserve codex runtime routing

* docs(changelog): note Codex routing fix

---------

Co-authored-by: Merlin <258679497+funmerlin@users.noreply.github.com>
Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-25 22:13:08 +01:00
Peter Steinberger
5dccba7405 refactor: share realtime forced consult coordination 2026-05-25 22:02:19 +01:00
Vincent Koc
f6a49a4e8a test(qa-lab): add runtime confidence reports 2026-05-25 22:00:21 +01:00
Peter Steinberger
cda7c30150 build: refresh dependency pins (#86628)
* build: refresh dependencies

* build: align pi fallback version
2026-05-25 21:55:46 +01:00
Peter Steinberger
9f7485e182 test: port release validation stabilizers 2026-05-25 21:50:49 +01:00
Bryan P
c51fa0d127 fix(cron): stop forcing message tool for delivery
Keep isolated cron announce delivery owned by runner fallback while leaving agent-initiated message sends optional. `delivery.mode: none` no longer forces message delivery, announce delivery skips fallback only after a verified same-target message-tool send, and prompt allowlist checks now match runtime tool policy normalization/group expansion.

Verified with focused cron tests, `check:changed`, autoreview, and PR CI on 7ab77bad97.

Thanks @bryanpearson.

Co-authored-by: bryanpearson <bryanmpearson@gmail.com>
2026-05-25 21:46:51 +01:00
Neerav Makwana
148db14736 fix(google): omit request config with cached content
Fix Gemini cached-content GenerateContent payloads so cached requests no longer resend request-level systemInstruction, tools, or toolConfig.

Covers explicit cachedContent and managed cacheRetention prompt caching; fixes #84919.

Proof: Real behavior proof passed on PR head 198a42bbc6 after live Gemini repro/fix evidence was added to the PR body. Focused tests and check:changed were already green.

Thanks @neeravmakwana.
2026-05-25 21:42:21 +01:00
Peter Steinberger
5a9673ecd7 test: stabilize release validation test harnesses 2026-05-25 21:35:29 +01:00
Vincent Koc
f1197ed6fc fix(test): bound kitchen sink command output 2026-05-25 22:28:56 +02:00
Peter Steinberger
4e9dac5e00 fix(discord): stabilize realtime wake-name feedback 2026-05-25 21:24:06 +01:00
Galin Iliev
b30f8e5290 test(config): guard legacy agentRuntime regression
Adds regression coverage for agents.defaults.agentRuntime schema acceptance and invalid-config doctor fix reachability.

The runtime behavior fix already landed on main in 5b9be2cdb1c01a2896783c52f5f0654c5f22a249; this PR locks the expected behavior with focused tests.

Closes #72872
2026-05-25 13:23:59 -07:00
Peter Steinberger
2afb8198c1 perf: precompute audio resample kernels
Precompute FIR resample kernels for common voice sample-rate conversions to avoid per-sample trigonometry while preserving output for tested ratios.\n\nVerification: node scripts/run-vitest.mjs extensions/voice-call/src/telephony-audio.test.ts; pnpm tsgo:core; autoreview --mode commit --commit HEAD; PR CI green.
2026-05-25 21:22:09 +01:00
Peter Steinberger
009b18c1f4 fix(codex): allow env api-key app-server bootstrap 2026-05-25 21:21:01 +01:00
Peter Steinberger
77d9ac30bb refactor: reuse shared coercion helpers (#86419)
* refactor: share talk event metric extraction

* refactor: reuse shared coercion helpers

* refactor: reuse shared primitive guards

* refactor: reuse shared record guard

* refactor: reuse shared primitive helpers

* refactor: reuse shared string guards

* refactor: reuse shared non-empty string guard

* refactor: share plugin primitive coercion helpers

* refactor: reuse plugin coercion helpers

* refactor: reuse plugin coercion helpers in more plugins

* refactor: reuse channel coercion helpers

* refactor: reuse monitor coercion helpers

* refactor: reuse provider coercion helpers

* refactor: reuse core coercion helpers

* refactor: reuse runtime coercion helpers

* refactor: reuse helper coercion in codex paths

* refactor: reuse helper coercion in runtime paths

* refactor: reuse codex app-server coercion helpers

* refactor: reuse codex record helpers

* refactor: reuse migration and qa record helpers

* refactor: reuse feishu and core helper guards

* refactor: reuse browser and policy coercion helpers

* refactor: reuse memory wiki record helper

* refactor: share boolean coercion helpers

* refactor: reuse finite number coercion

* refactor: reuse trimmed string list helpers

* refactor: reuse string list normalization

* refactor: reuse remaining string list helpers

* refactor: reuse string entry normalizer

* refactor: share sorted string helpers

* refactor: share string list normalization

* test: preserve command registry browser imports

* refactor: reuse trimmed list helpers

* refactor: reuse string dedupe helpers

* refactor: reuse local dedupe helpers

* refactor: reuse more string dedupe helpers

* refactor: reuse command string dedupe helpers

* refactor: dedupe memory path lists with helper

* refactor: expose string dedupe helpers to plugins

* refactor: reuse core string dedupe helpers

* refactor: reuse shared unique value helpers

* refactor: reuse unique helpers in agent utilities

* refactor: reuse unique helpers in config plumbing

* refactor: reuse unique helpers in extensions

* refactor: reuse unique helpers in core utilities

* refactor: reuse unique helpers in qa plugins

* refactor: reuse unique helpers in memory plugins

* refactor: reuse unique helpers in channel plugins

* refactor: reuse unique helpers in core tails

* refactor: reuse unique helper in comfy workflow

* refactor: reuse unique helpers in test utilities

* refactor: expose unique value helper to plugins

* refactor: reuse unique helpers for numeric lists

* refactor: replace index dedupe filters

* refactor: reuse string entry normalization

* refactor: reuse string normalization in plugin helpers

* refactor: reuse string normalization in extension helpers

* refactor: reuse string normalization in channel parsers

* refactor: reuse string normalization in memory search

* refactor: reuse string normalization in provider parsers

* refactor: reuse string normalization in qa helpers

* refactor: reuse string normalization in infra parsers

* refactor: reuse string normalization in messaging parsers

* refactor: reuse string normalization in core parsers

* refactor: reuse string normalization in extension parsers

* refactor: reuse string normalization in remaining parsers

* refactor: reuse string normalization in final parser spots

* refactor: reuse string normalization in qa media helpers

* refactor: reuse normalization in provider and media lists

* refactor: reuse normalization for remaining set filters

* refactor: reuse normalization in policy allowlists

* refactor: reuse normalization in session and owner lists

* refactor: centralize primitive string lists

* refactor: reuse lowercase entry helpers

* refactor: reuse sorted string helpers

* refactor: reuse unique trimmed helpers

* refactor: reuse string normalization helpers

* refactor: reuse catalog string helpers

* refactor: reuse remaining string helpers

* refactor: simplify remaining list normalization

* refactor: reuse codex auth order normalization

* chore: refresh plugin sdk api baseline

* fix: make shared string sorting deterministic

* chore: refresh plugin sdk api baseline

* fix: align host env security ordering
2026-05-25 21:20:41 +01:00
Peter Steinberger
a98660eebd fix(cron): preserve runtime snapshot for isolated delivery
Fix isolated cron delivery so agent-default derivation keeps using the paired runtime config snapshot, preserving resolved channel credentials such as Discord SecretRefs. Fixes #86545.
2026-05-25 21:10:14 +01:00
Vincent Koc
c55bee5ec7 fix(test): model active assistant failover attempts 2026-05-25 22:03:03 +02:00
Peter Steinberger
fe14bcecee docs: update changelog for bug sweep landings 2026-05-25 21:00:05 +01:00
Peter Steinberger
aa05c5c9dd test: fix mock signatures for tsgo 2026-05-25 20:57:08 +01:00
Sebastien Tardif
e7c7ee4385 docs(manifest): note safe-regex validation for modelPatterns 2026-05-25 20:57:04 +01:00
Sebastien Tardif
36f269d60b docs: document fail-closed behavior for rejected modelPatterns
Add inline comment explaining that compileSafeRegex rejects patterns
with nested repetition (ReDoS risk) and returns null. Rejected patterns
are silently skipped; the plugin will not match via that pattern but
other patterns and prefixes still apply.

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
2026-05-25 20:57:04 +01:00
Sebastien Tardif
117e08240b fix(security): guard plugin modelPatterns with compileSafeRegex
Replace raw `new RegExp(patternSource, "u")` in
`resolveModelSupportMatchKind` with the existing
`compileSafeRegex()` guard from `src/security/safe-regex.ts`.

A malicious or careless plugin manifest pattern like `(a+)+$`
causes catastrophic backtracking (ReDoS) against non-matching model
IDs. `compileSafeRegex` detects nested repetition and returns null,
which the caller now treats as a non-match (equivalent to the
previous catch-continue for invalid regex).

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
2026-05-25 20:57:04 +01:00
Sebastien Tardif
9a6c16130a style: use bracket notation for __openclaw to satisfy no-underscore-dangle
Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
2026-05-25 20:57:01 +01:00
Sebastien Tardif
aff8e644fc test: tighten oversized metadata assertion to check exact id in __openclaw
Replace string containment check with direct field assertions:
- oversized.role is 'assistant'
- __openclaw.id is 'oversized-child' (exact match)
- parentId extraction proven by record inclusion in active tree

5/5 oversized transcript tests pass.

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
2026-05-25 20:57:01 +01:00
Sebastien Tardif
fe8d99d421 fix(security): escape field names in transcript regex extraction
extractJsonStringFieldPrefix and extractJsonNullableStringFieldPrefix
interpolate the `field` parameter into `new RegExp(...)` without
escaping.  All current callers pass hardcoded strings ("id",
"parentId", "type", "role"), but the function signature accepts
any string.  A future caller passing a field containing regex
metacharacters (e.g. "foo.bar") would match unintended patterns.

Wrap the interpolation with escapeRegExp() from src/shared/regexp.ts
so metacharacters are treated literally.

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
2026-05-25 20:57:01 +01:00
Peter Steinberger
78a1e7dfe6 fix(logging): keep string failure codes on EPIPE 2026-05-25 20:56:56 +01:00
Peter Steinberger
623a60a2b7 fix(logging): preserve failure exit on EPIPE 2026-05-25 20:56:56 +01:00
Pavel Zakharov
2aa5f1771f fix(logging): exit on stdout/stderr EPIPE instead of spinning
When the gateway process is orphaned after a systemd service restart,
the parent's journal pipe closes and every write to stdout/stderr returns
EPIPE. The previous handler swallowed it with a bare return, so background
loops (config file watcher, etc.) kept firing and the process spun at
100% CPU indefinitely.

Exit cleanly with code 0 instead — a process whose own output streams
are broken has nowhere to log and no reason to keep running.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 20:56:56 +01:00
Peter Steinberger
778fa8705c fix(docs): keep spellcheck bash 3.2-compatible 2026-05-25 20:56:52 +01:00
Vitalii
fef57f99ba fix(scripts): docs-spellcheck.sh fails on bash 3.2 with set -u
scripts/docs-spellcheck.sh uses set -u and constructs args=( ... "${write_flag[@]}" ), where write_flag may be an empty array. On bash 3.2 (still the default /bin/bash on macOS), referencing an empty array under set -u raises an unbound variable error. Newer bash (>= 4.4) handles this expression correctly, which is why the script ships green on Linux CI runners.

Switch to the bash 3.2-safe parameter expansion ${write_flag[@]+"${write_flag[@]}"}: it expands to nothing when the array is empty and to the array contents otherwise, preserving --write behavior unchanged.

Also fixes overrideable -> overridable in docs/reference/test.md, which the now-running spellcheck surfaces.

Repro:
  bash scripts/docs-spellcheck.sh                # was: write_flag[@]: unbound variable, exit 1
  bash scripts/docs-spellcheck.sh                # now: codespell runs to completion
2026-05-25 20:56:52 +01:00
Vincent Koc
74f3a1eee2 fix(test): assert e2e agent reply payloads 2026-05-25 21:49:16 +02:00
Peter Steinberger
c88f660258 test(gateway): pin live gateway models to pi runtime 2026-05-25 20:37:33 +01:00
Peter Steinberger
a0023fbfa0 perf: speed up local TUI startup 2026-05-25 20:30:00 +01:00
Peter Steinberger
d0ab0d9922 refactor: share realtime voice activation helpers (#86615) 2026-05-25 20:25:17 +01:00
clawsweeper[bot]
170e0aac2a fix(feishu): render native presentation buttons (#86588)
Summary:
- The PR replaces Feishu presentation/action card fallback rendering with a shared JSON 2.0 button/behaviors renderer, updates native card sanitization, and expands Feishu channel/outbound tests.
- PR surface: Source +118, Tests +223. Total +341 across 5 files.
- Reproducibility: yes. source-reproducible: current main renders Feishu presentation button blocks through ma ...  help` fallback. I did not run local tests because this review was required to keep the checkout read-only.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(feishu): render native presentation buttons
- PR branch already contained follow-up commit before automerge: fix(clawsweeper): address review for automerge-openclaw-openclaw-8601…

Validation:
- ClawSweeper review passed for head 36d6a36323.
- Required merge gates passed before the squash merge.

Prepared head SHA: 36d6a36323
Review: https://github.com/openclaw/openclaw/pull/86588#issuecomment-4536092569

Co-authored-by: NianJiuZst <3235467914@qq.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: takhoffman
Co-authored-by: takhoffman <781889+takhoffman@users.noreply.github.com>
2026-05-25 19:20:42 +00:00
Vincent Koc
423f7d22bc fix(test): narrow plugin gauntlet prebuild 2026-05-25 21:07:17 +02:00
Peter Steinberger
5b6d409248 fix: route Discord gateway metadata through proxy (#86601)
* fix: route Discord gateway metadata through proxy

* fix: keep Discord gateway proxy fetch guarded
2026-05-25 19:59:51 +01:00
Peter Steinberger
f00a912c25 fix: tighten Discord voice wake matching (#86595)
* fix: tighten Discord voice wake matching

* test: avoid wildcard model runtime normalization
2026-05-25 19:51:32 +01:00
Peter Steinberger
baab4cf045 refactor(logging): share diagnostic message lifecycle
Refactor diagnostic queued/state/processed emission into a shared helper used by dispatch and isolated cron turns.

Preserve dispatch processed-event behavior, cron queue-depth symmetry, and final cron session-id adoption while adding focused helper coverage and reviewer comments for the non-obvious invariants.
2026-05-25 19:48:45 +01:00
Vincent Koc
e844d1d6e5 fix(cron): restore suspended lanes to default concurrency 2026-05-25 20:39:37 +02:00
Dallin Romney
a61d5308b5 fix(auth): emit one-shot doctor-pointer warning for Keychain-only legacy Codex OAuth profiles (#86220) 2026-05-25 11:39:32 -07:00
Peter Steinberger
9b9d8970b0 fix(codex): recover stale preflight bindings (#86602)
Fixes #86211.
Recreates the focused recovery from #86216 with maintainer-side tests.

Co-authored-by: Paul Frederiksen <paul@paulfrederiksen.com>
2026-05-25 19:37:49 +01:00
Peter Steinberger
8351556059 test(cron): pin sequential duration regression 2026-05-25 19:23:47 +01:00
Peter Steinberger
bdc6b32828 docs: update changelog for cron preservation (#86415) 2026-05-25 19:23:47 +01:00
IWhatsskill
985bc934a1 fix(cron): canonicalize preserved row ids 2026-05-25 19:23:47 +01:00
IWhatsskill
c916906584 fix(cron): preserve unsupported payload rows on writes 2026-05-25 19:23:47 +01:00
Peter Steinberger
9330b76a51 build: bump qs to patched release
Fixes Dependabot alert #118 for GHSA-q8mj-m7cp-5q26 by updating the workspace qs override from 6.14.2 to 6.15.2 and regenerating root and plugin shrinkwrap files.

Runtime surface: transitive qs consumers through Express, Slack, Feishu, Teams, ACP, and MCP paths.
2026-05-25 19:23:30 +01:00
brokemac79
1e188bcda9 fix(status): prefer active OAuth for runtime aliases
Prefer the active Claude CLI OAuth auth label when the configured Anthropic model resolves through an equivalent Claude CLI runtime alias, so `/status` no longer reports an unused env API-key label.

Also adds regression coverage for both text and message status renderers, plus the maintainer changelog entry.

Closes #80184.

Co-authored-by: brokemac79 <martin_cleary@yahoo.co.uk>
2026-05-25 19:19:51 +01:00
Vincent Koc
407cf8e328 chore(acpx): bump bundled acpx to 0.10.0 2026-05-25 19:17:25 +01:00
Peter Steinberger
c0f2d89c20 docs: make changelog release-owned 2026-05-25 19:15:37 +01:00
Sebastien Tardif
915c820c38 fix(google): stop appending preview to flash lite
Normalize Google Gemini 3.1 Flash Lite routing to the GA model id and keep the retired preview spelling as a compatibility alias. Align default alias docs, FAQ guidance, and deprecated-model manifest recommendations with the GA id.

Fixes #86151.

Co-authored-by: Sebastien Tardif <sebtardif@ncf.ca>
2026-05-25 19:12:36 +01:00
Peter Steinberger
cd7994f227 docs: update changelog for bug sweep landings 2026-05-25 19:06:08 +01:00
Vincent Koc
44bb0be033 fix(crabbox): detect timed macos js commands 2026-05-25 20:05:26 +02:00
Ayaan Zaidi
cf275676f3 fix(mantis): release telegram user leases on startup failure 2026-05-25 23:34:35 +05:30
Peter Steinberger
baf469f02e fix(agents): notify stale cron media failures 2026-05-25 19:04:03 +01:00
Peter Steinberger
f01b2a8eab fix(agents): deliver stale cron media completions 2026-05-25 19:04:03 +01:00
ai-hpc
f5d2db2a60 fix(agents): keep cron media completions run-scoped 2026-05-25 19:04:03 +01:00
tianxiaochannel-oss88
9445960d9d guide workspace-only scratch paths 2026-05-25 19:03:57 +01:00
Peter Steinberger
207a5a2983 fix(cron): report rotated session in final diagnostics 2026-05-25 19:03:50 +01:00
Arnab Saha
48532227d5 fix(cron): gate lifecycle diagnostic events behind isDiagnosticsEnabled
Address clawsweeper P2: cron isolated-agent lifecycle (message.queued,
session.state, message.processed) now mirrors the dispatch path and
respects the diagnostics.enabled master toggle. Added regression test
for the disabled-config path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 19:03:50 +01:00
Arnab Saha
804a31ec5c fix(cron): address review — drop unsupported taskLabel, pair with session.state lifecycle, add regression test + changelog 2026-05-25 19:03:50 +01:00
Arnab Saha
6ccd4e72f0 fix(cron): emit message.queued/processed for isolated-agent turns 2026-05-25 19:03:50 +01:00
haoxingjun
b5ada806dd fix: hydrate current turn image attachments 2026-05-25 19:03:43 +01:00
YUHAO-corn
177ebdc24c fix(gateway): ignore inherited launchd env for respawn 2026-05-25 19:03:37 +01:00
Vincent Koc
b0c8a4d11d fix(test): preserve undici exports in discord proxy tests 2026-05-25 20:02:10 +02:00
Peter Steinberger
bc12e04993 fix: raise default cron concurrency 2026-05-25 18:59:26 +01:00
liaoyl830
6e8d2dbbbc fix(doctor): skip restart prompt when gateway is healthy after recent restart (#86533)
* fix(doctor): skip restart prompt when gateway is healthy after recent restart

`openclaw doctor` unconditionally prompted "Restart gateway service now?"
with default=Yes whenever the gateway was running, even if it had just
restarted via SIGUSR1 after an update. This caused restart loops on macOS
where the prompt raced with launchctl KeepAlive.

Changes:
- Probe gateway health before the restart prompt when a restart handoff
  exists (deep doctor mode). If healthy, skip the prompt entirely.
- Change `initialValue` from `true` to `false` as a safety net so users
  don't accidentally confirm a restart by pressing Enter.
- Update existing test that expected a single `readGatewayRestartHandoffSync`
  call (now called twice: diagnostic display + health-probe check).

Fixes #86518

* fix(doctor): correct GatewayRestartHandoff mock types in tests

Add explicit literal types + satisfies constraint so the mock handoff
objects match the exact GatewayRestartHandoff type expected by the
type-check CI.

* fix(doctor): apply recent-restart skip to normal doctor flow

* test(doctor): align normal-flow handoff expectation

* chore: add doctor restart prompt changelog

---------

Co-authored-by: OpenClaw Contributor <openclaw-contributor@example.com>
Co-authored-by: liaoyl830 <267396060+liaoyl830@users.noreply.github.com>
Co-authored-by: sallyom <somalley@redhat.com>
2026-05-25 13:53:28 -04:00
brokemac79
8129dba5d8 fix: emit agent.send lifecycle hooks on rotation (#85875)
* fix: emit agent send lifecycle hooks

* fix(gateway): align agent send session lifecycle hooks

* fix(gateway): emit agent lifecycle before validation exits

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-25 18:51:13 +01:00
Vincent Koc
7cd15d2493 fix(crabbox): bootstrap macos shell js commands 2026-05-25 19:39:28 +02:00
Peter Steinberger
822ee62947 fix: tighten openshell exec preflight 2026-05-25 18:36:55 +01:00
brokemac79
aafed830a5 fix: preflight malformed openshell exec commands 2026-05-25 18:36:55 +01:00
Peter Steinberger
f87aa0ff1b docs: clarify unshipped compat policy 2026-05-25 18:35:25 +01:00
NVIDIAN
8061d66713 fix(update): allow package-manager hardlinks in swaps
Allow package-manager-managed hardlinked package roots during update/install swaps while keeping generic plugin, hook, and dependency-free install moves fail-closed.

Fixes #85559.

Co-authored-by: ai-hpc <mail.speedy.hpc@hotmail.com>
2026-05-25 18:30:49 +01:00
Peter Steinberger
17954a4f33 docs: ban repo-hosted proof artifacts 2026-05-25 18:25:57 +01:00
Josh Avant
c5b987274a fix(discord): restore bare numeric channel sends (#86571)
* fix(discord): restore bare numeric channel sends

* docs: add Discord channel send changelog
2026-05-25 10:24:20 -07:00
Vincent Koc
b83dfcb953 fix(installer): handle alpine apk runtime floors 2026-05-25 19:23:10 +02:00
Sally O'Malley
bd65b4232a fix(security): audit Claude permission overrides under YOLO (#86557)
* fix(agents): warn on Claude permission overrides under YOLO

* fix: narrow Claude audit backend guard

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-25 18:18:51 +01:00
Peter Steinberger
5ae91f01fa fix: speed up Discord voice wake consults 2026-05-25 18:09:16 +01:00
Vincent Koc
3eb06e305e fix(qa): harden restart inflight Windows scenario 2026-05-25 18:49:04 +02:00
Jason (Json)
5cfa577778 Recover Codex context overflow prompt errors (#85542)
* fix: recover codex context overflow prompt errors

* test: align Codex overflow prompt proof

* test: satisfy manifest registry mock contract

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-25 17:44:48 +01:00
Peter Steinberger
d967760b41 docs: update changelog for #70473 2026-05-25 17:35:47 +01:00
FullerStackDev
d5b0174eb1 fix(agents): derive overflow budgets from provider errors 2026-05-25 17:35:47 +01:00
Peter Steinberger
313762282c fix(plugins): only memoize complete metadata snapshots 2026-05-25 17:29:46 +01:00
Peter Steinberger
a11d4e6871 docs: update changelog for media wake fallback (#85489) 2026-05-25 17:23:28 +01:00
Jason (Json)
1b64ccbfff fix: fallback after active media wake failure (#85489)
* fix: fallback after active media wake failure

* docs: clarify generated media fallback docs
2026-05-25 17:23:20 +01:00
Peter Steinberger
159e4406ab perf(plugins): reuse derived metadata snapshots 2026-05-25 17:20:39 +01:00
Jason (Json)
f271f003d4 docs: require maintainer-editable PR branches
Require contributor and agent-created PR branches to stay maintainer-editable, with a GitHub Actions/secrets caveat for fork PRs.

Verification:
- pnpm docs:list
- git diff --check
- Real behavior proof: https://github.com/openclaw/openclaw/actions/runs/26409882732/job/77741796262
- check-docs: https://github.com/openclaw/openclaw/actions/runs/26409857961/job/77741751070

Changelog intentionally skipped per maintainer request.

Co-authored-by: FullerStackDev <263060202+fuller-stack-dev@users.noreply.github.com>
2026-05-25 17:19:40 +01:00
Peter Steinberger
dd375f9fc3 docs: note agent transcript OpenClaw session scan 2026-05-25 16:48:42 +01:00
Peter Steinberger
4012ae4f42 fix: scan OpenClaw sessions in agent transcript finder 2026-05-25 16:48:42 +01:00
Peter Steinberger
fc93af5637 docs: require generic local fixes 2026-05-25 16:45:56 +01:00
Peter Steinberger
a9c91ca81f fix: broaden leading voice wake fuzzing 2026-05-25 16:45:56 +01:00
Gio Della-Libera
657b246e56 test(agents): preserve provider hook mock exports (#86523) 2026-05-25 08:45:37 -07:00
Gio Della-Libera
fbb6340542 Policy: add agent-scoped policy overlays (#85817)
* feat(policy): add agent-scoped policy overlays

* docs(policy): use generic agent-scoped examples

* fix(policy): generalize scoped policy overlays

* fix(policy): clean scoped overlay checks

* fix(policy): evaluate inherited scoped agent posture

* chore(policy): keep agent harness out of scoped policy pr
2026-05-25 08:45:16 -07:00
Sebastien Tardif
abe99230df fix(kilocode): normalize string stop param to array in stream wrapper (#86461)
* fix(kilocode): normalize string stop param to array in stream wrapper

* fix: move kilocode stop normalization into extension

* fix: keep kilocode stream wrapper plugin-local

* fix: normalize kilocode stop after extra body

* fix(qa-lab): preserve WhatsApp RTT source literal

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-25 16:40:07 +01:00
Gio Della-Libera
dc17412c3a Doctor: expose shell completion health findings (#85566)
* feat(doctor): expose shell completion health findings

* fix(doctor): keep shell completion lint informational
2026-05-25 08:39:41 -07:00
Sally O'Malley
f0b6f70053 fix(agents): honor effective exec policy for Claude live Bash (#86330)
* fix(agents): answer Claude live control_request can_use_tool via exec policy

Claude CLI emits stream-json control_request frames with subtype
can_use_tool when it wants to use a native tool. The Claude live-session
bridge previously dropped these frames, leaving Claude waiting for a
control_response until the 180/600s no-output timeout fired (see #80819).

Resolve the effective OpenClaw exec policy (per-agent tools.exec -> global
tools.exec -> allowlist/on-miss defaults) once at session-start time and
thread it through fingerprinting and the session record. When a
can_use_tool request arrives:

- Allow native Bash when the resolved policy is security=full, ask=off
  (matching the bypassPermissions semantics OpenClaw already documents).
- Otherwise deny with a message that names the resolved policy and
  points the agent at OpenClaw MCP tools.

Unsupported control_request subtypes get a structured error response
instead of a silent no-op, and stray control_response frames are
silently dropped. Adds spawn-test coverage for both allow and deny paths.

Fixes #80819

* fix(agents): align Claude live control_request policy with backend defaults

Resolve the effective exec policy through the same defaults that
extensions/anthropic/cli-shared.ts:isOpenClawRequestedYolo and
src/agents/exec-defaults.ts:resolveExecDefaults already use (security
?? "full", ask ?? "off") instead of falling back to a hand-rolled
allowlist/on-miss default that disagreed with the rest of the codebase.
Without this, a default-config OpenClaw deployment launches Claude with
--permission-mode bypassPermissions but the bridge would still deny
Bash control_requests, re-creating the #80819 stall for the very
default-config case the issue reports.

Also thread the effective Claude permission mode into the policy
decision. Prefer the operator's explicit --permission-mode in argv,
falling back to what normalizeClaudePermissionArgs would have inserted
for an un-overridden launch. Native Bash is auto-allowed only when the
effective mode is bypassPermissions AND tools.exec resolves to
full/no-ask, so explicit raw-arg overrides like --permission-mode
default or acceptEdits broaden Claude's native prompting and are
honored by routing through deny.

Adds a no-config regression test (default deployment allows Bash, no
stall) and a permission-mode-override test (tools.exec full/off plus
explicit --permission-mode default in raw args denies). Existing
allow/deny tests continue to pass via the synthesized-mode fallback.

* fix(agents): honor effective exec policy for Claude live Bash

---------

Co-authored-by: Guillaume Thirry <g.thirry@gmail.com>
2026-05-25 11:39:17 -04:00
Vincent Koc
99997e4441 fix(test): stabilize e2e runtime imports 2026-05-25 17:35:26 +02:00
Vincent Koc
633e4b8a7c fix(test): clean plugin gauntlet temp roots 2026-05-25 17:29:51 +02:00
Peter Steinberger
69d728ac4f perf: cache plugin package realpaths (#86517) 2026-05-25 16:26:36 +01:00
Vincent Koc
2cac9e54b4 fix(qa): settle restart races with live budget 2026-05-25 17:20:54 +02:00
Vincent Koc
50d6611c10 test(crabbox): tolerate Windows shell capture 2026-05-25 17:20:54 +02:00
Vincent Koc
8a93851ee2 fix(qa): extend config cleanup Windows budget 2026-05-25 17:20:54 +02:00
Vincent Koc
e97e831c12 fix(crabbox): sync full sparse lease runs 2026-05-25 17:20:54 +02:00
Vincent Koc
3f363e0450 fix(qa): extend config mutation Windows budget 2026-05-25 17:20:54 +02:00
Chunyue Wang
89aea9b843 fix(sessions): stop doctor OOM on large session stores and reclaim stale store temps (#85967)
* fix(sessions): stop doctor OOM on large session stores and reclaim stale store temps

`openclaw doctor` loaded the full sessions.json via loadSessionStore with the
default cache-write plus return clone, materializing a multi-hundred-MB
monolithic store several times and exhausting the heap (#56827). The read-only
doctor checks (state integrity, heartbeat target, codex route scan) now load
with { skipCache: true, clone: false } so the store is materialized once.

Orphaned session-store atomic-write temps were also never reclaimed: the store
write went through the generic atomic writer, staging a shared
.fs-safe-replace.<pid>.<uuid>.tmp not identifiable as a store temp. Give the
store write a store-specific tempPrefix so its temps stage as
sessions.json.<pid>.<uuid>.tmp, classify them (isSessionStoreTempArtifactName),
and reclaim stale ones via the disk-budget sweep and the unreferenced-artifact
prune on a short staleness window so in-flight temps are preserved.

Fixes #56827

* docs(changelog): note large session store doctor fix

* test(qa): preserve WhatsApp RTT source literal

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-25 16:19:35 +01:00
clawsweeper[bot]
c4bce00727 fix(ollama): strip inline kimi cloud reasoning leak (#86515)
Summary:
- This PR adds an Ollama Kimi-cloud visible-content sanitizer for streamed and final assistant replies, updates stream handling and regression tests, and adds a changelog entry.
- PR surface: Source +183, Tests +473, Docs +1. Total +657 across 7 files.
- Reproducibility: yes. from source and the linked report: current main appends Ollama `message.content` direc ...  payload described in the issue would be shown. I did not run a live vendor repro in this read-only review.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(ollama): sanitize kimi inline reasoning in stream events
- PR branch already contained follow-up commit before automerge: fix(ollama): buffer kimi cloud stream reasoning
- PR branch already contained follow-up commit before automerge: fix(ollama): cover kimi inline boundary variants
- PR branch already contained follow-up commit before automerge: fix(ollama): preserve text start partial state
- PR branch already contained follow-up commit before automerge: fix(ollama): bound kimi stream sanitizer hold
- PR branch already contained follow-up commit before automerge: fix(ollama): keep kimi sanitizer deltas append-only

Validation:
- ClawSweeper review passed for head b709229157.
- Required merge gates passed before the squash merge.

Prepared head SHA: b709229157
Review: https://github.com/openclaw/openclaw/pull/86515#issuecomment-4534945393

Co-authored-by: Jason O'Neal <jason.allen.oneal@gmail.com>
Co-authored-by: Onur Solmaz <2453968+osolmaz@users.noreply.github.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: osolmaz
Co-authored-by: osolmaz <2453968+osolmaz@users.noreply.github.com>
2026-05-25 15:16:42 +00:00
Neerav Makwana
bc10fad79c fix(discord): merge media captions into one message (#86487)
Summary:
- This PR changes the shared block reply coalescer/pipeline so compatible buffered visible text is merged into a following media payload, adds focused regression tests, and records a Discord changelog fix.
- PR surface: Source +50, Tests +175, Docs +1. Total +226 across 6 files.
- Reproducibility: yes. Current main has a clear source reproduction path: media enqueue forces a text flush and then sends the media payload separately, and the PR adds focused tests for the corrected merge path.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix: route streamed media through reply coalescer
- PR branch already contained follow-up commit before automerge: fix(discord): merge media captions into one message
- PR branch already contained follow-up commit before automerge: fix(clawsweeper): address review for automerge-openclaw-openclaw-8648…

Validation:
- ClawSweeper review passed for head ceafbeaf3c.
- Required merge gates passed before the squash merge.

Prepared head SHA: ceafbeaf3c
Review: https://github.com/openclaw/openclaw/pull/86487#issuecomment-4534402219

Co-authored-by: Neerav Makwana <261249544+neeravmakwana@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: takhoffman
Co-authored-by: takhoffman <781889+takhoffman@users.noreply.github.com>
2026-05-25 15:16:29 +00:00
esadomer
8f260de3e7 fix(utils): clamp fetch timeout timers (#85985) 2026-05-25 16:16:09 +01:00
Anup Sharma
276ba1090e fix(ui): preserve user code block rendering (#85942) 2026-05-25 16:16:04 +01:00
BonRaynn
16ffc2507a fix(memory): prevent silent vector index degradation when embedding provider temporarily unavailable (#85704)
* fix(memory): prevent silent vector index degradation when embedding provider temporarily unavailable

Two related bugs cause complete loss of semantic vector data:

1. Promise cache deadlock in ensureProviderInitialized():
   When the embedding provider (e.g. local MLX server on port 8123) is
   temporarily unreachable at Gateway startup, loadProviderResult() throws
   and providerInitPromise becomes a permanently-cached Rejected Promise.
   The  block only clears it on success (providerInitialized=true),
   so the stale rejection blocks all future init attempts until Gateway restart.

2. Silent fts-only overwrite in runSync():
   With the provider stuck at null, shouldRunFullMemoryReindex() compares
   the stored meta.model (e.g. 'jina-embeddings-v5-text-small') against the
   runtime provider model, and since provider is null, falls through to the
   'meta.model !== fts-only' check — returning true. This triggers a full
   reindex where every file is written as fts-only, silently erasing all
   existing 11k+ semantic vectors.

Fix 1: Clear providerInitPromise in the catch block so the next call can
retry initialization (self-healing when the provider comes back online).

Fix 2: Guard runSync() — if requestedProvider is set and not 'none', but
the runtime provider is null, throw an error instead of silently degrading
to fts-only. This protects existing vector data by failing loudly.

Tested on production: 11,715 chunks + 1024-dim vectors fully preserved
after Gateway restart with the fix applied. The guard correctly blocks
sync when MLX is offline and allows normal operation when it recovers.

* fix: use this.settings.provider instead of private requestedProvider

The guard clause in runSync() was referencing this.requestedProvider
which is a private property on the MemoryIndexManager subclass and not
accessible from MemoryManagerSyncOps. Use this.settings.provider
instead, which is the same value and is accessible via the protected
abstract settings property.

* fix(memory): narrow degradation guard to only protect existing semantic indexes

The previous guard was too broad — it blocked sync for ALL non-none
provider configurations when provider was null, including the default
'auto' path where users without embedding credentials legitimately
build FTS-only indexes.

Narrow the guard to only abort when:
1. provider is null (embedding unavailable)
2. existing index metadata has a semantic model (not 'fts-only')
3. settings.provider is configured and not 'none'

This preserves the legitimate FTS-only fallback for auto/no-provider
users while still protecting existing semantic vector indexes from
silent degradation.

Reported-by: ClawSweeper (PR #85704 review)

* test: cover memory semantic index outage guard

* fix: protect semantic memory index fallback paths

* test: update memory sync harnesses

---------

Co-authored-by: Bo Yan <yaaboo-gif@users.noreply.github.com>
Co-authored-by: Yan Bo <yanbo@Mac.lan>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-25 16:15:59 +01:00
Peter Steinberger
8da8bc4aad docs: clarify agent transcript placeholders 2026-05-25 16:07:41 +01:00
Peter Steinberger
bb6f37e777 test(qa): annotate live transport RTT measurements 2026-05-25 15:56:13 +01:00
clawsweeper[bot]
aa702cf3db fix(qqbot): derive outbound watchdog from configured timeouts (#85267) (#86500)
Summary:
- The branch replaces QQBot's hardcoded outbound response watchdog with a resolver based on existing agent/provider `timeoutSeconds` settings, adds regression tests, and updates the changelog.
- PR surface: Source +113, Tests +116, Docs +1. Total +230 across 5 files.
- Reproducibility: yes. at source level: current main and the latest release use a hardcoded 300000 ms QQBot o ... s an 1800s provider timeout. I did not run the reporter's live QQBot/Ollama setup in this read-only review.

Automerge notes:
- PR branch already contained follow-up commit before automerge: test(qqbot): cover slow provider response watchdog
- PR branch already contained follow-up commit before automerge: fix(qqbot): derive outbound watchdog from configured timeouts (#85267)
- PR branch already contained follow-up commit before automerge: fix(clawsweeper): address review for automerge-openclaw-openclaw-8527…

Validation:
- ClawSweeper review passed for head 7bd829292a.
- Required merge gates passed before the squash merge.

Prepared head SHA: 7bd829292a
Review: https://github.com/openclaw/openclaw/pull/86500#issuecomment-4534669816

Co-authored-by: SymbolStar <symbolstar@users.noreply.github.com>
Co-authored-by: Onur Solmaz <2453968+osolmaz@users.noreply.github.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: osolmaz
Co-authored-by: osolmaz <2453968+osolmaz@users.noreply.github.com>
2026-05-25 14:52:42 +00:00
Vincent Koc
6f695c1864 fix(test): clean kitchen sink rpc temp state 2026-05-25 16:47:23 +02:00
Galin Iliev
277d8fece2 fix: quiet missing daily memory reads
Closes #82928
2026-05-25 07:42:57 -07:00
Peter Steinberger
026cfb6ba1 fix: tighten empty plugin registry reuse 2026-05-25 15:42:18 +01:00
Peter Steinberger
e7ad116b9b perf: speed up agent transcript lookup 2026-05-25 15:40:35 +01:00
clawsweeper[bot]
2e3b59bc58 fix: guard QMD session stem fallback (#86482)
Summary:
- This PR changes `resolveTranscriptStemToSessionKeys` to skip empty or missing `sessionId` values during QMD slug fallback, adds regression coverage, and adds a changelog entry.
- PR surface: Source +1, Tests +17, Docs +1. Total +19 across 3 files.
- Reproducibility: yes. from source inspection: current main reaches `normalizeQmdSessionStem(entry.sessionId) ... ad-only review, but the source PR includes a direct after-fix resolver probe for the same mixed-store case.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix: guard QMD session stem fallback
- PR branch already contained follow-up commit before automerge: fix(clawsweeper): address review for automerge-openclaw-openclaw-8632…

Validation:
- ClawSweeper review passed for head 81478b0ee6.
- Required merge gates passed before the squash merge.

Prepared head SHA: 81478b0ee6
Review: https://github.com/openclaw/openclaw/pull/86482#issuecomment-4534348706

Co-authored-by: abnershang <abner.shang@gmail.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: Abner Shang <75654486+abnershang@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: takhoffman
Co-authored-by: takhoffman <781889+takhoffman@users.noreply.github.com>
2026-05-25 14:35:43 +00:00
clawsweeper[bot]
489e415339 Guard OpenAI chat payload turns (#86497)
Summary:
- Adds a scoped ModelStudio/DashScope OpenAI-compatible guard for chat payloads with no non-empty user or assi ... turn, shared turn-detection helper coverage, prompt-skip handling, regression tests, and a changelog entry.
- PR surface: Source +83, Tests +298, Docs +1. Total +382 across 10 files.
- Reproducibility: yes. source-reproducible for the OpenClaw-side malformed payload shape: current main has no ... he exact qwen-long/qwen3-coder-plus provider error was not reproduced with the available DashScope account.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix: make OpenAI payload guard content-aware
- PR branch already contained follow-up commit before automerge: fix: scope openai payload turn guard
- PR branch already contained follow-up commit before automerge: Guard OpenAI chat payload turns

Validation:
- ClawSweeper review passed for head e16a3fe9f2.
- Required merge gates passed before the squash merge.

Prepared head SHA: e16a3fe9f2
Review: https://github.com/openclaw/openclaw/pull/86497#issuecomment-4534668405

Co-authored-by: Andy Ye <35905412+TurboTheTurtle@users.noreply.github.com>
Co-authored-by: Onur Solmaz <2453968+osolmaz@users.noreply.github.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: osolmaz
Co-authored-by: osolmaz <2453968+osolmaz@users.noreply.github.com>
2026-05-25 14:28:03 +00:00
Peter Steinberger
459e89ada8 fix(gateway): keep session tool mirrors under pressure
Reverts the diagnostic queue-pressure suppression of non-terminal session tool mirrors from PR 84846 while keeping PR 86503 recipient dedupe intact. Session-only Control UI subscribers keep receiving tool lifecycle mirrors; overlapping run and session subscribers still receive one canonical run-scoped frame. Verification: focused gateway and diagnostic tests, diff check, changed check, and autoreview all passed.
2026-05-25 15:22:52 +01:00
Peter Steinberger
0ab63e2b18 docs: route github creation through agent transcript 2026-05-25 15:21:21 +01:00
Mason Huang
f0bfb3fc33 test(tools): add unmocked image custom-provider auth regression (#85733)
Summary:
- The branch adds an unmocked image-tool custom-provider auth regression test, fixes split agents Vitest config routing, adds routing coverage, and records a changelog entry.
- PR surface: Tests +203, Docs +1, Other +8. Total +212 across 4 files.
- Reproducibility: not applicable. as a current-main failing issue: the production runtime bug was addressed by the linked predecessor, and this PR adds regression coverage plus test-routing verification for that path.

Automerge notes:
- PR branch already contained follow-up commit before automerge: test(tools): polish image auth regression and fix agents vitest routing
- PR branch already contained follow-up commit before automerge: test(tools): remove proof test filename after regression rename
- PR branch already contained follow-up commit before automerge: fix(test): remove duplicate agent shard constants
- PR branch already contained follow-up commit before automerge: test(tools): add unmocked image custom-provider auth regression
- PR branch already contained follow-up commit before automerge: fix(clawsweeper): address review for automerge-openclaw-openclaw-8573…

Validation:
- ClawSweeper review passed for head cff5476aeb.
- Required merge gates passed before the squash merge.

Prepared head SHA: cff5476aeb
Review: https://github.com/openclaw/openclaw/pull/85733#issuecomment-4525628364

Co-authored-by: Mason Huang <masonxhuang@tencent.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: hxy91819
Co-authored-by: hxy91819 <8814856+hxy91819@users.noreply.github.com>
2026-05-25 14:19:04 +00:00
1942 changed files with 67887 additions and 33051 deletions

View File

@@ -17,7 +17,9 @@ Best-effort local-only provenance for OpenClaw PR/issue bodies. Use during agent
- Fail closed on unresolved secrets, private keys, browser/session/cookie details, or auth URLs.
- Drop system/developer prompts, raw tool outputs, reasoning, env, cookies, tokens, and broad local paths.
- Keep user prompts, assistant visible decisions, terse tool summaries, and test/proof outcomes.
- Remove session turns unrelated to the PR/issue work. Use the PR/issue title, branch name, changed files, and stated goal as scope; omit earlier/later unrelated tasks even when they are in the same session log.
- Best effort only: PR/issue creation must continue if no safe transcript is found.
- Add the `## Agent Transcript` section only when inserting a real transcript. Never add a placeholder transcript heading or text such as "A sanitized local transcript preview was generated but not included."
- Use a collapsed `<details>` section and update existing markers instead of duplicating sections.
## Helper
@@ -35,6 +37,8 @@ Find a likely local session:
--since-days 14
```
`find` scans the newest 400 matching local JSONL logs by default across Codex, Claude, Pi, and OpenClaw agent sessions. Use `--max-files N` for a wider local search.
Render a PR/issue body section:
```bash
@@ -68,9 +72,10 @@ Append/update a body file before `gh pr create --body-file` or connector PR crea
3. If a high-confidence session is found, ask:
`Include a redacted agent transcript? It helps reviewers and can make the PR easier to prioritize. I can open a local preview first.`
4. If the user wants preview, run `preview`, open the HTML with `open`, and wait for confirmation.
5. If the user approves, run `append-body`.
6. Use the enriched body file for creation/update.
7. If no safe session is found, say nothing and continue without transcript. If the user declines, continue without transcript.
5. Before insertion, trim unrelated session turns from the generated section. Keep only turns that explain this PR/issue's goal, implementation choices, files, tests, proof, blockers, and final outcome.
6. If the user approves, run `append-body`.
7. Use the enriched body file for creation/update.
8. If no safe session is found, say nothing and continue without transcript. If the user declines, continue without transcript and do not add any transcript placeholder section.
## Review Artifacts

View File

@@ -11,7 +11,7 @@ const DEFAULT_ENTRY_MAX_CHARS = 6000;
function usage() {
console.log(`Usage:
agent-transcript find --query TEXT [--cwd PATH] [--since-days N] [--root PATH...]
agent-transcript find --query TEXT [--cwd PATH] [--since-days N] [--max-files N] [--root PATH...]
agent-transcript render --session FILE [--out FILE] [--max-chars N] [--entry-max-chars N] [--title TEXT] [--url URL]
agent-transcript preview --session FILE [--out FILE] [--max-chars N] [--entry-max-chars N] [--title TEXT] [--url URL]
agent-transcript append-body --body FILE --session FILE [--out FILE] [--max-chars N] [--entry-max-chars N]
@@ -51,11 +51,35 @@ function homePath(...parts) {
return path.join(os.homedir(), ...parts);
}
function openClawSessionRoots() {
const stateDir = process.env.OPENCLAW_STATE_DIR || homePath(".openclaw");
const agentsDir = path.join(stateDir, "agents");
if (!fs.existsSync(agentsDir)) return [];
try {
const roots = fs
.readdirSync(agentsDir, { withFileTypes: true })
.filter((entry) => entry.isDirectory())
.flatMap((entry) => {
const agentDir = path.join(agentsDir, entry.name);
return [
path.join(agentDir, "sessions"),
path.join(agentDir, "agent", "sessions"),
path.join(agentDir, "agent", "codex-home", "sessions"),
];
})
.filter((root) => fs.existsSync(root));
return [...new Set(roots)];
} catch {
return [];
}
}
function defaultRoots() {
return [
homePath(".codex", "sessions"),
homePath(".claude", "projects"),
homePath(".pi", "agent", "sessions"),
...openClawSessionRoots(),
];
}
@@ -110,6 +134,12 @@ function detectAgent(file, rows) {
if (file.includes(`${path.sep}.codex${path.sep}`)) return "codex";
if (file.includes(`${path.sep}.claude${path.sep}`)) return "claude";
if (file.includes(`${path.sep}.pi${path.sep}`)) return "pi";
if (
file.includes(`${path.sep}.openclaw${path.sep}`) ||
(file.includes(`${path.sep}agents${path.sep}`) && file.includes(`${path.sep}sessions${path.sep}`))
) {
return "openclaw";
}
if (rows.some((row) => row?.type === "session_meta" || row?.type === "response_item")) return "codex";
if (rows.some((row) => row?.sessionId && row?.userType)) return "claude";
return "agent";
@@ -425,17 +455,14 @@ function readBoundedText(file, maxBytes = 220000) {
}
}
function sessionScanRecord(file) {
function sessionScanRecord(file, maxBytes) {
const stat = fs.statSync(file);
let agent = "agent";
try {
agent = detectAgent(file, readJsonl(file, 50));
} catch {}
const agent = detectAgent(file, []);
return {
file,
agent,
mtime: new Date(stat.mtimeMs).toISOString(),
haystack: `${file}\n${readBoundedText(file)}`.toLowerCase(),
haystack: `${file}\n${readBoundedText(file, maxBytes)}`.toLowerCase(),
};
}
@@ -461,6 +488,25 @@ function scoreScanRecord(record, terms, cwd) {
return { file: record.file, score, reasons, mtime: record.mtime, agent: record.agent };
}
function recentFiles(files, maxFiles) {
return files
.map((file) => {
try {
return { file, mtimeMs: fs.statSync(file).mtimeMs };
} catch {
return null;
}
})
.filter(Boolean)
.sort((a, b) => b.mtimeMs - a.mtimeMs)
.slice(0, maxFiles)
.map((entry) => entry.file);
}
function candidateFiles(roots, terms, sinceMs, options = {}) {
return recentFiles(roots.flatMap((root) => walkJsonl(root, sinceMs)), Number(options["max-files"] || 400));
}
function findSessions(options) {
const sinceDays = Number(options["since-days"] || 14);
const sinceMs = Date.now() - sinceDays * 24 * 60 * 60 * 1000;
@@ -470,9 +516,10 @@ function findSessions(options) {
.split(/\s+/)
.concat(query.match(/https?:\/\/\S+/g) || [])
.filter(Boolean);
const files = roots.flatMap((root) => walkJsonl(root, sinceMs));
const files = candidateFiles(roots, terms, sinceMs, options);
const scanBytes = Number(options["scan-bytes"] || 60000);
const results = files
.map((file) => scoreScanRecord(sessionScanRecord(file), terms, options.cwd))
.map((file) => scoreScanRecord(sessionScanRecord(file, scanBytes), terms, options.cwd))
.filter((result) => result.score > 0)
.sort((a, b) => b.score - a.score || b.mtime.localeCompare(a.mtime))
.slice(0, Number(options.limit || 10));
@@ -487,7 +534,7 @@ function sessionScanRecords(options) {
return roots
.flatMap((root) => walkJsonl(root, sinceMs))
.filter((file) => !excluded.has(path.resolve(file)))
.map(sessionScanRecord);
.map((file) => sessionScanRecord(file, Number(options["scan-bytes"] || 90000)));
}
function replaceSection(body, section) {

View File

@@ -26,8 +26,9 @@ Use when:
- If a review-triggered fix changes code, rerun focused tests and rerun the structured review helper.
- For security-audit suppression changes, verify accepted findings remain auditable: suppressed findings stay in structured output, active output keeps an unsuppressible suppression notice, and aggregate findings cannot hide unrelated active risk.
- Never switch or override the requested review engine/model. If the review hits model capacity, retry the same command a few times with the same engine/model.
- Be patient with large bundles. Structured review can be silent for several minutes while the model call is active, especially with Codex tools or web search. Treat `review still running: ... elapsed=... pid=...` as healthy progress, not a hang.
- Do not kill a review just because it has been quiet for 2-5 minutes. Inspect the process only after multiple heartbeat intervals or an obviously failed subprocess; prefer letting the same helper command finish.
- Be patient with large bundles. Structured review can take up to 30 minutes while the model call is active, especially with Codex tools or web search.
- Treat heartbeat lines like `review still running: ... elapsed=... pid=...` as healthy progress, not a hang. Let the helper continue while heartbeats are advancing.
- Do not kill a review just because it has been quiet for 2-5 minutes, or because it is still running under the 30-minute window. Inspect the process only after missing multiple expected heartbeats, after 30 minutes, or after an obviously failed subprocess; prefer letting the same helper command finish.
- Tools are useful in review mode. The helper allows read-only inspection tools and web search by default so reviewers can check dependency contracts, upstream docs, and current behavior.
- Security perspective is always included, but it should not cripple legitimate functionality. Report security findings only when the change creates a concrete, actionable risk or removes an important safety check.
- Do not invoke built-in `codex review`, nested reviewers, or reviewer panels from inside the review. The helper builds one bundle, calls one selected engine, validates one structured result, and stops.

View File

@@ -149,7 +149,7 @@ pnpm crabbox:run -- \
--ttl 240m \
--timing-json \
--shell -- \
"env CI=1 NODE_OPTIONS=--max-old-space-size=4096 OPENCLAW_TEST_PROJECTS_PARALLEL=6 OPENCLAW_VITEST_MAX_WORKERS=1 OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=900000 pnpm test:changed"
"pnpm test:changed"
```
Full suite:
@@ -160,7 +160,7 @@ pnpm crabbox:run -- \
--ttl 240m \
--timing-json \
--shell -- \
"env CI=1 NODE_OPTIONS=--max-old-space-size=4096 OPENCLAW_TEST_PROJECTS_PARALLEL=6 OPENCLAW_VITEST_MAX_WORKERS=1 OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=900000 pnpm test"
"pnpm test"
```
Focused rerun:
@@ -171,7 +171,7 @@ pnpm crabbox:run -- \
--ttl 240m \
--timing-json \
--shell -- \
"env CI=1 NODE_OPTIONS=--max-old-space-size=4096 OPENCLAW_VITEST_MAX_WORKERS=1 OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=900000 pnpm test <path-or-filter>"
"pnpm test <path-or-filter>"
```
Read the JSON summary. Useful fields:
@@ -206,7 +206,7 @@ node scripts/crabbox-wrapper.mjs run \
--ttl 240m \
--timing-json \
-- \
CI=1 NODE_OPTIONS=--max-old-space-size=4096 OPENCLAW_TEST_PROJECTS_PARALLEL=6 OPENCLAW_VITEST_MAX_WORKERS=1 OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=900000 OPENCLAW_TESTBOX=1 OPENCLAW_TESTBOX_REMOTE_RUN=1 pnpm check:changed
corepack pnpm check:changed
```
Read the JSON summary and the Testbox line. Useful fields:
@@ -544,14 +544,14 @@ If brokered AWS cannot dispatch, sync, attach, or stop, retry once with
```sh
pnpm crabbox:run -- --debug --timing-json -- \
CI=1 NODE_OPTIONS=--max-old-space-size=4096 OPENCLAW_TEST_PROJECTS_PARALLEL=6 OPENCLAW_VITEST_MAX_WORKERS=1 OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=900000 pnpm test:changed
pnpm test:changed
```
Full suite:
```sh
pnpm crabbox:run -- --debug --timing-json -- \
CI=1 NODE_OPTIONS=--max-old-space-size=4096 OPENCLAW_TEST_PROJECTS_PARALLEL=6 OPENCLAW_VITEST_MAX_WORKERS=1 OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=900000 pnpm test
pnpm test
```
Auth fallback, only when `blacksmith` says auth is missing:
@@ -591,7 +591,7 @@ Minimal Blacksmith-backed Crabbox run, from repo root:
```sh
pnpm crabbox:run -- --provider blacksmith-testbox --timing-json -- \
CI=1 NODE_OPTIONS=--max-old-space-size=4096 OPENCLAW_TEST_PROJECTS_PARALLEL=6 OPENCLAW_VITEST_MAX_WORKERS=1 pnpm test:changed
corepack pnpm test:changed
```
Use direct Blacksmith only when Crabbox is the broken layer and you are
@@ -617,7 +617,7 @@ provider deliberately.
```sh
pnpm crabbox:warmup -- --class beast --market on-demand --idle-timeout 90m
pnpm crabbox:hydrate -- --id <cbx_id-or-slug>
pnpm crabbox:run -- --id <cbx_id-or-slug> --timing-json --shell -- "env NODE_OPTIONS=--max-old-space-size=4096 OPENCLAW_TEST_PROJECTS_PARALLEL=6 OPENCLAW_VITEST_MAX_WORKERS=1 OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=900000 pnpm test:changed"
pnpm crabbox:run -- --id <cbx_id-or-slug> --timing-json --shell -- "pnpm test:changed"
pnpm crabbox:stop -- <cbx_id-or-slug>
```

View File

@@ -89,11 +89,11 @@ Reject:
- if unwritable or wrong shape, create own PR and preserve useful contributor credit
- if no PR exists, create one
- add regression test when it fits
- changelog for user-facing fixes; thank credited human reporter/contributor
- release-note context for user-facing fixes in PR body or commit message; credit human reporter/contributor when known
6. Review, refresh, and publish:
- rebase or otherwise refresh the PR branch on current `origin/main`
- resolve drift, including newly exposed CI failures, rather than counting the PR as ready
- changelog-only conflicts are routine on busy `main`; resolve them mechanically when already refreshing, but do not treat them as a real code conflict, a reason to reject the PR, or evidence that the branch needs extra fixup beyond the changelog entry order
- do not add `CHANGELOG.md` during normal sweep PRs; release automation generates it from PRs and commits
- left-test the rebased head with the smallest meaningful local/Testbox/live command that proves the bug
- run `$autoreview` until no accepted/actionable findings remain before creating, updating, or presenting the PR URL
- create/update PR with real body and proof fields

View File

@@ -139,12 +139,12 @@ Issue triage is review/prove/patch-local by default:
2. Fix only issues that are easy, high-confidence, and narrowly owned by the implicated path.
3. Add focused regression proof when practical.
4. Stop with the dirty diff, touched files, and test/gate output for maintainer review.
5. After maintainer approval to ship, make one commit per accepted fix, with its own changelog entry when user-facing.
5. After maintainer approval to ship, make one commit per accepted fix, with release-note context in the PR body or commit message when user-facing.
6. Pull/rebase, push, then comment and close only the issues that were fixed or explicitly triaged closed.
Do not batch unrelated issue fixes into one commit. Do not publish, comment, close, or label during the review/prove phase.
Missing changelog is not a PR review finding or merge blocker. If landing/fixing a user-visible change, add/update changelog automatically when practical; never ask or block solely on it.
Missing `CHANGELOG.md` is not a PR review finding or merge blocker. If landing/fixing a user-visible change, make sure the PR body or commit message captures the release-note context; never ask or block solely on it.
Only list candidates that pass all gates:
@@ -244,9 +244,8 @@ gh search issues --repo openclaw/openclaw --match title,body --limit 50 \
## Follow PR review and landing hygiene
- Never mention merge conflicts that are relatively easy to resolve, such as
`CHANGELOG.md` entries, in review-only output. These are landing mechanics,
not correctness findings.
- Never mention release-note bookkeeping in review-only output. It is landing
or release-generation mechanics, not a correctness finding.
- If bot review conversations exist on your PR, address them and resolve them yourself once fixed.
- Leave a review conversation unresolved only when reviewer or maintainer judgment is still needed.
- Before landing any PR with non-trivial code changes, run `$autoreview` until no accepted/actionable findings remain, unless equivalent manual review already covered it, the change is trivial/docs-only, or the user opts out.

View File

@@ -23,7 +23,8 @@ Use this skill for release and publish-time workflow. Load `$release-private` if
green. Then branch from that commit so regular development can continue on
`main` while release validation runs.
- Before release branching, commit any dirty files in coherent groups, push,
pull/rebase, then run `/changelog` on `main` and commit/push/pull that
pull/rebase, then generate `CHANGELOG.md` on `main` from merged PRs and all
direct commits since the last reachable release tag. Commit/push/pull that
changelog rewrite immediately before creating the release branch.
- During release planning, inspect both `src/plugins/compat/registry.ts` and
`src/commands/doctor/shared/deprecation-compat.ts` before branching and again
@@ -68,8 +69,8 @@ Use this skill for release and publish-time workflow. Load `$release-private` if
or clawgrit reports. Report regressions explicitly. A major regression is a
release blocker unless the operator waives it or the data clearly proves
infrastructure noise.
- Use `/changelog` before version/tag preparation so the top changelog section
is deduped and ordered by user impact.
- Generate the changelog before version/tag preparation so the top changelog
section is deduped and ordered by user impact.
- Do not create beta-specific `CHANGELOG.md` headings. Beta releases use the
stable base version section, for example `v2026.4.20-beta.1` uses
`## 2026.4.20` release notes.
@@ -136,11 +137,25 @@ Use this skill for release and publish-time workflow. Load `$release-private` if
## Build changelog-backed release notes
- `CHANGELOG.md` is release-owned. Normal PRs and direct `main` fixes should
not edit it.
- Before release branching or tagging, rewrite the target `CHANGELOG.md`
section from commit history, not just from existing notes: scan commits since
the last reachable release tag, add missed user-facing changes, dedupe
overlapping entries, and sort each section from most to least interesting for
users.
section from history, not existing notes. Use the last reachable stable or
beta release tag as the base, then inspect every commit through the target
release SHA.
- Include both merged PR commits and direct commits on `main`. Direct commits
matter: infer notes from their subject, body, touched files, linked issues,
tests, and nearby code when no PR body exists.
- Prefer PR bodies, issue links, review proof, and commit bodies over commit
subjects alone. If a commit fixed an issue directly, the commit body should
name the user-visible behavior, affected surface, issue ref, and credited
reporter/contributor when known.
- Treat missing context as a release-note audit gap: inspect the diff and linked
issue, draft the best accurate entry, and note the uncertainty for maintainer
review rather than inventing impact.
- Add missed user-facing changes, remove internal-only noise, dedupe overlapping
PR/direct-commit entries, and sort each section from most to least interesting
for users.
- Changelog entries should be user-facing, not internal release-process notes.
- GitHub release and prerelease bodies must use the full matching
`CHANGELOG.md` version section, not highlights or an excerpt. When creating

View File

@@ -123,14 +123,14 @@ runs:
shell: bash
run: |
set -euo pipefail
docker pull "${OPENCLAW_DOCKER_E2E_BARE_IMAGE}"
bash scripts/ci-docker-pull-retry.sh "${OPENCLAW_DOCKER_E2E_BARE_IMAGE}"
- name: Pull shared functional Docker E2E image
if: inputs.hydrate-artifacts == 'true' && steps.plan.outputs.needs_functional_image == '1'
shell: bash
run: |
set -euo pipefail
docker pull "${OPENCLAW_DOCKER_E2E_FUNCTIONAL_IMAGE}"
bash scripts/ci-docker-pull-retry.sh "${OPENCLAW_DOCKER_E2E_FUNCTIONAL_IMAGE}"
- name: Validate Docker E2E credentials
if: inputs.hydrate-artifacts == 'true'

View File

@@ -26,11 +26,23 @@ inputs:
runs:
using: composite
steps:
- name: Normalize container toolcache
shell: bash
run: |
set -euo pipefail
if [[ -d /__t && ! -e /opt/hostedtoolcache ]]; then
mkdir -p /opt
ln -s /__t /opt/hostedtoolcache
fi
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: ${{ inputs.node-version }}
check-latest: false
shell: bash
env:
REQUESTED_NODE_VERSION: ${{ inputs.node-version }}
run: |
set -euo pipefail
source "$GITHUB_ACTION_PATH/../setup-pnpm-store-cache/ensure-node.sh"
openclaw_ensure_node "$REQUESTED_NODE_VERSION"
- name: Setup pnpm
uses: ./.github/actions/setup-pnpm-store-cache
@@ -40,9 +52,10 @@ runs:
- name: Setup Bun
if: inputs.install-bun == 'true'
uses: oven-sh/setup-bun@v2.2.0
with:
bun-version: "1.3.13"
shell: bash
run: |
set -euo pipefail
npm install -g bun@1.3.13
- name: Runtime versions
shell: bash

View File

@@ -14,7 +14,7 @@ inputs:
required: false
default: ""
use-actions-cache:
description: Whether pnpm/action-setup should cache the pnpm store.
description: Whether actions/cache should cache the pnpm store.
required: false
default: "true"
outputs:
@@ -47,12 +47,42 @@ runs:
openclaw_ensure_node "$requested_node"
- name: Setup pnpm from packageManager
uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093
shell: bash
env:
COREPACK_ENABLE_DOWNLOAD_PROMPT: "0"
PACKAGE_MANAGER_FILE: ${{ inputs.package-manager-file }}
run: |
set -euo pipefail
package_manager="$(node -e "const fs = require('node:fs'); const path = require('node:path'); const pkg = JSON.parse(fs.readFileSync(path.resolve(process.argv[1]), 'utf8')); process.stdout.write(pkg.packageManager || '')" "$PACKAGE_MANAGER_FILE")"
case "$package_manager" in
pnpm@*) ;;
*)
echo "::error::Expected packageManager to pin pnpm, got '${package_manager:-<empty>}'"
exit 1
;;
esac
corepack enable
corepack prepare "$package_manager" --activate
- name: Resolve pnpm store path
id: pnpm-store
if: ${{ inputs.use-actions-cache == 'true' && runner.os != 'Windows' }}
shell: bash
run: |
set -euo pipefail
store_path="$(pnpm store path --silent)"
node -e "require('node:fs').mkdirSync(process.argv[1], { recursive: true })" "$store_path"
echo "path=$store_path" >> "$GITHUB_OUTPUT"
- name: Restore pnpm store cache
if: ${{ inputs.use-actions-cache == 'true' && runner.os != 'Windows' }}
uses: actions/cache@v5
with:
package_json_file: ${{ inputs.package-manager-file }}
run_install: false
cache: ${{ inputs.use-actions-cache }}
cache_dependency_path: ${{ inputs.lockfile-path }}
path: ${{ steps.pnpm-store.outputs.path }}
key: pnpm-store-${{ runner.os }}-${{ inputs.node-version }}-${{ hashFiles(inputs.lockfile-path) }}
restore-keys: |
pnpm-store-${{ runner.os }}-${{ inputs.node-version }}-
pnpm-store-${{ runner.os }}-
- name: Record pnpm version
id: pnpm-version

View File

@@ -28,9 +28,17 @@ openclaw_active_node_version() {
openclaw_prepend_node_bin() {
local node_bin_dir="$1"
export PATH="$node_bin_dir:$PATH"
local shell_node_bin_dir="$node_bin_dir"
if command -v cygpath >/dev/null 2>&1; then
shell_node_bin_dir="$(cygpath -u "$node_bin_dir" 2>/dev/null || printf '%s' "$node_bin_dir")"
fi
export PATH="$shell_node_bin_dir:$PATH"
if [[ -n "${GITHUB_PATH:-}" ]]; then
echo "$node_bin_dir" >> "$GITHUB_PATH"
local github_node_bin_dir="$shell_node_bin_dir"
if command -v cygpath >/dev/null 2>&1; then
github_node_bin_dir="$(cygpath -w "$shell_node_bin_dir" 2>/dev/null || printf '%s' "$shell_node_bin_dir")"
fi
echo "$github_node_bin_dir" >> "$GITHUB_PATH"
fi
hash -r
}
@@ -43,6 +51,7 @@ openclaw_find_toolcache_node() {
"${RUNNER_TOOL_CACHE:-}" \
"${AGENT_TOOLSDIRECTORY:-}" \
"${ACTIONS_RUNNER_TOOL_CACHE:-}" \
"${OPENCLAW_CONTAINER_TOOL_CACHE:-/__t}" \
"/opt/hostedtoolcache" \
"/home/runner/_work/_tool" \
"/Users/runner/hostedtoolcache" \
@@ -68,6 +77,56 @@ openclaw_find_toolcache_node() {
return 1
}
openclaw_resolve_node_download_version() {
local requested_node="$1"
if [[ "$requested_node" =~ ^v?[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
[[ "$requested_node" == v* ]] && printf '%s\n' "$requested_node" || printf 'v%s\n' "$requested_node"
return 0
fi
local prefix="${requested_node#v}"
prefix="${prefix%%[xX]*}"
prefix="v${prefix}"
[[ "$prefix" == *. ]] || prefix="${prefix}."
curl -fsSL https://nodejs.org/dist/index.json |
OPENCLAW_NODE_PREFIX="$prefix" python3 -c 'import json, os, sys
prefix = os.environ["OPENCLAW_NODE_PREFIX"]
for item in json.load(sys.stdin):
version = item.get("version", "")
if version.startswith(prefix):
print(version)
break
'
}
openclaw_node_download_platform() {
local os_name arch_name
os_name="$(uname -s)"
arch_name="$(uname -m)"
case "$os_name:$arch_name" in
Linux:x86_64) printf 'linux-x64\n' ;;
Linux:aarch64 | Linux:arm64) printf 'linux-arm64\n' ;;
Darwin:x86_64) printf 'darwin-x64\n' ;;
Darwin:arm64) printf 'darwin-arm64\n' ;;
*)
return 1
;;
esac
}
openclaw_download_node() {
local requested_node="$1"
local version platform archive_url install_root
version="$(openclaw_resolve_node_download_version "$requested_node")"
platform="$(openclaw_node_download_platform)" || return 1
install_root="${RUNNER_TEMP:-/tmp}/openclaw-node-${version}-${platform}"
archive_url="https://nodejs.org/dist/${version}/node-${version}-${platform}.tar.xz"
mkdir -p "$install_root"
echo "Downloading Node ${version} from ${archive_url}"
curl -fsSL "$archive_url" | tar -xJ -C "$install_root" --strip-components=1
openclaw_prepend_node_bin "$install_root/bin"
}
openclaw_ensure_node() {
local requested_node="${1:-}"
requested_node="${requested_node#v}"
@@ -86,6 +145,8 @@ openclaw_ensure_node() {
if [[ -n "$node_bin" ]]; then
echo "Using Node $("$node_bin" -p 'process.versions.node') from $node_bin"
openclaw_prepend_node_bin "$(dirname "$node_bin")"
else
openclaw_download_node "$requested_node" || true
fi
active_node_version="$(openclaw_active_node_version)"

View File

@@ -12,7 +12,7 @@ Hard limits:
- Do not change production code, tests, package metadata, generated baselines, lockfiles, or CI config.
- Keep changes minimal and factual.
- Use "plugin/plugins" in user-facing docs/UI/changelog; `extensions/` is only the internal workspace layout.
- Do not add a changelog entry unless the docs update describes a user-facing behavior/API change from the triggering commit.
- Do not add `CHANGELOG.md` entries during normal docs work. Capture user-facing release-note context in the PR body or commit message instead.
Allowed paths:

View File

@@ -61,7 +61,7 @@ jobs:
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}"
git -C "$workdir" config gc.auto 0
timeout --signal=TERM 30s git -C "$workdir" \
timeout --signal=TERM --kill-after=10s 30s git -C "$workdir" \
-c protocol.version=2 \
-c "http.extraheader=AUTHORIZATION: basic ${auth_header}" \
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \

View File

@@ -59,7 +59,7 @@ jobs:
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}"
git -C "$workdir" config gc.auto 0
timeout --signal=TERM 30s git -C "$workdir" \
timeout --signal=TERM --kill-after=10s 30s git -C "$workdir" \
-c protocol.version=2 \
-c "http.extraheader=AUTHORIZATION: basic ${auth_header}" \
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
@@ -132,6 +132,5 @@ jobs:
- name: Run Testbox
uses: useblacksmith/run-testbox@5ca05834db1d3813554d1dd109e5f2087a8d7cbc
if: success()
continue-on-error: true
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"

View File

@@ -76,13 +76,16 @@ jobs:
android_matrix: ${{ steps.manifest.outputs.android_matrix }}
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: ${{ inputs.target_ref || github.sha }}
fetch-depth: 1
fetch-tags: false
persist-credentials: true
submodules: false
env:
CHECKOUT_REPO: ${{ github.repository }}
CHECKOUT_REF: ${{ inputs.target_ref || github.sha }}
run: |
set -euo pipefail
git init "$GITHUB_WORKSPACE"
git -C "$GITHUB_WORKSPACE" config gc.auto 0
git -C "$GITHUB_WORKSPACE" remote add origin "https://github.com/${CHECKOUT_REPO}.git"
git -C "$GITHUB_WORKSPACE" fetch --no-tags --depth=1 origin "+${CHECKOUT_REF}:refs/remotes/origin/checkout"
git -C "$GITHUB_WORKSPACE" checkout --detach refs/remotes/origin/checkout
- name: Resolve checkout SHA
id: checkout_ref
@@ -299,13 +302,16 @@ jobs:
PRE_COMMIT_HOME: .cache/pre-commit-security-fast
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: ${{ inputs.target_ref || github.sha }}
fetch-depth: 1
fetch-tags: false
persist-credentials: true
submodules: false
env:
CHECKOUT_REPO: ${{ github.repository }}
CHECKOUT_REF: ${{ inputs.target_ref || github.sha }}
run: |
set -euo pipefail
git init "$GITHUB_WORKSPACE"
git -C "$GITHUB_WORKSPACE" config gc.auto 0
git -C "$GITHUB_WORKSPACE" remote add origin "https://github.com/${CHECKOUT_REPO}.git"
git -C "$GITHUB_WORKSPACE" fetch --no-tags --depth=1 origin "+${CHECKOUT_REF}:refs/remotes/origin/checkout"
git -C "$GITHUB_WORKSPACE" checkout --detach refs/remotes/origin/checkout
- name: Ensure security base commit
if: github.event_name != 'workflow_dispatch'
@@ -335,22 +341,20 @@ jobs:
fi
echo "PRE_COMMIT_CONFIG_PATH=$trusted_config" >> "$GITHUB_ENV"
- name: Setup Python
- name: Resolve Python runtime
id: setup-python
uses: actions/setup-python@v6
with:
python-version: "3.12"
- name: Restore pre-commit cache
uses: actions/cache@v5
with:
path: .cache/pre-commit-security-fast
key: pre-commit-security-fast-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('.pre-commit-config.yaml') }}
restore-keys: |
pre-commit-security-fast-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-
run: |
set -euo pipefail
python3 --version
version="$(python3 - <<'PY'
import platform
print(platform.python_version())
PY
)"
echo "python-version=${version}" >> "$GITHUB_OUTPUT"
- name: Install pre-commit
run: python -m pip install --disable-pip-version-check pre-commit==4.2.0
run: python3 -m pip install --disable-pip-version-check pre-commit==4.2.0
- name: Detect committed private keys
run: pre-commit run --config "${PRE_COMMIT_CONFIG_PATH:-.pre-commit-config.yaml}" --all-files detect-private-key
@@ -383,10 +387,12 @@ jobs:
pre-commit run --config "${PRE_COMMIT_CONFIG_PATH:-.pre-commit-config.yaml}" zizmor --files "${workflow_files[@]}"
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: "24.x"
check-latest: false
env:
REQUESTED_NODE_VERSION: "24.x"
run: |
set -euo pipefail
source .github/actions/setup-pnpm-store-cache/ensure-node.sh
openclaw_ensure_node "$REQUESTED_NODE_VERSION"
- name: Audit production dependencies
run: node scripts/pre-commit/pnpm-audit-prod.mjs --audit-level=high
@@ -411,7 +417,6 @@ jobs:
env:
CHECKOUT_REPO: ${{ github.repository }}
CHECKOUT_SHA: ${{ needs.preflight.outputs.checkout_revision }}
CHECKOUT_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
@@ -427,10 +432,10 @@ jobs:
reset_checkout_dir
git init "$workdir" >/dev/null
git config --global --add safe.directory "$workdir"
git -C "$workdir" remote add origin "https://x-access-token:${CHECKOUT_TOKEN}@github.com/${CHECKOUT_REPO}.git"
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}.git"
git -C "$workdir" config gc.auto 0
timeout --signal=TERM 30s git -C "$workdir" \
timeout --signal=TERM --kill-after=10s 30s git -C "$workdir" \
-c protocol.version=2 \
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
"+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1
@@ -513,7 +518,24 @@ jobs:
run: pnpm test:build:singleton
- name: Check CLI startup memory
run: pnpm test:startup:memory
shell: bash
run: |
set +e
pnpm test:startup:memory
status=$?
if [[ -f .artifacts/startup-memory/summary.md ]]; then
cat .artifacts/startup-memory/summary.md >> "$GITHUB_STEP_SUMMARY"
fi
exit "$status"
- name: Upload startup memory report
if: always()
uses: actions/upload-artifact@v7
with:
name: startup-memory
path: .artifacts/startup-memory/
if-no-files-found: ignore
retention-days: 7
- name: Run built artifact checks
id: built_artifact_checks
@@ -619,7 +641,6 @@ jobs:
env:
CHECKOUT_REPO: ${{ github.repository }}
CHECKOUT_SHA: ${{ needs.preflight.outputs.checkout_revision }}
CHECKOUT_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
@@ -635,10 +656,10 @@ jobs:
reset_checkout_dir
git init "$workdir" >/dev/null
git config --global --add safe.directory "$workdir"
git -C "$workdir" remote add origin "https://x-access-token:${CHECKOUT_TOKEN}@github.com/${CHECKOUT_REPO}.git"
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}.git"
git -C "$workdir" config gc.auto 0
timeout --signal=TERM 30s git -C "$workdir" \
timeout --signal=TERM --kill-after=10s 30s git -C "$workdir" \
-c protocol.version=2 \
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
"+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1
@@ -706,7 +727,6 @@ jobs:
env:
CHECKOUT_REPO: ${{ github.repository }}
CHECKOUT_SHA: ${{ needs.preflight.outputs.checkout_revision }}
CHECKOUT_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
@@ -722,10 +742,10 @@ jobs:
reset_checkout_dir
git init "$workdir" >/dev/null
git config --global --add safe.directory "$workdir"
git -C "$workdir" remote add origin "https://x-access-token:${CHECKOUT_TOKEN}@github.com/${CHECKOUT_REPO}.git"
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}.git"
git -C "$workdir" config gc.auto 0
timeout --signal=TERM 30s git -C "$workdir" \
timeout --signal=TERM --kill-after=10s 30s git -C "$workdir" \
-c protocol.version=2 \
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
"+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1
@@ -787,7 +807,6 @@ jobs:
env:
CHECKOUT_REPO: ${{ github.repository }}
CHECKOUT_SHA: ${{ needs.preflight.outputs.checkout_revision }}
CHECKOUT_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
@@ -803,10 +822,10 @@ jobs:
reset_checkout_dir
git init "$workdir" >/dev/null
git config --global --add safe.directory "$workdir"
git -C "$workdir" remote add origin "https://x-access-token:${CHECKOUT_TOKEN}@github.com/${CHECKOUT_REPO}.git"
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}.git"
git -C "$workdir" config gc.auto 0
timeout --signal=TERM 30s git -C "$workdir" \
timeout --signal=TERM --kill-after=10s 30s git -C "$workdir" \
-c protocol.version=2 \
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
"+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1
@@ -865,7 +884,6 @@ jobs:
env:
CHECKOUT_REPO: ${{ github.repository }}
CHECKOUT_SHA: ${{ needs.preflight.outputs.checkout_revision }}
CHECKOUT_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
@@ -881,10 +899,10 @@ jobs:
reset_checkout_dir
git init "$workdir" >/dev/null
git config --global --add safe.directory "$workdir"
git -C "$workdir" remote add origin "https://x-access-token:${CHECKOUT_TOKEN}@github.com/${CHECKOUT_REPO}.git"
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}.git"
git -C "$workdir" config gc.auto 0
timeout --signal=TERM 30s git -C "$workdir" \
timeout --signal=TERM --kill-after=10s 30s git -C "$workdir" \
-c protocol.version=2 \
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
"+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1
@@ -941,7 +959,6 @@ jobs:
env:
CHECKOUT_REPO: ${{ github.repository }}
CHECKOUT_SHA: ${{ needs.preflight.outputs.checkout_revision }}
CHECKOUT_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
@@ -957,10 +974,10 @@ jobs:
reset_checkout_dir
git init "$workdir" >/dev/null
git config --global --add safe.directory "$workdir"
git -C "$workdir" remote add origin "https://x-access-token:${CHECKOUT_TOKEN}@github.com/${CHECKOUT_REPO}.git"
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}.git"
git -C "$workdir" config gc.auto 0
timeout --signal=TERM 30s git -C "$workdir" \
timeout --signal=TERM --kill-after=10s 30s git -C "$workdir" \
-c protocol.version=2 \
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
"+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1
@@ -1064,7 +1081,6 @@ jobs:
env:
CHECKOUT_REPO: ${{ github.repository }}
CHECKOUT_SHA: ${{ needs.preflight.outputs.checkout_revision }}
CHECKOUT_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
@@ -1080,10 +1096,10 @@ jobs:
reset_checkout_dir
git init "$workdir" >/dev/null
git config --global --add safe.directory "$workdir"
git -C "$workdir" remote add origin "https://x-access-token:${CHECKOUT_TOKEN}@github.com/${CHECKOUT_REPO}.git"
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}.git"
git -C "$workdir" config gc.auto 0
timeout --signal=TERM 30s git -C "$workdir" \
timeout --signal=TERM --kill-after=10s 30s git -C "$workdir" \
-c protocol.version=2 \
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
"+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1
@@ -1195,7 +1211,6 @@ jobs:
env:
CHECKOUT_REPO: ${{ github.repository }}
CHECKOUT_SHA: ${{ needs.preflight.outputs.checkout_revision }}
CHECKOUT_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
@@ -1211,10 +1226,10 @@ jobs:
reset_checkout_dir
git init "$workdir" >/dev/null
git config --global --add safe.directory "$workdir"
git -C "$workdir" remote add origin "https://x-access-token:${CHECKOUT_TOKEN}@github.com/${CHECKOUT_REPO}.git"
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}.git"
git -C "$workdir" config gc.auto 0
timeout --signal=TERM 30s git -C "$workdir" \
timeout --signal=TERM --kill-after=10s 30s git -C "$workdir" \
-c protocol.version=2 \
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
"+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1
@@ -1345,7 +1360,6 @@ jobs:
env:
CHECKOUT_REPO: ${{ github.repository }}
CHECKOUT_SHA: ${{ needs.preflight.outputs.checkout_revision }}
CHECKOUT_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
@@ -1361,10 +1375,10 @@ jobs:
reset_checkout_dir
git init "$workdir" >/dev/null
git config --global --add safe.directory "$workdir"
git -C "$workdir" remote add origin "https://x-access-token:${CHECKOUT_TOKEN}@github.com/${CHECKOUT_REPO}.git"
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}.git"
git -C "$workdir" config gc.auto 0
timeout --signal=TERM 30s git -C "$workdir" \
timeout --signal=TERM --kill-after=10s 30s git -C "$workdir" \
-c protocol.version=2 \
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
"+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1
@@ -1391,12 +1405,13 @@ jobs:
install-bun: "false"
- name: Checkout ClawHub docs source
uses: actions/checkout@v6
with:
repository: openclaw/clawhub
path: clawhub-source
fetch-depth: 1
persist-credentials: true
run: |
set -euo pipefail
git init clawhub-source
git -C clawhub-source config gc.auto 0
git -C clawhub-source remote add origin "https://github.com/openclaw/clawhub.git"
git -C clawhub-source fetch --no-tags --depth=1 origin "+HEAD:refs/remotes/origin/checkout"
git -C clawhub-source checkout --detach refs/remotes/origin/checkout
- name: Check docs
env:
@@ -1412,11 +1427,16 @@ jobs:
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: ${{ needs.preflight.outputs.checkout_revision }}
persist-credentials: true
submodules: false
env:
CHECKOUT_REPO: ${{ github.repository }}
CHECKOUT_SHA: ${{ needs.preflight.outputs.checkout_revision }}
run: |
set -euo pipefail
git init "$GITHUB_WORKSPACE"
git -C "$GITHUB_WORKSPACE" config gc.auto 0
git -C "$GITHUB_WORKSPACE" remote add origin "https://github.com/${CHECKOUT_REPO}.git"
git -C "$GITHUB_WORKSPACE" fetch --no-tags --depth=1 origin "+${CHECKOUT_SHA}:refs/remotes/origin/checkout"
git -C "$GITHUB_WORKSPACE" checkout --detach refs/remotes/origin/checkout
- name: Setup Python
uses: actions/setup-python@v6
@@ -1455,11 +1475,16 @@ jobs:
matrix: ${{ fromJson(needs.preflight.outputs.checks_windows_matrix) }}
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: ${{ needs.preflight.outputs.checkout_revision }}
persist-credentials: true
submodules: false
env:
CHECKOUT_REPO: ${{ github.repository }}
CHECKOUT_SHA: ${{ needs.preflight.outputs.checkout_revision }}
run: |
set -euo pipefail
git init "$GITHUB_WORKSPACE"
git -C "$GITHUB_WORKSPACE" config gc.auto 0
git -C "$GITHUB_WORKSPACE" remote add origin "https://github.com/${CHECKOUT_REPO}.git"
git -C "$GITHUB_WORKSPACE" fetch --no-tags --depth=1 origin "+${CHECKOUT_SHA}:refs/remotes/origin/checkout"
git -C "$GITHUB_WORKSPACE" checkout --detach refs/remotes/origin/checkout
- name: Try to exclude workspace from Windows Defender (best-effort)
shell: pwsh
@@ -1481,10 +1506,12 @@ jobs:
}
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 24.x
check-latest: false
env:
REQUESTED_NODE_VERSION: "24.x"
run: |
set -euo pipefail
source .github/actions/setup-pnpm-store-cache/ensure-node.sh
openclaw_ensure_node "$REQUESTED_NODE_VERSION"
- name: Setup pnpm
uses: ./.github/actions/setup-pnpm-store-cache
@@ -1548,11 +1575,16 @@ jobs:
matrix: ${{ fromJson(needs.preflight.outputs.macos_node_matrix) }}
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: ${{ needs.preflight.outputs.checkout_revision }}
persist-credentials: true
submodules: false
env:
CHECKOUT_REPO: ${{ github.repository }}
CHECKOUT_SHA: ${{ needs.preflight.outputs.checkout_revision }}
run: |
set -euo pipefail
git init "$GITHUB_WORKSPACE"
git -C "$GITHUB_WORKSPACE" config gc.auto 0
git -C "$GITHUB_WORKSPACE" remote add origin "https://github.com/${CHECKOUT_REPO}.git"
git -C "$GITHUB_WORKSPACE" fetch --no-tags --depth=1 origin "+${CHECKOUT_SHA}:refs/remotes/origin/checkout"
git -C "$GITHUB_WORKSPACE" checkout --detach refs/remotes/origin/checkout
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
@@ -1589,11 +1621,16 @@ jobs:
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: ${{ needs.preflight.outputs.checkout_revision }}
persist-credentials: true
submodules: false
env:
CHECKOUT_REPO: ${{ github.repository }}
CHECKOUT_SHA: ${{ needs.preflight.outputs.checkout_revision }}
run: |
set -euo pipefail
git init "$GITHUB_WORKSPACE"
git -C "$GITHUB_WORKSPACE" config gc.auto 0
git -C "$GITHUB_WORKSPACE" remote add origin "https://github.com/${CHECKOUT_REPO}.git"
git -C "$GITHUB_WORKSPACE" fetch --no-tags --depth=1 origin "+${CHECKOUT_SHA}:refs/remotes/origin/checkout"
git -C "$GITHUB_WORKSPACE" checkout --detach refs/remotes/origin/checkout
- name: Install XcodeGen / SwiftLint / SwiftFormat
run: brew install xcodegen swiftlint swiftformat
@@ -1693,7 +1730,6 @@ jobs:
env:
CHECKOUT_REPO: ${{ github.repository }}
CHECKOUT_SHA: ${{ needs.preflight.outputs.checkout_revision }}
CHECKOUT_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
@@ -1709,10 +1745,10 @@ jobs:
reset_checkout_dir
git init "$workdir" >/dev/null
git config --global --add safe.directory "$workdir"
git -C "$workdir" remote add origin "https://x-access-token:${CHECKOUT_TOKEN}@github.com/${CHECKOUT_REPO}.git"
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}.git"
git -C "$workdir" config gc.auto 0
timeout --signal=TERM 30s git -C "$workdir" \
timeout --signal=TERM --kill-after=10s 30s git -C "$workdir" \
-c protocol.version=2 \
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
"+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1

View File

@@ -141,7 +141,13 @@ jobs:
if ! command -v docker >/dev/null 2>&1; then
echo "docker not found; installing fallback engine"
curl -fsSL https://get.docker.com | sudo sh
curl --fail --show-error --location \
--connect-timeout "${OPENCLAW_CRABBOX_HYDRATE_DOWNLOAD_CONNECT_TIMEOUT_SECONDS:-15}" \
--max-time "${OPENCLAW_CRABBOX_HYDRATE_DOWNLOAD_TIMEOUT_SECONDS:-300}" \
--retry "${OPENCLAW_CRABBOX_HYDRATE_DOWNLOAD_RETRIES:-3}" \
--retry-delay "${OPENCLAW_CRABBOX_HYDRATE_DOWNLOAD_RETRY_DELAY_SECONDS:-5}" \
--retry-all-errors \
https://get.docker.com | sudo sh
fi
if command -v systemctl >/dev/null 2>&1; then
@@ -166,7 +172,12 @@ jobs:
esac
buildx_version="${DOCKER_BUILDX_VERSION:-v0.15.1}"
mkdir -p "$HOME/.docker/cli-plugins"
curl -fsSL \
curl --fail --show-error --location \
--connect-timeout "${OPENCLAW_CRABBOX_HYDRATE_DOWNLOAD_CONNECT_TIMEOUT_SECONDS:-15}" \
--max-time "${OPENCLAW_CRABBOX_HYDRATE_DOWNLOAD_TIMEOUT_SECONDS:-300}" \
--retry "${OPENCLAW_CRABBOX_HYDRATE_DOWNLOAD_RETRIES:-3}" \
--retry-delay "${OPENCLAW_CRABBOX_HYDRATE_DOWNLOAD_RETRY_DELAY_SECONDS:-5}" \
--retry-all-errors \
"https://github.com/docker/buildx/releases/download/${buildx_version}/buildx-${buildx_version}.linux-${buildx_arch}" \
-o "$HOME/.docker/cli-plugins/docker-buildx"
chmod 0755 "$HOME/.docker/cli-plugins/docker-buildx"
@@ -307,7 +318,13 @@ jobs:
if ! command -v docker >/dev/null 2>&1; then
echo "docker not found; installing fallback engine"
curl -fsSL https://get.docker.com | sudo sh
curl --fail --show-error --location \
--connect-timeout "${OPENCLAW_CRABBOX_HYDRATE_DOWNLOAD_CONNECT_TIMEOUT_SECONDS:-15}" \
--max-time "${OPENCLAW_CRABBOX_HYDRATE_DOWNLOAD_TIMEOUT_SECONDS:-300}" \
--retry "${OPENCLAW_CRABBOX_HYDRATE_DOWNLOAD_RETRIES:-3}" \
--retry-delay "${OPENCLAW_CRABBOX_HYDRATE_DOWNLOAD_RETRY_DELAY_SECONDS:-5}" \
--retry-all-errors \
https://get.docker.com | sudo sh
fi
if command -v systemctl >/dev/null 2>&1; then
@@ -332,7 +349,12 @@ jobs:
esac
buildx_version="${DOCKER_BUILDX_VERSION:-v0.15.1}"
mkdir -p "$HOME/.docker/cli-plugins"
curl -fsSL \
curl --fail --show-error --location \
--connect-timeout "${OPENCLAW_CRABBOX_HYDRATE_DOWNLOAD_CONNECT_TIMEOUT_SECONDS:-15}" \
--max-time "${OPENCLAW_CRABBOX_HYDRATE_DOWNLOAD_TIMEOUT_SECONDS:-300}" \
--retry "${OPENCLAW_CRABBOX_HYDRATE_DOWNLOAD_RETRIES:-3}" \
--retry-delay "${OPENCLAW_CRABBOX_HYDRATE_DOWNLOAD_RETRY_DELAY_SECONDS:-5}" \
--retry-all-errors \
"https://github.com/docker/buildx/releases/download/${buildx_version}/buildx-${buildx_version}.linux-${buildx_arch}" \
-o "$HOME/.docker/cli-plugins/docker-buildx"
chmod 0755 "$HOME/.docker/cli-plugins/docker-buildx"

View File

@@ -245,7 +245,7 @@ jobs:
DOCKER_BUILDKIT: "1"
run: |
set -euo pipefail
timeout --foreground --kill-after=30s 35m docker build \
timeout --kill-after=30s 35m docker build \
--target runtime-assets \
--build-arg OPENCLAW_EXTENSIONS="diagnostics-otel,codex" \
.
@@ -287,7 +287,7 @@ jobs:
printf '%s\n' "$output"
return 0
fi
if [[ "$output" == *"Bad credentials"* || "$output" == *"HTTP 401"* || "$output" == *"secondary rate limit"* || "$output" == *"API rate limit"* ]]; then
if [[ "$output" == *"Bad credentials"* || "$output" == *"HTTP 401"* || "$output" == *"secondary rate limit"* || "$output" == *"API rate limit"* || "$output" == *"Sorry. Your account was suspended"* ]]; then
echo "::warning::gh $* failed on attempt ${attempt}: ${output}" >&2
sleep $((attempt * 10))
continue
@@ -417,7 +417,7 @@ jobs:
printf '%s\n' "$output"
return 0
fi
if [[ "$output" == *"Bad credentials"* || "$output" == *"HTTP 401"* || "$output" == *"secondary rate limit"* || "$output" == *"API rate limit"* ]]; then
if [[ "$output" == *"Bad credentials"* || "$output" == *"HTTP 401"* || "$output" == *"secondary rate limit"* || "$output" == *"API rate limit"* || "$output" == *"Sorry. Your account was suspended"* ]]; then
echo "::warning::gh $* failed on attempt ${attempt}: ${output}" >&2
sleep $((attempt * 10))
continue
@@ -557,7 +557,7 @@ jobs:
printf '%s\n' "$output"
return 0
fi
if [[ "$output" == *"Bad credentials"* || "$output" == *"HTTP 401"* || "$output" == *"secondary rate limit"* || "$output" == *"API rate limit"* ]]; then
if [[ "$output" == *"Bad credentials"* || "$output" == *"HTTP 401"* || "$output" == *"secondary rate limit"* || "$output" == *"API rate limit"* || "$output" == *"Sorry. Your account was suspended"* ]]; then
echo "::warning::gh $* failed on attempt ${attempt}: ${output}" >&2
sleep $((attempt * 10))
continue
@@ -859,7 +859,7 @@ jobs:
printf '%s\n' "$output"
return 0
fi
if [[ "$output" == *"Bad credentials"* || "$output" == *"HTTP 401"* || "$output" == *"secondary rate limit"* || "$output" == *"API rate limit"* ]]; then
if [[ "$output" == *"Bad credentials"* || "$output" == *"HTTP 401"* || "$output" == *"secondary rate limit"* || "$output" == *"API rate limit"* || "$output" == *"Sorry. Your account was suspended"* ]]; then
echo "::warning::gh $* failed on attempt ${attempt}: ${output}" >&2
sleep $((attempt * 10))
continue
@@ -975,7 +975,7 @@ jobs:
printf '%s\n' "$output"
return 0
fi
if [[ "$output" == *"Bad credentials"* || "$output" == *"HTTP 401"* || "$output" == *"secondary rate limit"* || "$output" == *"API rate limit"* ]]; then
if [[ "$output" == *"Bad credentials"* || "$output" == *"HTTP 401"* || "$output" == *"secondary rate limit"* || "$output" == *"API rate limit"* || "$output" == *"Sorry. Your account was suspended"* ]]; then
echo "::warning::gh $* failed on attempt ${attempt}: ${output}" >&2
sleep $((attempt * 10))
continue
@@ -1089,6 +1089,29 @@ jobs:
run: |
set -euo pipefail
gh_with_retry() {
local output status attempt
for attempt in 1 2 3 4 5 6; do
set +e
output="$(gh "$@" 2>&1)"
status=$?
set -e
if [[ "$status" -eq 0 ]]; then
printf '%s\n' "$output"
return 0
fi
if [[ "$output" == *"Bad credentials"* || "$output" == *"HTTP 401"* || "$output" == *"secondary rate limit"* || "$output" == *"API rate limit"* || "$output" == *"Sorry. Your account was suspended"* ]]; then
echo "::warning::gh $* failed on attempt ${attempt}: ${output}" >&2
sleep $((attempt * 10))
continue
fi
printf '%s\n' "$output" >&2
return "$status"
done
printf '%s\n' "$output" >&2
return "$status"
}
release_check_blocking_job() {
case "$1" in
"resolve_target" | \
@@ -1145,7 +1168,7 @@ jobs:
fi
local run_json status conclusion url attempt head_sha
run_json="$(gh run view "$run_id" --json status,conclusion,url,attempt,headSha,jobs)"
run_json="$(gh_with_retry run view "$run_id" --json status,conclusion,url,attempt,headSha,jobs)"
status="$(jq -r '.status' <<< "$run_json")"
conclusion="$(jq -r '.conclusion' <<< "$run_json")"
url="$(jq -r '.url' <<< "$run_json")"
@@ -1192,7 +1215,7 @@ jobs:
fi
local run_json row
run_json="$(gh run view "$run_id" --json status,conclusion,url,createdAt,updatedAt,headSha)"
run_json="$(gh_with_retry run view "$run_id" --json status,conclusion,url,createdAt,updatedAt,headSha)"
row="$(
jq -r --arg label "$label" '
def ts: fromdateiso8601;
@@ -1228,7 +1251,7 @@ jobs:
echo
echo "### Slowest jobs: ${label}"
echo
gh run view "$run_id" --json jobs --jq '
gh_with_retry run view "$run_id" --json jobs --jq '
def ts: fromdateiso8601;
"| Job | Result | Minutes |",
"| --- | --- | ---: |",
@@ -1245,7 +1268,7 @@ jobs:
echo
echo "### Longest queues: ${label}"
echo
gh api --paginate "repos/${GITHUB_REPOSITORY}/actions/runs/${run_id}/jobs?per_page=100" --jq ".jobs[] | @json" | jq -sr '
gh_with_retry api --paginate "repos/${GITHUB_REPOSITORY}/actions/runs/${run_id}/jobs?per_page=100" --jq ".jobs[] | @json" | jq -sr '
def ts: fromdateiso8601;
"| Job | Result | Queue minutes | Run minutes |",
"| --- | --- | ---: | ---: |",
@@ -1274,7 +1297,7 @@ jobs:
fi
local run_json status conclusion artifacts_json
run_json="$(gh run view "$run_id" --json status,conclusion,url,jobs)"
run_json="$(gh_with_retry run view "$run_id" --json status,conclusion,url,jobs)"
status="$(jq -r '.status' <<< "$run_json")"
conclusion="$(jq -r '.conclusion' <<< "$run_json")"
if [[ "$status" == "completed" && "$conclusion" == "success" ]]; then
@@ -1297,7 +1320,7 @@ jobs:
echo
echo "Artifacts:"
artifacts_json="$(
gh api "repos/${GITHUB_REPOSITORY}/actions/runs/${run_id}/artifacts?per_page=100" 2>/dev/null || true
gh_with_retry api "repos/${GITHUB_REPOSITORY}/actions/runs/${run_id}/artifacts?per_page=100" 2>/dev/null || true
)"
if [[ -n "${artifacts_json// }" ]]; then
jq -r '
@@ -1471,6 +1494,7 @@ jobs:
PLUGIN_PRERELEASE_RUN_ID: ${{ needs.plugin_prerelease.outputs.run_id }}
RELEASE_CHECKS_RUN_ID: ${{ needs.release_checks.outputs.run_id }}
NPM_TELEGRAM_RUN_ID: ${{ needs.npm_telegram.outputs.run_id }}
PERFORMANCE_RUN_ID: ${{ needs.performance.outputs.run_id }}
run: |
set -euo pipefail
manifest_dir="${RUNNER_TEMP}/full-release-validation"

View File

@@ -121,7 +121,7 @@ jobs:
# builder stalls; an explicit buildx invocation fails closed instead.
- name: Build root Dockerfile smoke image
run: |
timeout 45m docker buildx build \
timeout --kill-after=30s 45m docker buildx build \
--progress=plain \
--load \
--build-arg OPENCLAW_EXTENSIONS=matrix \
@@ -132,7 +132,7 @@ jobs:
- name: Run root Dockerfile CLI smoke
run: |
docker run --rm --entrypoint sh openclaw-dockerfile-smoke:local -lc '
timeout --kill-after=30s 20m docker run --rm --entrypoint sh openclaw-dockerfile-smoke:local -lc '
which openclaw &&
openclaw --version &&
node -e "
@@ -163,7 +163,7 @@ jobs:
- name: Smoke test Dockerfile with matrix extension build arg
run: |
docker run --rm --entrypoint sh openclaw-ext-smoke:local -lc '
timeout --kill-after=30s 20m docker run --rm --entrypoint sh openclaw-ext-smoke:local -lc '
which openclaw &&
openclaw --version &&
node -e "
@@ -235,7 +235,7 @@ jobs:
IMAGE_REF: ${{ needs.preflight.outputs.dockerfile_image }}
run: |
set -euo pipefail
if timeout 180s docker pull "$IMAGE_REF"; then
if timeout --kill-after=30s 180s docker pull "$IMAGE_REF"; then
echo "exists=true" >> "$GITHUB_OUTPUT"
echo "Using existing root Dockerfile smoke image: \`$IMAGE_REF\`" >> "$GITHUB_STEP_SUMMARY"
else
@@ -256,7 +256,7 @@ jobs:
env:
IMAGE_REF: ${{ needs.preflight.outputs.dockerfile_image }}
run: |
timeout 45m docker buildx build \
timeout --kill-after=30s 45m docker buildx build \
--progress=plain \
--push \
--build-arg OPENCLAW_EXTENSIONS=matrix \
@@ -320,13 +320,13 @@ jobs:
- name: Pull root Dockerfile smoke image
env:
IMAGE_REF: ${{ needs.root_dockerfile_image.outputs.image_ref }}
run: timeout 600s docker pull "$IMAGE_REF"
run: timeout --kill-after=30s 600s docker pull "$IMAGE_REF"
- name: Run root Dockerfile CLI smoke
env:
IMAGE_REF: ${{ needs.root_dockerfile_image.outputs.image_ref }}
run: |
docker run --rm --entrypoint sh "$IMAGE_REF" -lc '
timeout --kill-after=30s 20m docker run --rm --entrypoint sh "$IMAGE_REF" -lc '
which openclaw &&
openclaw --version &&
node -e "
@@ -359,7 +359,7 @@ jobs:
env:
IMAGE_REF: ${{ needs.root_dockerfile_image.outputs.image_ref }}
run: |
docker run --rm --entrypoint sh "$IMAGE_REF" -lc '
timeout --kill-after=30s 20m docker run --rm --entrypoint sh "$IMAGE_REF" -lc '
which openclaw &&
openclaw --version &&
node -e "
@@ -426,7 +426,7 @@ jobs:
- name: Pull root Dockerfile smoke image
env:
IMAGE_REF: ${{ needs.root_dockerfile_image.outputs.image_ref }}
run: timeout 600s docker pull "$IMAGE_REF"
run: timeout --kill-after=30s 600s docker pull "$IMAGE_REF"
- name: Set up Blacksmith Docker Builder
uses: useblacksmith/setup-docker-builder@722e97d12b1d06a961800dd6c05d79d951ad3c80 # v1
@@ -435,7 +435,7 @@ jobs:
- name: Build installer smoke image
run: |
timeout 20m docker buildx build \
timeout --kill-after=30s 20m docker buildx build \
--progress=plain \
--load \
-t openclaw-install-smoke:local \
@@ -444,7 +444,7 @@ jobs:
- name: Build installer non-root image
run: |
timeout 20m docker buildx build \
timeout --kill-after=30s 20m docker buildx build \
--progress=plain \
--load \
-t openclaw-install-nonroot:local \
@@ -475,13 +475,22 @@ jobs:
- name: Run Rocky Linux installer smoke
run: |
timeout 20m docker run --rm \
timeout --kill-after=30s 20m docker run --rm \
-e OPENCLAW_NO_ONBOARD=1 \
-e OPENCLAW_NO_PROMPT=1 \
-v "$PWD/scripts/install.sh:/tmp/install.sh:ro" \
rockylinux:9@sha256:d7be1c094cc5845ee815d4632fe377514ee6ebcf8efaed6892889657e5ddaaa6 \
bash -lc 'dnf install -y -q ca-certificates tar gzip xz findutils which sudo >/dev/null && bash /tmp/install.sh --install-method npm --version latest --no-onboard --no-prompt --verify && openclaw --version'
- name: Run Rocky Linux CLI installer smoke
run: |
timeout --kill-after=30s 20m docker run --rm \
-e OPENCLAW_NO_ONBOARD=1 \
-e OPENCLAW_NO_PROMPT=1 \
-v "$PWD/scripts/install-cli.sh:/tmp/install-cli.sh:ro" \
rockylinux:9@sha256:d7be1c094cc5845ee815d4632fe377514ee6ebcf8efaed6892889657e5ddaaa6 \
bash -lc 'dnf install -y -q ca-certificates tar gzip xz findutils which sudo >/dev/null && bash /tmp/install-cli.sh --prefix /tmp/openclaw-cli --version latest --no-onboard && /tmp/openclaw-cli/bin/openclaw --version'
bun_global_install_smoke:
needs: [preflight, root_dockerfile_image]
if: needs.preflight.outputs.run_full_install_smoke == 'true' && needs.preflight.outputs.run_bun_global_install_smoke == 'true'
@@ -503,7 +512,7 @@ jobs:
- name: Pull root Dockerfile smoke image
env:
IMAGE_REF: ${{ needs.root_dockerfile_image.outputs.image_ref }}
run: timeout 600s docker pull "$IMAGE_REF"
run: timeout --kill-after=30s 600s docker pull "$IMAGE_REF"
- name: Setup Node environment for Bun smoke
uses: ./.github/actions/setup-node-env

View File

@@ -48,6 +48,7 @@ env:
OPENCLAW_BUILD_PRIVATE_QA: "1"
OPENCLAW_ENABLE_PRIVATE_QA_CLI: "1"
CRABBOX_REF: main
CRABBOX_CAPACITY_REGIONS: eu-west-1,eu-west-2,eu-central-1,us-east-1,us-west-2
MANTIS_OUTPUT_DIR: .artifacts/qa-e2e/mantis/telegram-desktop-proof
jobs:
@@ -422,7 +423,7 @@ jobs:
{
printf '%s\n' 'Defaults env_keep += "CODEX_HOME CODEX_INTERNAL_ORIGINATOR_OVERRIDE"'
printf '%s\n' 'Defaults env_keep += "BASELINE_REF BASELINE_SHA CANDIDATE_REF CANDIDATE_SHA"'
printf '%s\n' 'Defaults env_keep += "CRABBOX_ACCESS_CLIENT_ID CRABBOX_ACCESS_CLIENT_SECRET CRABBOX_COORDINATOR CRABBOX_COORDINATOR_TOKEN CRABBOX_LEASE_ID CRABBOX_PROVIDER"'
printf '%s\n' 'Defaults env_keep += "CRABBOX_ACCESS_CLIENT_ID CRABBOX_ACCESS_CLIENT_SECRET CRABBOX_COORDINATOR CRABBOX_COORDINATOR_TOKEN CRABBOX_LEASE_ID CRABBOX_PROVIDER CRABBOX_CAPACITY_REGIONS"'
printf '%s\n' 'Defaults env_keep += "GH_TOKEN MANTIS_CANDIDATE_TRUST MANTIS_INSTRUCTIONS MANTIS_OUTPUT_DIR MANTIS_PR_NUMBER"'
printf '%s\n' 'Defaults env_keep += "OPENCLAW_BUILD_PRIVATE_QA OPENCLAW_ENABLE_PRIVATE_QA_CLI OPENCLAW_QA_CONVEX_SECRET_CI OPENCLAW_QA_CONVEX_SITE_URL OPENCLAW_QA_CREDENTIAL_OWNER_ID OPENCLAW_QA_MANTIS_CRABBOX_COORDINATOR OPENCLAW_QA_MANTIS_CRABBOX_COORDINATOR_TOKEN"'
printf '%s\n' 'Defaults env_keep += "OPENCLAW_TELEGRAM_USER_CRABBOX_BIN OPENCLAW_TELEGRAM_USER_CRABBOX_PROVIDER OPENCLAW_TELEGRAM_USER_DRIVER_SCRIPT OPENCLAW_TELEGRAM_USER_PROOF_CMD"'
@@ -451,6 +452,7 @@ jobs:
CRABBOX_ACCESS_CLIENT_SECRET: ${{ secrets.CRABBOX_ACCESS_CLIENT_SECRET }}
CRABBOX_COORDINATOR: ${{ secrets.CRABBOX_COORDINATOR || secrets.OPENCLAW_QA_MANTIS_CRABBOX_COORDINATOR }}
CRABBOX_COORDINATOR_TOKEN: ${{ secrets.CRABBOX_COORDINATOR_TOKEN || secrets.OPENCLAW_QA_MANTIS_CRABBOX_COORDINATOR_TOKEN }}
CRABBOX_CAPACITY_REGIONS: ${{ env.CRABBOX_CAPACITY_REGIONS }}
CRABBOX_LEASE_ID: ${{ needs.resolve_request.outputs.lease_id }}
CRABBOX_PROVIDER: ${{ needs.resolve_request.outputs.crabbox_provider }}
GH_TOKEN: ${{ github.token }}
@@ -492,8 +494,11 @@ jobs:
exit 0
fi
status=0
mapfile -d '' session_files < <(sudo find .artifacts/qa-e2e -path '*/telegram-user-crabbox/*/session.json' -type f -print0)
mapfile -d '' session_files < <(sudo find .artifacts/qa-e2e -name session.json -type f -print0)
for session_file in "${session_files[@]}"; do
if ! sudo -u codex node -e 'const fs = require("fs"); const session = JSON.parse(fs.readFileSync(process.argv[1], "utf8")); process.exit(session.command === "telegram-user-crabbox-session" ? 0 : 1);' "$session_file"; then
continue
fi
lease_file="${session_file%/session.json}/.session/lease.json"
if [[ ! -f "$lease_file" ]]; then
continue
@@ -508,8 +513,11 @@ jobs:
status=1
fi
done
mapfile -d '' lease_files < <(sudo find .artifacts/qa-e2e -path '*/telegram-user-crabbox/*/.session/lease.json' -type f -print0)
mapfile -d '' lease_files < <(sudo find .artifacts/qa-e2e -path '*/.session/lease.json' -type f -print0)
for lease_file in "${lease_files[@]}"; do
if ! sudo -u codex node -e 'const fs = require("fs"); const lease = JSON.parse(fs.readFileSync(process.argv[1], "utf8")); process.exit(lease.kind === "telegram-user" ? 0 : 1);' "$lease_file"; then
continue
fi
if ! sudo -u codex env \
OPENCLAW_QA_CONVEX_SECRET_CI="$OPENCLAW_QA_CONVEX_SECRET_CI" \
OPENCLAW_QA_CONVEX_SITE_URL="$OPENCLAW_QA_CONVEX_SITE_URL" \

View File

@@ -553,6 +553,15 @@ jobs:
use-actions-cache: "false"
- name: Download candidate artifact
id: download_candidate
continue-on-error: true
uses: actions/download-artifact@v8
with:
name: openclaw-cross-os-release-checks-candidate-${{ github.run_id }}
path: ${{ runner.temp }}/openclaw-cross-os-release-checks/candidate
- name: Retry candidate artifact download
if: ${{ steps.download_candidate.outcome == 'failure' }}
uses: actions/download-artifact@v8
with:
name: openclaw-cross-os-release-checks-candidate-${{ github.run_id }}
@@ -560,11 +569,38 @@ jobs:
- name: Download baseline artifact
if: ${{ matrix.suite == 'packaged-upgrade' }}
id: download_baseline
continue-on-error: true
uses: actions/download-artifact@v8
with:
name: openclaw-cross-os-release-checks-baseline-${{ github.run_id }}
path: ${{ runner.temp }}/openclaw-cross-os-release-checks/baseline
- name: Retry baseline artifact download
if: ${{ matrix.suite == 'packaged-upgrade' && steps.download_baseline.outcome == 'failure' }}
uses: actions/download-artifact@v8
with:
name: openclaw-cross-os-release-checks-baseline-${{ github.run_id }}
path: ${{ runner.temp }}/openclaw-cross-os-release-checks/baseline
- name: Verify release-check inputs
shell: bash
env:
CANDIDATE_TGZ: ${{ runner.temp }}/openclaw-cross-os-release-checks/candidate/${{ needs.prepare.outputs.candidate_file_name }}
BASELINE_TGZ: ${{ runner.temp }}/openclaw-cross-os-release-checks/baseline/${{ needs.prepare.outputs.baseline_file_name }}
OUTPUT_DIR: ${{ runner.temp }}/openclaw-cross-os-release-checks/${{ matrix.artifact_name }}-${{ matrix.suite }}
SUITE: ${{ matrix.suite }}
run: |
mkdir -p "${OUTPUT_DIR}"
if [[ ! -f "${CANDIDATE_TGZ}" ]]; then
echo "::error::candidate artifact missing: ${CANDIDATE_TGZ}"
exit 1
fi
if [[ "${SUITE}" == "packaged-upgrade" ]] && [[ ! -f "${BASELINE_TGZ}" ]]; then
echo "::error::baseline artifact missing: ${BASELINE_TGZ}"
exit 1
fi
- name: Run cross-OS release checks
shell: bash
env:
@@ -615,7 +651,8 @@ jobs:
if [[ -f "${SUMMARY_PATH}" ]]; then
cat "${SUMMARY_PATH}" >> "$GITHUB_STEP_SUMMARY"
else
echo "No summary generated." >> "$GITHUB_STEP_SUMMARY"
mkdir -p "$(dirname "${SUMMARY_PATH}")"
echo "No summary generated." | tee "${SUMMARY_PATH}" >> "$GITHUB_STEP_SUMMARY"
fi
- name: Upload release-check artifacts

View File

@@ -102,6 +102,11 @@ on:
- beta
- stable
- full
use_github_hosted_runners:
description: Use GitHub-hosted runners instead of Blacksmith runners
required: false
default: false
type: boolean
advisory:
description: Treat failures as advisory for the caller
required: false
@@ -208,6 +213,11 @@ on:
required: false
default: stable
type: string
use_github_hosted_runners:
description: Use GitHub-hosted runners instead of Blacksmith runners
required: false
default: true
type: boolean
secrets:
OPENAI_API_KEY:
required: false
@@ -474,7 +484,7 @@ jobs:
needs: validate_selected_ref
if: inputs.include_live_suites && !inputs.live_models_only && (inputs.live_suite_filter == '' || inputs.live_suite_filter == 'live-cache')
continue-on-error: ${{ inputs.advisory }}
runs-on: ${{ github.event_name == 'workflow_call' && 'ubuntu-24.04' || 'blacksmith-8vcpu-ubuntu-2404' }}
runs-on: ${{ inputs.use_github_hosted_runners && 'ubuntu-24.04' || 'blacksmith-8vcpu-ubuntu-2404' }}
timeout-minutes: 20
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
@@ -511,7 +521,7 @@ jobs:
set -euo pipefail
for attempt in 1 2; do
echo "live-cache attempt ${attempt}/2"
if timeout --foreground --kill-after=30s 8m pnpm test:live:cache; then
if timeout --kill-after=30s 8m pnpm test:live:cache; then
exit 0
fi
if [[ "$attempt" == "2" ]]; then
@@ -524,7 +534,7 @@ jobs:
needs: validate_selected_ref
if: inputs.include_repo_e2e && inputs.live_suite_filter == ''
continue-on-error: ${{ inputs.advisory }}
runs-on: ${{ github.event_name == 'workflow_call' && 'ubuntu-24.04' || 'blacksmith-8vcpu-ubuntu-2404' }}
runs-on: ${{ inputs.use_github_hosted_runners && 'ubuntu-24.04' || 'blacksmith-8vcpu-ubuntu-2404' }}
timeout-minutes: ${{ inputs.release_test_profile == 'full' && 90 || 60 }}
env:
OPENCLAW_VITEST_MAX_WORKERS: "2"
@@ -556,7 +566,7 @@ jobs:
needs: validate_selected_ref
if: inputs.include_repo_e2e && (inputs.live_suite_filter == '' || inputs.live_suite_filter == 'openshell-e2e')
continue-on-error: ${{ inputs.advisory }}
runs-on: ${{ github.event_name == 'workflow_call' && 'ubuntu-24.04' || 'blacksmith-8vcpu-ubuntu-2404' }}
runs-on: ${{ inputs.use_github_hosted_runners && 'ubuntu-24.04' || 'blacksmith-8vcpu-ubuntu-2404' }}
timeout-minutes: ${{ matrix.timeout_minutes }}
strategy:
fail-fast: false
@@ -630,7 +640,7 @@ jobs:
if: inputs.include_release_path_suites && inputs.docker_lanes == ''
name: Docker E2E (${{ matrix.label }})
continue-on-error: ${{ inputs.advisory }}
runs-on: ${{ github.event_name == 'workflow_call' && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }}
runs-on: ${{ inputs.use_github_hosted_runners && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }}
timeout-minutes: ${{ matrix.timeout_minutes }}
strategy:
fail-fast: false
@@ -921,7 +931,7 @@ jobs:
needs: validate_selected_ref
if: inputs.docker_lanes != ''
continue-on-error: ${{ inputs.advisory }}
runs-on: ${{ github.event_name == 'workflow_call' && 'ubuntu-24.04' || 'blacksmith-4vcpu-ubuntu-2404' }}
runs-on: ${{ inputs.use_github_hosted_runners && 'ubuntu-24.04' || 'blacksmith-4vcpu-ubuntu-2404' }}
timeout-minutes: 5
outputs:
groups_json: ${{ steps.groups.outputs.groups_json }}
@@ -950,7 +960,7 @@ jobs:
if: inputs.docker_lanes != ''
name: Docker E2E targeted lanes (${{ matrix.group.label }})
continue-on-error: ${{ inputs.advisory }}
runs-on: ${{ github.event_name == 'workflow_call' && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }}
runs-on: ${{ inputs.use_github_hosted_runners && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }}
timeout-minutes: 60
strategy:
fail-fast: false
@@ -1182,7 +1192,7 @@ jobs:
if: inputs.include_openwebui && !inputs.include_release_path_suites && inputs.docker_lanes == ''
name: Docker E2E (openwebui)
continue-on-error: ${{ inputs.advisory }}
runs-on: ${{ github.event_name == 'workflow_call' && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }}
runs-on: ${{ inputs.use_github_hosted_runners && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }}
timeout-minutes: 60
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
@@ -1308,7 +1318,7 @@ jobs:
needs: validate_selected_ref
if: inputs.include_release_path_suites || inputs.include_openwebui || inputs.docker_lanes != ''
continue-on-error: ${{ inputs.advisory }}
runs-on: ${{ github.event_name == 'workflow_call' && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }}
runs-on: ${{ inputs.use_github_hosted_runners && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }}
timeout-minutes: ${{ inputs.release_test_profile == 'full' && 90 || 60 }}
permissions:
actions: read
@@ -1424,7 +1434,7 @@ jobs:
fi
echo "Validating Docker E2E package tarball: $target"
started_at="$(date +%s)"
timeout --foreground 5m node scripts/check-openclaw-package-tarball.mjs "$target"
timeout --kill-after=30s 5m node scripts/check-openclaw-package-tarball.mjs "$target"
finished_at="$(date +%s)"
echo "Docker E2E package tarball validation finished in $((finished_at - started_at))s."
digest="$(sha256sum "$target" | awk '{print $1}')"
@@ -1551,7 +1561,7 @@ jobs:
needs: validate_selected_ref
if: inputs.include_live_suites && (inputs.live_suite_filter == '' || startsWith(inputs.live_suite_filter, 'live-') || startsWith(inputs.live_suite_filter, 'docker-live-models'))
continue-on-error: ${{ inputs.advisory }}
runs-on: ${{ github.event_name == 'workflow_call' && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }}
runs-on: ${{ inputs.use_github_hosted_runners && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }}
timeout-minutes: 60
permissions:
contents: read
@@ -1624,7 +1634,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')
continue-on-error: ${{ inputs.advisory }}
runs-on: ${{ github.event_name == 'workflow_call' && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }}
runs-on: ${{ inputs.use_github_hosted_runners && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }}
timeout-minutes: 45
strategy:
fail-fast: false
@@ -1768,14 +1778,14 @@ jobs:
- name: Run Docker live model sweep
if: contains(matrix.profiles, inputs.release_test_profile)
run: OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 35m bash .release-harness/scripts/test-live-models-docker.sh
run: OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --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')
continue-on-error: ${{ inputs.advisory }}
runs-on: ${{ github.event_name == 'workflow_call' && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }}
runs-on: ${{ inputs.use_github_hosted_runners && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }}
timeout-minutes: 45
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
@@ -1943,13 +1953,13 @@ jobs:
done
- name: Run Docker live model sweep
run: OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 35m bash .release-harness/scripts/test-live-models-docker.sh
run: OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --kill-after=30s 35m bash .release-harness/scripts/test-live-models-docker.sh
validate_live_provider_suites:
needs: validate_selected_ref
if: inputs.include_live_suites && !inputs.live_models_only && (inputs.live_suite_filter == '' || (startsWith(inputs.live_suite_filter, 'native-live-') && !startsWith(inputs.live_suite_filter, 'native-live-extensions-media') && inputs.live_suite_filter != 'native-live-extensions-a-k'))
continue-on-error: ${{ inputs.advisory }}
runs-on: ${{ github.event_name == 'workflow_call' && 'ubuntu-24.04' || 'blacksmith-8vcpu-ubuntu-2404' }}
runs-on: ${{ inputs.use_github_hosted_runners && 'ubuntu-24.04' || 'blacksmith-8vcpu-ubuntu-2404' }}
timeout-minutes: ${{ matrix.timeout_minutes }}
strategy:
fail-fast: false
@@ -2251,6 +2261,7 @@ jobs:
env:
OPENCLAW_LIVE_COMMAND: ${{ matrix.command }}
OPENCLAW_LIVE_SUITE_ADVISORY: ${{ matrix.advisory }}
shell: bash
run: |
set +e
bash .release-harness/scripts/ci-live-command-retry.sh
@@ -2270,7 +2281,7 @@ jobs:
needs: [validate_selected_ref, prepare_live_test_image]
if: inputs.include_live_suites && !inputs.live_models_only && (inputs.live_suite_filter == '' || startsWith(inputs.live_suite_filter, 'live-'))
continue-on-error: ${{ inputs.advisory }}
runs-on: ${{ github.event_name == 'workflow_call' && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }}
runs-on: ${{ inputs.use_github_hosted_runners && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }}
timeout-minutes: ${{ matrix.timeout_minutes }}
strategy:
fail-fast: false
@@ -2278,32 +2289,32 @@ jobs:
include:
- suite_id: live-gateway-docker
label: Docker live gateway OpenAI
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=openai OPENCLAW_LIVE_GATEWAY_MODELS=openai/gpt-5.5 OPENCLAW_LIVE_GATEWAY_MAX_MODELS=1 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=90000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=300000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 35m bash .release-harness/scripts/test-live-gateway-models-docker.sh
command: OPENCLAW_LIVE_GATEWAY_THINKING=low OPENCLAW_LIVE_GATEWAY_PROVIDERS=openai OPENCLAW_LIVE_GATEWAY_MODELS=openai/gpt-5.5 OPENCLAW_LIVE_GATEWAY_MAX_MODELS=1 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=90000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=600000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --kill-after=30s 35m bash .release-harness/scripts/test-live-gateway-models-docker.sh
timeout_minutes: 40
profile_env_only: false
profiles: beta 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=90000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=180000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 35m bash .release-harness/scripts/test-live-gateway-models-docker.sh
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=anthropic OPENCLAW_LIVE_GATEWAY_MODELS=anthropic/claude-sonnet-4-6 OPENCLAW_LIVE_GATEWAY_MAX_MODELS=1 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=90000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=180000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --kill-after=30s 35m bash .release-harness/scripts/test-live-gateway-models-docker.sh
timeout_minutes: 40
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=90000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=180000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 35m bash .release-harness/scripts/test-live-gateway-models-docker.sh
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=90000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=180000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --kill-after=30s 35m bash .release-harness/scripts/test-live-gateway-models-docker.sh
timeout_minutes: 40
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=1 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=90000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=180000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 35m bash .release-harness/scripts/test-live-gateway-models-docker.sh
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=minimax,minimax-portal OPENCLAW_LIVE_GATEWAY_MAX_MODELS=1 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=90000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=180000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --kill-after=30s 35m bash .release-harness/scripts/test-live-gateway-models-docker.sh
timeout_minutes: 40
profile_env_only: false
profiles: stable full
- suite_id: live-gateway-advisory-docker-deepseek-fireworks
suite_group: live-gateway-advisory-docker
label: Docker live gateway advisory DeepSeek/Fireworks
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=deepseek,fireworks OPENCLAW_LIVE_GATEWAY_MAX_MODELS=2 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=90000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=180000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 35m bash .release-harness/scripts/test-live-gateway-models-docker.sh
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=deepseek,fireworks OPENCLAW_LIVE_GATEWAY_MAX_MODELS=2 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=90000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=180000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --kill-after=30s 35m bash .release-harness/scripts/test-live-gateway-models-docker.sh
timeout_minutes: 40
profile_env_only: false
advisory: true
@@ -2311,7 +2322,7 @@ jobs:
- suite_id: live-gateway-advisory-docker-opencode-openrouter
suite_group: live-gateway-advisory-docker
label: Docker live gateway advisory OpenCode/OpenRouter
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=opencode-go,openrouter OPENCLAW_LIVE_GATEWAY_MAX_MODELS=2 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=90000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=180000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 35m bash .release-harness/scripts/test-live-gateway-models-docker.sh
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=opencode-go,openrouter OPENCLAW_LIVE_GATEWAY_MAX_MODELS=2 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=90000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=180000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --kill-after=30s 35m bash .release-harness/scripts/test-live-gateway-models-docker.sh
timeout_minutes: 40
profile_env_only: false
advisory: true
@@ -2319,32 +2330,32 @@ jobs:
- suite_id: live-gateway-advisory-docker-xai-zai
suite_group: live-gateway-advisory-docker
label: Docker live gateway advisory xAI/Z.ai
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=xai,zai OPENCLAW_LIVE_GATEWAY_MAX_MODELS=2 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=90000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=180000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 35m bash .release-harness/scripts/test-live-gateway-models-docker.sh
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=xai,zai OPENCLAW_LIVE_GATEWAY_MAX_MODELS=2 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=90000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=180000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --kill-after=30s 35m bash .release-harness/scripts/test-live-gateway-models-docker.sh
timeout_minutes: 40
profile_env_only: false
advisory: true
profiles: full
- suite_id: live-cli-backend-docker
label: Docker live CLI backend
command: OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 45m bash .release-harness/scripts/test-live-cli-backend-docker.sh
command: OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --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" timeout --foreground --kill-after=30s 45m bash .release-harness/scripts/test-live-acp-bind-docker.sh
command: OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --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" timeout --foreground --kill-after=30s 35m bash .release-harness/scripts/test-live-codex-harness-docker.sh
command: OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --kill-after=30s 35m bash .release-harness/scripts/test-live-codex-harness-docker.sh
timeout_minutes: 40
profile_env_only: false
profiles: stable full
- suite_id: live-subagent-announce-docker
label: Docker live subagent announce
command: OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 20m bash .release-harness/scripts/test-live-subagent-announce-docker.sh
command: OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --kill-after=30s 20m bash .release-harness/scripts/test-live-subagent-announce-docker.sh
timeout_minutes: 25
profile_env_only: false
profiles: stable full
@@ -2469,6 +2480,7 @@ jobs:
env:
OPENCLAW_LIVE_COMMAND: ${{ matrix.command }}
OPENCLAW_LIVE_SUITE_ADVISORY: ${{ matrix.advisory }}
shell: bash
run: |
set +e
bash .release-harness/scripts/ci-live-command-retry.sh
@@ -2488,7 +2500,7 @@ jobs:
needs: validate_selected_ref
if: inputs.include_live_suites && !inputs.live_models_only && (inputs.live_suite_filter == '' || startsWith(inputs.live_suite_filter, 'native-live-extensions-media') || inputs.live_suite_filter == 'native-live-extensions-a-k')
continue-on-error: ${{ inputs.advisory }}
runs-on: ${{ github.event_name == 'workflow_call' && 'ubuntu-24.04' || 'blacksmith-8vcpu-ubuntu-2404' }}
runs-on: ${{ inputs.use_github_hosted_runners && 'ubuntu-24.04' || 'blacksmith-8vcpu-ubuntu-2404' }}
container:
image: ghcr.io/openclaw/openclaw-live-media-runner:ubuntu-24.04
credentials:
@@ -2656,6 +2668,7 @@ jobs:
if: contains(matrix.profiles, inputs.release_test_profile) && (inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id || (inputs.live_suite_filter == 'native-live-extensions-media-video' && startsWith(matrix.suite_id, 'native-live-extensions-media-video-')))
env:
OPENCLAW_LIVE_SUITE_ADVISORY: ${{ matrix.advisory }}
shell: bash
run: |
set +e
${{ matrix.command }}

View File

@@ -307,7 +307,36 @@ jobs:
exit 1
fi
report_md="${report_json%.json}.md"
effective_status="$status"
if [[ "$FAIL_ON_REGRESSION" == "true" && "$status" != "0" ]]; then
if REPORT_JSON="$report_json" node <<'NODE'
const fs = require("node:fs");
const report = JSON.parse(fs.readFileSync(process.env.REPORT_JSON, "utf8"));
const statuses = report.summary?.statuses ?? {};
const nonPassStatuses = Object.entries(statuses)
.filter(([status, count]) => status !== "PASS" && Number(count) > 0);
const baselineRegressionCount =
Number(report.baseline?.comparison?.regressionCount ?? report.gate?.baseline?.regressionCount ?? 0);
const gate = report.gate;
const toleratedPartial =
gate?.verdict === "PARTIAL" &&
Number(gate.blockingCount ?? 0) === 0 &&
baselineRegressionCount === 0 &&
nonPassStatuses.length === 0;
if (!toleratedPartial) {
process.exit(1);
}
NODE
then
effective_status=0
{
echo "Kova returned a partial release-gate verdict for filtered performance coverage, but all selected scenarios passed and no baseline regression was reported."
echo
} >> "$GITHUB_STEP_SUMMARY"
fi
fi
echo "status=$status" >> "$GITHUB_OUTPUT"
echo "effective_status=$effective_status" >> "$GITHUB_OUTPUT"
echo "report_json=$report_json" >> "$GITHUB_OUTPUT"
echo "report_md=$report_md" >> "$GITHUB_OUTPUT"
@@ -344,8 +373,43 @@ jobs:
EOF
cat "$summary_path" >> "$GITHUB_STEP_SUMMARY"
if [[ "$FAIL_ON_REGRESSION" == "true" && "$status" != "0" ]]; then
exit "$status"
if [[ "$FAIL_ON_REGRESSION" == "true" && "$effective_status" != "0" ]]; then
exit "$effective_status"
fi
- name: Fetch previous source performance baseline
if: ${{ steps.lane.outputs.run == 'true' && matrix.lane == 'mock-provider' && steps.clawgrit.outputs.present == 'true' }}
env:
CLAWGRIT_REPORTS_TOKEN: ${{ secrets.CLAWGRIT_REPORTS_TOKEN }}
shell: bash
run: |
set -euo pipefail
reports_root=".artifacts/clawgrit-baseline"
mkdir -p "$reports_root"
git -C "$reports_root" init -b main
git -C "$reports_root" remote add origin "https://x-access-token:${CLAWGRIT_REPORTS_TOKEN}@github.com/openclaw/clawgrit-reports.git"
if ! git -C "$reports_root" fetch --depth=1 origin main; then
echo "No previous source performance baseline could be fetched." >> "$GITHUB_STEP_SUMMARY"
exit 0
fi
git -C "$reports_root" checkout -B main FETCH_HEAD
ref_slug="$(printf '%s' "${TESTED_REF}" | tr -c 'A-Za-z0-9._-' '-')"
pointer="${reports_root}/openclaw-performance/${ref_slug}/latest-mock-provider.json"
if [[ ! -f "$pointer" ]]; then
echo "No previous source performance baseline exists for ${TESTED_REF}." >> "$GITHUB_STEP_SUMMARY"
exit 0
fi
if ! latest_path="$(node -e "const fs=require('node:fs'); const data=JSON.parse(fs.readFileSync(process.argv[1],'utf8')); const value=String(data.path || ''); if (!/^openclaw-performance\\/[A-Za-z0-9._-]+\\/[0-9]+-[0-9]+\\/mock-provider$/u.test(value)) process.exit(1); process.stdout.write(value);" "$pointer")"; then
echo "Previous source performance baseline pointer is invalid." >> "$GITHUB_STEP_SUMMARY"
exit 0
fi
baseline_source="${reports_root}/${latest_path}/source"
if [[ -d "$baseline_source" ]]; then
baseline_source="$(realpath "$baseline_source")"
echo "SOURCE_PERF_BASELINE_DIR=$baseline_source" >> "$GITHUB_ENV"
echo "Using source performance baseline: ${latest_path}/source" >> "$GITHUB_STEP_SUMMARY"
else
echo "Previous source performance baseline has no source directory." >> "$GITHUB_STEP_SUMMARY"
fi
- name: Run OpenClaw source performance probes
@@ -359,7 +423,7 @@ jobs:
fi
mkdir -p "$SOURCE_PERF_DIR/mock-hello"
if ! node -e "const fs=require('node:fs'); const scripts=require('./package.json').scripts||{}; process.exit(scripts['test:gateway:cpu-scenarios'] && scripts.openclaw && fs.existsSync('scripts/bench-cli-startup.ts') ? 0 : 1)"; then
if ! node -e "const fs=require('node:fs'); const scripts=require('./package.json').scripts||{}; process.exit(scripts['test:gateway:cpu-scenarios'] && scripts['test:extensions:memory'] && scripts.openclaw && fs.existsSync('scripts/bench-cli-startup.ts') && fs.existsSync('scripts/profile-extension-memory.mjs') ? 0 : 1)"; then
cat > "$SOURCE_PERF_DIR/index.md" <<EOF
# OpenClaw Source Performance
@@ -371,7 +435,7 @@ jobs:
- Tested ref: ${TESTED_REF}
- Tested SHA: ${TESTED_SHA}
- Required scripts: test:gateway:cpu-scenarios, openclaw, scripts/bench-cli-startup.ts
- Required scripts: test:gateway:cpu-scenarios, test:extensions:memory, openclaw, scripts/bench-cli-startup.ts, scripts/profile-extension-memory.mjs
EOF
cat "$SOURCE_PERF_DIR/index.md" >> "$GITHUB_STEP_SUMMARY"
exit 0
@@ -391,6 +455,9 @@ jobs:
--startup-case fiftyPlugins \
--startup-case fiftyStartupLazyPlugins
pnpm test:extensions:memory \
-- --json "$SOURCE_PERF_DIR/extension-memory.json"
for run_index in $(seq 1 "$source_runs"); do
run_dir="$SOURCE_PERF_DIR/mock-hello/run-$(printf '%03d' "$run_index")"
pnpm openclaw qa suite \
@@ -460,9 +527,13 @@ jobs:
cleanup_gateway
trap - EXIT
node "$PERFORMANCE_HELPER_DIR/scripts/openclaw-performance-source-summary.mjs" \
summary_args=(node "$PERFORMANCE_HELPER_DIR/scripts/openclaw-performance-source-summary.mjs" \
--source-dir "$SOURCE_PERF_DIR" \
--output "$SOURCE_PERF_DIR/index.md"
--output "$SOURCE_PERF_DIR/index.md")
if [[ -n "${SOURCE_PERF_BASELINE_DIR:-}" && -d "$SOURCE_PERF_BASELINE_DIR" ]]; then
summary_args+=(--baseline-source-dir "$SOURCE_PERF_BASELINE_DIR")
fi
"${summary_args[@]}"
cat "$SOURCE_PERF_DIR/index.md" >> "$GITHUB_STEP_SUMMARY"

View File

@@ -344,7 +344,7 @@ jobs:
OPENCLAW_EXTENSION_BATCH_PARALLEL: 2
OPENCLAW_VITEST_MAX_WORKERS: 1
OPENCLAW_EXTENSION_BATCH: ${{ matrix.extensions_csv }}
run: pnpm test:extensions:batch "$OPENCLAW_EXTENSION_BATCH"
run: pnpm test:extensions:batch "$OPENCLAW_EXTENSION_BATCH" -- --exclude extensions/codex/src/app-server/run-attempt.test.ts
plugin-prerelease-inspector:
permissions:

View File

@@ -42,7 +42,7 @@ jobs:
run: |
set -euo pipefail
docker build -t openclaw-sandbox-smoke-base:bookworm-slim - <<'EOF'
timeout --kill-after=30s 5m docker build -t openclaw-sandbox-smoke-base:bookworm-slim - <<'EOF'
FROM debian:bookworm-slim
RUN useradd --create-home --shell /bin/bash sandbox
USER sandbox
@@ -63,5 +63,5 @@ jobs:
FINAL_USER=sandbox \
scripts/sandbox-common-setup.sh
u="$(docker run --rm openclaw-sandbox-common-smoke:bookworm-slim sh -lc 'id -un')"
u="$(timeout --kill-after=30s 2m docker run --rm openclaw-sandbox-common-smoke:bookworm-slim sh -lc 'id -un')"
test "$u" = "sandbox"

View File

@@ -38,4 +38,4 @@ jobs:
install-bun: "false"
- name: Run TUI PTY tests
run: timeout 120s node scripts/run-vitest.mjs run --config test/vitest/vitest.tui-pty.config.ts
run: timeout --kill-after=30s 120s node scripts/run-vitest.mjs run --config test/vitest/vitest.tui-pty.config.ts

View File

@@ -75,14 +75,14 @@ jobs:
- name: install.sh in Docker
run: |
docker run --rm \
timeout --kill-after=30s 20m docker run --rm \
-v "$PWD/scripts/install.sh:/tmp/install.sh:ro" \
node:24-bookworm-slim \
bash -lc 'bash /tmp/install.sh --version latest && openclaw --version'
- name: install-cli.sh in Docker
run: |
docker run --rm \
timeout --kill-after=30s 20m docker run --rm \
-e OPENCLAW_NO_ONBOARD=1 \
-e OPENCLAW_NO_PROMPT=1 \
-v "$PWD/scripts/install-cli.sh:/tmp/install-cli.sh:ro" \

View File

@@ -26,7 +26,16 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v6
env:
CHECKOUT_REPO: ${{ github.repository }}
CHECKOUT_SHA: ${{ github.sha }}
run: |
set -euo pipefail
git init "$GITHUB_WORKSPACE"
git -C "$GITHUB_WORKSPACE" config gc.auto 0
git -C "$GITHUB_WORKSPACE" remote add origin "https://github.com/${CHECKOUT_REPO}.git"
git -C "$GITHUB_WORKSPACE" fetch --no-tags --depth=1 origin "+${CHECKOUT_SHA}:refs/remotes/origin/checkout"
git -C "$GITHUB_WORKSPACE" checkout --detach refs/remotes/origin/checkout
- name: Fail on tabs in workflow files
run: |
@@ -58,7 +67,16 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v6
env:
CHECKOUT_REPO: ${{ github.repository }}
CHECKOUT_SHA: ${{ github.sha }}
run: |
set -euo pipefail
git init "$GITHUB_WORKSPACE"
git -C "$GITHUB_WORKSPACE" config gc.auto 0
git -C "$GITHUB_WORKSPACE" remote add origin "https://github.com/${CHECKOUT_REPO}.git"
git -C "$GITHUB_WORKSPACE" fetch --no-tags --depth=1 origin "+${CHECKOUT_SHA}:refs/remotes/origin/checkout"
git -C "$GITHUB_WORKSPACE" checkout --detach refs/remotes/origin/checkout
- name: Install actionlint
shell: bash
@@ -90,7 +108,16 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v6
env:
CHECKOUT_REPO: ${{ github.repository }}
CHECKOUT_SHA: ${{ github.sha }}
run: |
set -euo pipefail
git init "$GITHUB_WORKSPACE"
git -C "$GITHUB_WORKSPACE" config gc.auto 0
git -C "$GITHUB_WORKSPACE" remote add origin "https://github.com/${CHECKOUT_REPO}.git"
git -C "$GITHUB_WORKSPACE" fetch --no-tags --depth=1 origin "+${CHECKOUT_SHA}:refs/remotes/origin/checkout"
git -C "$GITHUB_WORKSPACE" checkout --detach refs/remotes/origin/checkout
- name: Setup Node environment
uses: ./.github/actions/setup-node-env

View File

@@ -27,7 +27,7 @@ Skills own workflows; root owns hard policy and routing.
- For PRs that add, remove, or change config/default surfaces with possible compatibility, upgrade, provider/plugin, operator, setup, startup, or fallback impact, ClawSweeper review should emit a `reviewMetrics` entry when practical. The metric should name the count and direction of the changes, such as added, changed, or removed config/default surfaces, and explain why the metric matters before merge. When the metric indicates concrete merge risk, also surface the concern in `risks`, use `mergeRiskLabels` when the risk matches the label rubric, make `bestSolution` name the desired pre-merge state, and ensure `labelJustifications` explain the specific reason rather than restating the label.
- Review whole decision surfaces, not only the touched runtime, provider, channel, harness, plugin seam, or context path. Check sibling Codex/Pi-style runtimes, provider/model routing, channel delivery, gateway/protocol, plugin SDK, and context-management paths when relevant.
- One-sided fixes need sibling-surface proof, an explanation for why siblings are unaffected, or explicit follow-up work.
- User-facing `fix`, `feat`, and `perf` changes need `CHANGELOG.md` before landing; contributor PR authors are not blocked solely on maintainer-owned changelog work. Never request thanks for bot/forbidden handles: `@openclaw`, `@clawsweeper`, `@codex`, `@steipete`.
- Changelog findings: see Docs / Changelog.
- Public ClawSweeper comments prefer `https://docs.openclaw.ai/...` when a public docs page exists; structured evidence still cites repo files, lines, SHAs.
- Findings need current source, shipped/current behavior, tests/CI evidence, and dependency contract proof when dependency-backed behavior is involved. Validation is judged against touched and sibling surfaces plus this file's commands; real behavior proof matters for user-visible changes, with Telegram/Desktop proof for Telegram-visible behavior when feasible.
- Prefer findings for concrete behavior regressions, missing changed-surface proof, owner-boundary violations, security/API contract issues, or docs/config mismatches.
@@ -58,9 +58,17 @@ Skills own workflows; root owns hard policy and routing.
- Externalizing a bundled plugin: update package excludes, official catalogs, docs, tests, and prove core runtime paths resolve installed plugin roots before root-dep removal.
- Runtime reads canonical config only. No silent compat for old/malformed config keys. If a config change invalidates existing files, add a matching `openclaw doctor --fix` migration. Core/auth config repairs live in core doctor; plugin-owned config repairs live in that plugin's doctor contract (`legacyConfigRules` / `normalizeCompatibilityConfig`).
- Fix shape: default to clean bounded refactor, not smallest patch. Move ownership to right boundary; delete stale abstractions, duplicate policy, dead branches, wrappers, fallback stacks.
- Fix observed local failures with generic product rules; do not hardcode names, ids, log phrases, or user examples in prod code unless they are an explicit contract.
- Tests may use observed examples, but prod literals need a short contract reason.
- Compatibility is opt-in. "Shipped" means reachable from a release Git tag; main/GitHub/PR/unreleased code is not shipped.
- Refactor default: one canonical path. Delete the old path unless user explicitly wants compat or the shipped public contract is obvious and cited.
- Keep old behavior only for an explicit public API/config/plugin SDK/data contract, tagged upgrade path, security/migration boundary, dependency contract, or observed prod state.
- If unsure, ask before preserving compat. Do not keep aliases, shims, fallback stacks, stale names, or obsolete tests just in case.
- Tests alone do not make internals contracts. If compat stays, name the contract and migration/removal plan in code, test, or PR.
- Lean code is a goal. No internal shims, aliases, legacy names, broad fallbacks, or defensive branches just to reduce diff or handle unrealistic edge cases.
- Handle real production states, shipped upgrade paths, security boundaries, and dependency contracts. Public/hostile/observed malformed input gets care; hypothetical malformed input does not.
- Public plugin SDK/API is the compat exception. New API first, old path only via named compat/deprecation metadata, docs, warnings when useful, tests for old+new, planned removal.
- Handle real production states, tagged upgrade paths, security boundaries, and dependency contracts. Public/hostile/observed malformed input gets care; hypothetical malformed input does not.
- Deprecate shipped public contracts only.
- Plugin SDK exception: shipped external API gets new API first plus named compat/deprecation, small tests/docs if useful, removal plan.
- Migrate internal/bundled callers to modern API in the same change. Do not let internal compat become permanent architecture.
- Channels are implementation under `src/channels/**`; plugin authors get SDK seams. Providers own auth/catalog/runtime hooks; core owns generic loop.
- Hot paths should carry prepared facts forward: provider id, model ref, channel id, target, capability family, attachment class. Do not rediscover with broad plugin/provider/channel/capability loaders.
@@ -68,7 +76,8 @@ Skills own workflows; root owns hard policy and routing.
- Gateway/plugin metadata is process-stable: installs, manifests, catalogs, generated paths, bundled metadata. Changes require restart or explicit owner reload/install/doctor flow.
- Runtime hot paths: no freshness polling (`stat`/`realpath`/JSON reread/hash). Reuse current snapshots, install records, discovery, lookup tables, root scopes, resolved paths.
- Process-local metadata caches ok when lifecycle-owned and bounded/single-slot. Freshness exceptions need named owner + tests.
- Inline code comments: brief notes for tricky, bug-prone, or previously buggy logic.
- Inline comments: preserve reviewer context at the code site. Use for cross-path/state invariants, platform/dependency caps, deterministic ordering, compact encoded state, lifecycle ordering, ownership boundaries, session/id adoption, queue-depth symmetry, fallbacks, or intentional caller differences.
- Comment shape: 1-3 short lines; state why the branch/helper exists, what contract it protects, and the bad outcome if removed. Cite nearby constants/helpers when useful. No syntax narration, PR/user-specific lore, or obvious mechanics.
- Gateway protocol changes: additive first; incompatible needs versioning/docs/client follow-through.
- Protocol version bumps: explicit owner confirmation only; never automatic/generated.
- Config contract: exported types, schema/help, metadata, baselines, docs aligned. Retired public keys stay retired; compat in raw migration/doctor only.
@@ -116,7 +125,6 @@ Skills own workflows; root owns hard policy and routing.
- Do not leave associated issues open for hypothetical future repros. Close with rationale; ask for a new issue or reopen only if concrete new evidence appears. Close comment states: decision, why, supported alternative, and what evidence would change the decision.
- PR review answer: bug/behavior, URL(s), affected surface, provenance for regressions when traceable, best-fix judgment, evidence from code/tests/CI/current or shipped behavior.
- Issue/PR final answer: last line is the full GitHub URL.
- Changelog: PR landings/fixes need one unless pure test/internal. Do not mention missing changelog as a review finding; Codex handles it during fix/landing.
- PR verification: before merge, post exact local commands, CI/Testbox run IDs, before/after proof when used, and known proof gaps.
- Issue fixed on `main` with proof: comment proof + commit/PR, then close.
- After landing or requested close/sweep: search duplicates; comment proof + canonical commit/PR/release before closing.
@@ -124,8 +132,10 @@ Skills own workflows; root owns hard policy and routing.
- `ship` that fixes an issue: after push, comment proof + commit link, then close the issue.
- GH comments with backticks, `$`, or shell snippets: use heredoc/body file, not inline double-quoted `--body`.
- PR create: real body required. Include Summary + Verification; mention refs, behavior, and proof.
- PR create/refresh: keep PR branches takeover-ready. Use a branch maintainers can push to, or for fork PRs ensure `maintainer_can_modify` / GitHub's `Allow edits by maintainers` is enabled unless explicitly told otherwise or GitHub's Actions/secrets warning makes that unsafe.
- GitHub issue/PR create: read `$agent-transcript`; ask about sanitized transcript logs when available.
- Real behavior proof section is parsed. Use exact `field: value` labels: `Behavior addressed`, `Real environment tested`, `Exact steps or command run after this patch`, `Evidence after fix`, `Observed result after fix`, `What was not tested`.
- PR artifacts/screenshots: attach to PR/comment/external artifact store. Do not commit `.github/pr-assets`.
- PR artifacts/screenshots: attach to PR/comment/external artifact store. Never push screenshots, videos, proof images, or proof assets to OpenClaw or any product repo branch, including temp artifact branches. Use Crabbox artifact publishing plus the manifest URL. Do not commit `.github/pr-assets`.
- CI polling: exact SHA, relevant checks only, minimal fields. Skip routine noise (`Auto response`, `Labeler`, docs agents, performance/stale). Logs only after failure/completion or concrete need.
- Maintainers: may skip/ignore `Real behavior proof` when local tests or Crabbox verified behavior; record proof in PR verification.
- `/landpr`: use `~/.codex/prompts/landpr.md`; do not idle on `auto-response` or `check-docs`.
@@ -149,10 +159,13 @@ Skills own workflows; root owns hard policy and routing.
- Inline simple one-use objects/spreads when clearer. Extract only when it removes duplication or hard logic.
- Tests prove behavior/regressions, not every internal branch.
- For non-trivial refactors, check `git diff --numstat` before closeout. If LOC grew, trim or explain why.
- Prefer existing narrow helpers over repeated casts/guards. Add local helpers when 2+ nearby call sites share real boundary logic.
- Prefer ctor parameter properties for injected deps/config. Do not ban them for erasable-syntax purity.
- Prefer `satisfies` for registries/config maps; derive types from schemas when a runtime schema already exists.
- Table-drive repetitive tests when it reduces code and keeps failure names clear.
- Dynamic import: no static+dynamic import for same prod module. Use `*.runtime.ts` lazy boundary. After edits: `pnpm build`; check `[INEFFECTIVE_DYNAMIC_IMPORT]`.
- Cycles: keep `pnpm check:import-cycles` + architecture/madge green.
- Classes: no prototype mixins/mutations. Prefer inheritance/composition. Tests prefer per-instance stubs.
- Comments: brief, only non-obvious logic.
- Split files around ~700 LOC when clarity/testability improves.
- Naming: **OpenClaw** product/docs; `openclaw` CLI/package/path/config.
- English: American spelling.
@@ -174,9 +187,9 @@ Skills own workflows; root owns hard policy and routing.
- Use `$technical-documentation` for docs writing/review. Docs change with behavior/API.
- Codex harness upgrade (`extensions/codex/package.json` `@openai/codex`): refresh `docs/plugins/codex-harness.md` model snapshot from the new harness `model/list`.
- Docs final answers: include relevant full `https://docs.openclaw.ai/...` URL(s). If issue/PR work too, GitHub URL last.
- Changelog entries: active version `### Changes`/`### Fixes`; single-line bullets only.
- Contributor PR authors should not edit `CHANGELOG.md`; maintainer/AI adds entries during landing/merge.
- Contributor-facing changelog entries thank credited human `@author`. Never thank bots, `@openclaw`, `@clawsweeper`, or `@steipete`; if unknown, omit thanks.
- `CHANGELOG.md`: release-owned. Do not edit for normal PRs, direct `main` fixes, or `ship it`; only explicit release/changelog generation may rewrite it. Do not ask contributors/agents for changelog edits.
- User-facing `fix`/`feat`/`perf`: put release-note context in PR body, squash message, or direct commit: behavior, surface, issue/PR refs, credited human author/reporter.
- Release generation: derive `CHANGELOG.md` from merged PRs + all direct `main` commits. Entries: active `### Changes`/`### Fixes`, single-line, thank credited humans; never thank bots/forbidden handles: `@openclaw`, `@clawsweeper`, `@codex`, `@steipete`.
## Git
@@ -185,7 +198,7 @@ Skills own workflows; root owns hard policy and routing.
- No manual stash/autostash unless explicit. No branch/worktree changes unless requested.
- `main`: no merge commits; rebase on latest `origin/main` before push. After one green run plus clean rebase sanity, do not chase moving `main` with repeated full gates.
- User says `commit`: your changes only. `commit all`: all changes in grouped chunks. `push`: may `git pull --rebase` first.
- User says `ship it`: changelog if needed, commit intended changes, pull --rebase, push.
- User says `ship it`: commit intended changes, pull --rebase, push.
- Do not delete/rename unexpected files; ask if blocking, else ignore.
- Bulk PR close/reopen >5: ask with count/scope.

View File

@@ -6,6 +6,12 @@ Docs: https://docs.openclaw.ai
### Changes
- Voice: expose shared realtime turn-context tracking through the realtime voice SDK and reuse it for Discord speaker attribution and wake-name context recovery.
- Voice: reuse shared realtime output activity tracking in Google Meet command and node audio bridges, including recent-output checks for local barge-in detection.
- Voice: expose shared realtime output activity tracking through the realtime voice SDK and reuse it for Discord playback activity and barge-in decisions.
- Voice: expose shared realtime consult question matching, speakable-result extraction, and alias-aware forced-consult coordination through the realtime voice SDK, then reuse it in Gateway Talk, Voice Call, and Discord voice paths.
- Voice: share activation-name matching and consult-transcript screening through the realtime voice SDK so Discord, browser voice, and meeting surfaces can reuse one implementation.
- Cron: default `cron.maxConcurrentRuns` to 8 so scheduled automations and their isolated agent turns can make progress in parallel without explicit configuration.
- QA-Lab: add `qa coverage --match <query>` so focused proof selection can discover matching scenarios from existing metadata before running live or remote lanes.
- Control UI: add an ephemeral Activity tab for sanitized live tool activity summaries without persisting raw telemetry. Fixes #12831. Thanks @BunsDev.
- Build: include `ui:build` in the `full` and `ciArtifacts` profiles of `scripts/build-all.mjs` so `pnpm build` always rebuilds `dist/control-ui` after `tsdown` cleans `dist`, removing the second-command requirement and the missing-asset failure mode for source/runtime installs and CI artifact uploads. (#85206)
@@ -15,33 +21,96 @@ Docs: https://docs.openclaw.ai
### Fixes
- Telegram/network: treat `ENETDOWN` as a transient pre-connect network failure so Telegram sends, gateway unhandled-rejection handling, and cron network retries follow the same recovery path as sibling network outages. (#86762) Thanks @TurboTheTurtle.
- Agents/sessions: include visibility metadata on restricted `sessions_list` results so scoped counts are clearly reported without widening access or exposing hidden-session counts. (#86944) Thanks @ferminquant.
- Gateway/DNS: validate wide-area discovery domains before deriving zone paths or writing zone files, so invalid `discovery.wideArea.domain` and `dns setup --domain` values fail with a DNS-name diagnostic instead of falling through to unrelated configuration errors. Thanks @mmaps.
- Agents/BTW: route fallback side-question streams through the embedded stream resolver so Anthropic-compatible MiniMax requests use the same capped transport as normal chat. (#86312) Thanks @neeravmakwana.
- Telegram: treat `/command@TargetBot` bot-command entities as explicit mentions for the addressed bot so `requireMention` groups no longer drop targeted commands or captions. Fixes #84462. (#86553) Thanks @luoyanglang.
- CI: bound Docker/Bash E2E tarball npm installs with `OPENCLAW_E2E_NPM_INSTALL_TIMEOUT` so package, onboarding, plugin, and upgrade lanes fail instead of hanging on a stuck npm install.
- CI: keep `OPENCLAW_TESTBOX=1 pnpm check:changed` delegating to Blacksmith Testbox through Crabbox without forwarding local Testbox or worker env into the remote command.
- CI: send KILL after the TERM grace period for manual checkout fetch timeouts so stuck Testbox and workflow checkout retries cannot hang behind a wedged `git fetch`.
- iMessage: thread current channel/account inbound attachment roots into the image tool so iMessage-saved attachments under `~/Library/Messages/Attachments` (including the wildcard `/Users/*/Library/Messages/Attachments` root) are read through the existing inbound path policy instead of being rejected as `path-not-allowed`. Literal `localRoots` stays workspace-scoped. Fixes #30170. (#86569)
- QQ Bot: respect `OPENCLAW_HOME` for outbound media path resolution so `<qqmedia>` sends no longer silently fail when `HOME` and `OPENCLAW_HOME` differ (Docker / multi-user hosts). Persisted QQ Bot data (sessions, known users, refs) stays anchored on the OS home for upgrade compatibility. Fixes #83562. Thanks @sliverp.
- Update: report the primary malformed `openclaw.extensions` payload error without adding a duplicate missing-main diagnostic. (#86596) Thanks @ferminquant.
- Control UI: keep host-local Markdown file paths inert while preserving app-relative links. (#86620) Thanks @BryanTegomoh.
- Gateway: dampen repeated unauthenticated device-required probes per URL while preserving explicit-auth and paired recovery paths. (#86575) Thanks @ferminquant.
- IRC: store inbound channel routes with the canonical `channel:#name` target and join transient channel sends before writing. (#85906) Thanks @Kailigithub.
- Usage: surface unknown all-zero model pricing as missing cost entries instead of a confident `$0` total. (#85882) Thanks @MichaelZelbel.
- Agents/Codex: honor yolo app-server approval policy only for the full `never` plus `danger-full-access` case. (#85909) Thanks @earlvanze.
- Gateway/Gmail: clear Gmail watcher renewal intervals on re-entry so hot reloads do not leak lifecycle timers. (#82947) Thanks @SebTardif.
- Logging: exit cleanly on broken stdout/stderr pipes without masking existing failure exit codes. (#80059) Thanks @pavelzak.
- Gateway/security: escape transcript metadata field names while extracting oversized session line prefixes. (#85934) Thanks @SebTardif.
- Plugins/security: validate manifest model pattern regexes with the safe-regex compiler so unsafe patterns are ignored before matching. (#86046) Thanks @SebTardif.
- Discord: route gateway metadata REST lookups through the configured Discord proxy so proxied accounts do not fall back to direct `discord.com` connections before opening the WebSocket. Fixes #80227. Thanks @Clivilwalker.
- Agents/media: hydrate current-turn image attachments from filename-derived MIME types so active vision can see generated or forwarded images whose source omitted an image content type. (#84812) Thanks @marchpure.
- Agents/fs: point workspace-only scratch-path guidance at in-workspace temp directories while keeping host-root writes rejected by the tool guard. (#86501) Thanks @tianxiaochannel-oss88.
- Agents/media: keep async cron media completions scoped to their run session while preserving direct delivery for stale generated-media success and failure notifications. (#86529) Thanks @ai-hpc.
- Gateway: emit plugin `session_end`/`session_start` hooks when `agent.send` rotates or replaces a session id, keeping hook lifecycle state aligned with `sessions.changed` notifications. Fixes #83507. (#85875) Thanks @brokemac79.
- OpenShell/SSH: reject malformed generated exec commands before sandbox/session setup so unresolved workflow placeholders fail fast instead of reaching the remote shell. Fixes #72373. Thanks @brokemac79.
- Google: stop normalizing `gemini-3.1-flash-lite` to the retired preview endpoint and update Flash Lite alias guidance to the GA model id. Fixes #86151. (#86240) Thanks @SebTardif.
- Installer: make Alpine apk installs cover Git, verify the Node runtime floor, try `nodejs-current`, and report Alpine version guidance when repositories only provide older Node packages.
- Agents/status: prefer the active Claude CLI OAuth auth label over an unused Anthropic env API-key label for equivalent runtime aliases. Fixes #80184. (#86570) Thanks @brokemac79.
- Agents/media: send direct fallback for generated media still missing after an active requester wake fails. (#85489) Thanks @fuller-stack-dev.
- Agents: derive overflow compaction budgets from provider-reported and synthetic over-budget token counts so confirmed context overflows compact before retrying. (#70473) Thanks @fuller-stack-dev.
- Agents/Codex: recover Codex context-window prompt errors through overflow compaction and surface reset guidance when recovery is exhausted. (#85542) Thanks @fuller-stack-dev.
- Agents/Codex: allow Codex app-server runs to bootstrap from `CODEX_API_KEY` or `OPENAI_API_KEY` when no Codex auth profile is configured.
- Agents/Codex: keep selected Codex runtime routing on OpenAI-Codex while preserving direct OpenAI API-key compaction fallback. (#86408) Thanks @funmerlin and @VACInc.
- Agent transcript: include OpenClaw agent session logs when finding local transcript candidates.
- Crabbox: bootstrap raw AWS macOS shell commands wrapped in absolute `time` paths so RSS probes can run Node and pnpm on fresh macOS runners.
- Crabbox: bootstrap raw AWS macOS shell commands even when setup statements precede Node or pnpm usage.
- TUI/local: skip unnecessary secret resolution, gateway model catalog loading, bootstrap, and skill scans in explicit local-model runs so startup reaches the model request faster.
- Sessions/doctor: load large session stores without clone amplification during read-only doctor checks and reclaim stale `sessions.json.*.tmp` sidecars. Fixes #56827. Thanks @openperf.
- Tests: clean successful plugin gateway gauntlet isolated temp roots while keeping an explicit preservation switch for failed/debug runs.
- Plugins/perf: reuse derived plugin metadata snapshots for the lifetime of the process so reply-time skill setup no longer rescans plugin metadata on every turn.
- Discord/OpenAI voice: keep wake-name master consults using the current speaker context after ignored ambient transcripts and shorten the default capture silence grace.
- Doctor: skip redundant Gateway restart prompts when a recent supervisor restart leaves the Gateway healthy. Fixes #86518. (#86533) Thanks @liaoyl830.
- Cron: restore suspended cron lanes to the configured/default concurrency instead of falling back to one after quota or circuit-breaker auto-resume.
- Gateway: keep session-only Control UI tool-start mirrors flowing during diagnostic queue pressure instead of silently dropping non-terminal tool updates.
- Agents/memory: return optional not-found context for missing date-only daily memory reads instead of logging benign first-run `ENOENT` failures. Fixes #82928. Thanks @galiniliev.
- Discord: merge streamed text captions into following media block replies so captions and attachments send as one message. (#86487) Thanks @neeravmakwana.
- Gateway: avoid sending duplicate tool-event frames to Control UI connections that are subscribed by both run and session.
- Discord/OpenAI voice: accept broader edge-position fuzzy wake-name transcripts while keeping ambient speech gated.
- Discord/OpenAI voice: accept longer leading wake-name mistranscripts such as "Open Club" for OpenClaw.
- Agents/OpenAI-compatible: stop ModelStudio-compatible chat requests before sending system/tool-only payloads that have no usable user or assistant turn. (#86177) Thanks @TurboTheTurtle.
- Gateway/plugins: reuse plugin package realpath checks while building installed plugin indexes so startup avoids repeated filesystem resolution work.
- Kilo Gateway: send string `stop` sequences as arrays so Kilo accepts OpenAI-compatible chat completions. (#86461) Thanks @SebTardif.
- Discord/OpenAI voice: accept leading fuzzy wake-name transcripts such as "Monty" or "Moti" for a Molty agent while keeping ambient speech gated.
- Media understanding: convert HEIC and HEIF images to JPEG before image description providers run so iPhone photos work in direct and configured image-description flows. (#86037)
- Agents: release embedded-attempt session locks from outer teardown so post-prompt exceptions cannot wedge later requests behind `SessionWriteLockTimeoutError`. Fixes #86014. Thanks @openperf.
- Discord/OpenAI voice: rotate Realtime sessions at provider max duration without logging the expected session-expiry event as an error.
- Sessions: skip metadata-only entries during QMD-slugified session lookup so one incomplete row does not block transcript hit resolution. (#86327) Thanks @abnershang.
- Agents/media: derive bundled plugin local-media trust from plugin tool metadata instead of importing the full plugin registry on subscription paths. (#84409) Thanks @samzong.
- Image tool: keep config-backed custom-provider API keys usable for auto-discovered vision models, including deferred image-tool execution without env keys or auth profiles. (#85733)
- Memory/local embeddings: run local GGUF embeddings in an isolated worker sidecar and degrade to configured fallback or keyword search on worker failure so native embedding crashes do not take down the Gateway. (#85348) Thanks @osolmaz.
- Gateway: clear the runtime config snapshot before `SIGUSR1` in-process restarts so config changes survive the next gateway loop. (#86388) Thanks @XuZehan-iCenter.
- Models: show OAuth delegation markers as configured `models.json` auth while keeping runtime route usability checks strict. (#86378) Thanks @rohitjavvadi.
- Cron: seed active scheduled and manual cron task rows with a progress summary so status surfaces do not look blank while jobs run. (#86313) Thanks @ferminquant.
- Cron: preserve unsupported persisted cron payload rows during routine store writes while keeping those rows non-runnable. Fixes #84922. (#86415) Thanks @IWhatsskill.
- Updater: exclude prerelease git tags from stable channel resolution so source updates do not check out newer alpha/rc/preview/canary tags. (#86260) Thanks @stevenepalmer.
- Security/Audit: flag webhook `hooks.token` reuse of active Gateway password auth in `openclaw security audit` while keeping password-mode startup compatibility. (#84338) Thanks @coygeek.
- QQBot: derive the outbound reply watchdog from configured agent and provider timeouts so slow local model replies are not cut off at five minutes. Fixes #85267. (#85271) Thanks @SymbolStar.
- Agents/heartbeat: stop heartbeat turns after the first valid `heartbeat_respond` so repeated response loops do not burn tokens. (#86357) Thanks @udaymanish6.
- Tasks: keep retained lost tasks out of default status health counts, explain their cleanup window during maintenance, and prune lost task records after 24 hours instead of the general 7-day terminal retention.
- Memory-core: keep REM dreaming focused on live light-staged memories and mark staged entries as considered so old recall history no longer dominates fresh candidates. (#86302) Thanks @SebTardif.
- Memory: abort sync instead of downgrading an existing semantic vector index to FTS-only when the configured embedding provider is temporarily unavailable. (#85704) Thanks @yaaboo-gif.
- Telegram: propagate forum topic names through the account-scoped topic cache for native command context and topic create/edit actions. (#86299) Thanks @SebTardif.
- Slack: keep downloaded read-only files out of reply media so Slack file reads do not echo files back to the conversation. (#86318) Thanks @neeravmakwana.
- Cron: accept leading-plus relative durations such as `+5m` for one-shot `--at` schedules. (#86341) Thanks @mushuiyu886.
- Agents/media: preserve async-started media tool metadata so background generation starts no longer surface generic incomplete-turn warnings while replay stays unsafe. (#85933) Thanks @fuller-stack-dev.
- Docker E2E: dedupe scheduler lane resources so npm/service package lanes are not over-counted and serialized unnecessarily.
- QA/diagnostics: add a collector-backed OpenTelemetry smoke lane, make the OTLP payload leak check scenario-aware, and keep source QA builds from failing on optional dependency imports resolved through pnpm's temp module path.
- Crabbox: bootstrap Git metadata for sparse remote changed gates so raw synced workspaces can run `pnpm check:changed` from the intended diff.
- xAI/LM Studio: avoid buffering ordinary bracketed or `final` prose until stream completion while watching for plain-text tool-call fallbacks.
- Doctor: warn and continue when the cron job store exists but cannot be read so later health checks still run. Fixes #86102. (#86384) Thanks @1052326311.
- Discord: suppress a bot's previous reply body and referenced media from prompt context when a user replies to that bot message, while keeping reply metadata for routing. (#86238) Thanks @fuller-stack-dev.
- Discord: restore bare numeric channel IDs for outbound message-tool sends while keeping explicit DM targets unambiguous. (#86571) Thanks @joshavant.
- Docker E2E: avoid rebuilding the Control UI twice while preparing the shared OpenClaw package tarball for package-backed scenario runs.
- Tests: avoid rebuilding the Control UI twice during the installer Docker smoke now that `pnpm build` includes `ui:build`.
- Tests: give QA config mutation RPCs enough native Windows budget to finish gateway config writes and restart settle after hot scenario runs.
- Tests: keep the gateway restart-inflight QA scenario focused on restart recovery on native Windows by allowing expected embedded prompt handoff errors and using the Windows-safe timeout budget.
- QA-Lab: make the synthetic OpenAI provider honor generic `reply exactly:` directives after required kickoff reads so restart-recovery scenarios do not fall through to generic repo-summary prose.
- Gateway: abort active `agent` RPC runs during forced restart shutdown so stale in-process turns cannot keep writing a session after the Gateway lifecycle restarts.
- Crabbox: sync clean sparse worktrees through a temporary full checkout even when reusing an existing lease so tracked build-time files are not omitted.
- Build: route `scripts/ui.js` through the shared pnpm runner and keep Control UI chunking helpers in sparse-included source so native Windows Corepack builds can produce `dist/control-ui`.
- Tests: give the memory fallback QA scenario enough turn budget to exercise native Windows gateway runs instead of failing on the client timeout while the mock agent is still dispatching.
- Tests: collect QA gateway CPU/RSS metrics on native Windows and give the channel baseline enough turn budget to report slow gateway runs instead of timing out before proof.
@@ -58,18 +127,23 @@ Docs: https://docs.openclaw.ai
- Checks: keep intentional Knip unused-file findings optional so full CI and sparse proof workspaces stay aligned.
- Docker: restore writable `~/.config` in runtime images. Fixes #85968. Thanks @hkoessler and @Bartok9.
- Plugin SDK: keep legacy root diagnostic subscriptions connected when built plugin SDK aliases resolve diagnostic helpers through a separate module graph.
- Diagnostics: export alertable OTel and Prometheus signals for blocked tools, model failover, stale sessions, liveness warnings, oversized payloads, and webhook ingress while fixing shared OTLP endpoints with query strings.
- Tests: normalize macOS canonical temp paths in exec allowlists, fs-safe trash assertions, installed plugin matching, Telegram topic-name stores, and built ACPX MCP server expectations so native macOS proof runners cover the intended behavior.
- Codex/app-server: preserve message-tool-only source reply delivery mode on active runs so sub-agent completion wakeups can steer the active Codex turn instead of being rejected. (#86287) Thanks @ferminquant.
- Tests: sample the Windows kitchen-sink RPC gateway directly and serialize RSS probes so native runs keep the memory guard active.
- Tests: normalize bundled plugin lifecycle probe paths and state-root lookup so native Windows release sweeps accept valid packaged plugin installs.
- Agents/Claude CLI: route live native Bash permission requests through OpenClaw exec policy so Claude turns no longer stall on `control_request`, and document that OpenClaw exec policy is authoritative. Fixes #80819. (#86330, from #81971) Thanks @guthirry and @sallyom.
- Security audit: warn when YOLO OpenClaw exec policy overrides a restrictive raw Claude `--permission-mode` for managed live sessions. (#86557) Thanks @sallyom.
- Config: keep benign legacy metadata write anomalies out of default doctor and config command output while preserving explicit anomaly logging for diagnostics.
- Codex: log when implicit app-server `never` approvals are promoted for OpenClaw tool policy, including whether the trigger was a `before_tool_call` hook or trusted tool policy.
- Codex harness: make subscription usage-limit errors without reset times explain that OpenClaw cannot determine the reset and point users to wait until Codex is available, use another Codex account, or switch to another configured model/provider. Thanks @amknight.
- Google Vertex: support production ADC modes such as Workload Identity Federation, service-account credentials, and metadata-server ADC for the native Vertex transport. (#83971) Thanks @damianFelixPago.
- Telegram: route normal `[telegram][diag]` polling diagnostics through `runtime.log` while keeping non-diag warnings and persistence failures on `runtime.error`, so healthy polling startup no longer looks like an error. Fixes #82957. (#82958) Thanks @galiniliev.
- Providers/Ollama: strip inline Kimi cloud reasoning prefixes from streamed and final visible replies while keeping ordinary Kimi answers append-only. (#86286) Thanks @jason-allen-oneal.
- Gateway: require Talk secret authority before setup-code handoff can include Talk secrets. (#85690) Thanks @ngutman.
- Agents: keep fallback error reporting scoped to the active model candidate so stale prior-provider quota/auth text is not reported for later fallback attempts. (#86134) thanks @zhangguiping-xydt.
- iMessage: dedupe watcher startup when `channels.imessage.accounts` lists both `default` and a named account that point at the same local Messages source, so the gateway no longer spawns two `imsg rpc` processes or doubles inbound replies; the dedupe is scoped to watcher startup, leaving duplicate accounts addressable for outbound sends, status, and capability listings, and `openclaw doctor` flags the redundant account with a rebinding hint. Fixes #65141. (#86705) Thanks @swang430.
## 2026.5.25
@@ -118,6 +192,7 @@ Docs: https://docs.openclaw.ai
- Crabbox: install Corepack shims into the writable hydration `PNPM_HOME` so local AWS runner hydration no longer tries to overwrite `/usr/local/bin/pnpm`.
- Live tests: fail Gateway live model sweeps when selected coverage is lost to timeouts or stale high-signal filters instead of reporting false missing-profile coverage, and pin Docker OpenAI gateway coverage to the current `gpt-5.5` lane.
- Tests: fail Docker resource-ceiling checks when stats samples or configured limits are invalid instead of silently reporting zero peaks.
- Auth/Codex: emit a one-shot actionable `log.warn` from the embedded legacy Codex OAuth sidecar loader when the only available seed lives in the macOS Keychain, naming `openclaw doctor --fix` and macOS Keychain instead of letting the credential silently fall through to a downstream `No API key found for provider "openai-codex"`. Thanks @romneyda.
- Agents: fail closed when provider-less session models match multiple provider-prefixed runtime policies so CLI runtime routing no longer depends on config order. (#85970) Thanks @potterdigital.
- Control UI/agents: keep collapsed tool rows readable without early ellipses, preserve raw expanded tool details, and make post-compaction AGENTS.md reinjection opt-in to avoid duplicated project context. Fixes #45649 and #45488. Thanks @BunsDev.
@@ -166,6 +241,7 @@ Docs: https://docs.openclaw.ai
- Gateway/plugins: reuse a compatible Gateway startup plugin registry during dispatch so safe plugin dispatches avoid redundant registry loading. (#84324) Thanks @ai-hpc.
- Plugins/SDK: add a general `embeddingProviders` capability contract and registration API so embeddings can become a reusable provider surface outside memory-specific adapters.
- Dependencies: refresh provider, plugin, UI, and tooling packages, update `protobufjs` to 8.4.0 to clear the current npm advisory, and carry the Claude ACP completion patch forward to `@agentclientprotocol/claude-agent-acp` 0.36.1.
- ACPX: bump the bundled ACP backend to `acpx` 0.10.0 for session export/import support.
- Agents/tools: remove the old sender-owner tool gating path so configured tools stay visible for trusted sessions while command and channel-action auth still carry real sender identity.
- QA-Lab: add curated mock JSONL replay fixtures and first-drift reporting for runtime-parity audits. (#80323, refs #80176) Thanks @100yenadmin.
- QA-Lab: add a QA bus tool-trace visibility scenario for sanitized tool-call assertions.
@@ -185,6 +261,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- CLI/update: allow package-manager-managed hardlinked package roots during global update swaps while keeping generic plugin, hook, and dependency-free install moves fail-closed. (#85569) Thanks @ai-hpc.
- Gateway/update: avoid fetching unrelated tags during dev-channel git updates so moved release tags do not block branch-based updates. (#84737) Thanks @rubencu.
- CLI/update: suppress the expected future-config warning while an old update parent hands off to the freshly installed post-core process.
- MiniMax: store OAuth token expiry as an absolute millisecond timestamp so OAuth profiles no longer appear expired on every request. (#83480) Thanks @NianJiuZst.
@@ -2096,6 +2173,7 @@ Docs: https://docs.openclaw.ai
- Telegram/groups: include the recent local chat window and nearby reply-target window as generic inbound context so stale reply ancestry does not overshadow the live group conversation.
- Plugins/Nix: allow externally configured plugin roots under `/nix/store` to load in `OPENCLAW_NIX_MODE=1` while keeping normal external plugin hardlink rejection unchanged. Thanks @joshp123.
- Nextcloud Talk: include the required bot `response` feature in setup, explain missing `--feature response` on rejected sends, and surface missing response capability in doctor/status checks. Fixes #78935. (#79657) Thanks @joshavant.
- Cron/diagnostics: emit the existing `message.queued`, `session.state` (processing/idle), and `message.processed` lifecycle events for isolated-cron agent turns in `runCronIsolatedAgentTurn`, matching the dispatch and embedded-runner paths so subscribers (diagnostics OTLP, OTel exporters, custom observability plugins) get per-run session attribution instead of bucketing isolated cron LLM calls under static fallback ids. Events are gated on `isDiagnosticsEnabled(cfg)` so the documented `diagnostics.enabled: false` master toggle continues to silence the recorder. (#79214) Thanks @arniesaha.
- fix(discord): gate user allowlist name resolution [AI]. (#79002) Thanks @pgondhi987.
- fix(msteams): gate startup user allowlist resolution [AI]. (#79003) Thanks @pgondhi987.
- Infra/fetch-timeout: pass `operation` and `url` context to `buildTimeoutAbortSignal` from the music-generate reference fetch and the Matrix guarded redirect transport, so the `fetch timeout reached; aborting operation` warning carries actionable structured fields instead of a bare line. Fixes #79195. Thanks @pandadev66.

View File

@@ -107,6 +107,7 @@ For coordinated change sets that genuinely need more than 20 PRs, join the **#cl
- Test locally with your OpenClaw instance
- External PRs must include a filled **Real behavior proof** section in the PR body. Show the real setup you tested, the exact command or steps you ran after the patch, after-fix evidence, the observed result, and anything you did not test. Screenshots, recordings, terminal screenshots, console output, copied live output, linked artifacts, and redacted runtime logs all count. Unit tests, mocks, snapshots, lint, typechecks, and CI are useful but do not satisfy this requirement by themselves. Maintainers may apply `proof: override` only when the proof gate should not apply.
- Keep PRs takeover-ready: open them from a branch maintainers can push to. For fork PRs, leave GitHub's **Allow edits by maintainers** option enabled so maintainers can finish urgent fixes, changelog entries, or merge prep when needed. If GitHub shows **Allow edits and access to secrets by maintainers**, enable it only when that workflow/secrets access is acceptable and say so in the PR.
- Do not edit `CHANGELOG.md` in contributor PRs. Maintainers or ClawSweeper add the changelog entry when landing user-facing changes.
- Run tests: `pnpm build && pnpm check && pnpm test`
- For iterative local commits, `scripts/committer --fast "message" <files...>` passes `FAST_COMMIT=1` through to the pre-commit hook so it skips the repo-wide `pnpm check`. Only use it when you've already run equivalent targeted validation for the touched surface.

View File

@@ -65,8 +65,8 @@ android {
applicationId = "ai.openclaw.app"
minSdk = 31
targetSdk = 36
versionCode = 2026052501
versionName = "2026.5.25"
versionCode = 2026052601
versionName = "2026.5.26"
ndk {
// Support all major ABIs — native libs are tiny (~47 KB per ABI)
abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")

View File

@@ -1,5 +1,9 @@
# OpenClaw iOS Changelog
## 2026.5.26 - 2026-05-26
Maintenance update for the current OpenClaw release.
## 2026.5.25 - 2026-05-25
Maintenance update for the current OpenClaw release.

View File

@@ -2,8 +2,8 @@
// Source of truth: apps/ios/version.json
// Generated by scripts/ios-sync-versioning.ts.
OPENCLAW_IOS_VERSION = 2026.5.25
OPENCLAW_MARKETING_VERSION = 2026.5.25
OPENCLAW_IOS_VERSION = 2026.5.26
OPENCLAW_MARKETING_VERSION = 2026.5.26
OPENCLAW_BUILD_VERSION = 1
#include? "../build/Version.xcconfig"

View File

@@ -85,6 +85,8 @@ struct TalkToolbarTray: View {
var isSpeaking: Bool
var isUserSpeechDetected: Bool
var permissionState: TalkGatewayPermissionState
var voiceModeTitle: String
var voiceModeSubtitle: String?
var onEnableTalk: () -> Void
var onStopTalk: () -> Void
@@ -135,6 +137,13 @@ struct TalkToolbarTray: View {
.foregroundStyle(.secondary)
.lineLimit(1)
}
if let voiceModeText = self.voiceModeText {
Text(voiceModeText)
.font(.caption2.weight(.semibold))
.foregroundStyle(.secondary)
.lineLimit(1)
}
}
Spacer(minLength: 0)
@@ -193,7 +202,22 @@ struct TalkToolbarTray: View {
}
.accessibilityElement(children: .combine)
.accessibilityLabel("Talk Mode")
.accessibilityValue("\(self.state.title), \(self.subtitle)")
.accessibilityValue(self.accessibilityValue)
}
private var accessibilityValue: String {
if let voiceModeText {
return "\(self.state.title), \(self.subtitle), \(voiceModeText)"
}
return "\(self.state.title), \(self.subtitle)"
}
private var voiceModeText: String? {
guard !self.state.prefersPermissionCopy else { return nil }
let title = self.voiceModeTitle.trimmingCharacters(in: .whitespacesAndNewlines)
guard !title.isEmpty, title != "Not loaded" else { return nil }
let subtitle = (self.voiceModeSubtitle ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
return subtitle.isEmpty ? title : "\(title)\(subtitle)"
}
private var subtitle: String {

View File

@@ -519,6 +519,8 @@ private struct CanvasContent: View {
isSpeaking: self.appModel.talkMode.isSpeaking,
isUserSpeechDetected: self.appModel.talkMode.isUserSpeechDetected,
permissionState: self.appModel.talkMode.gatewayTalkPermissionState,
voiceModeTitle: self.appModel.talkMode.gatewayTalkVoiceModeTitle,
voiceModeSubtitle: self.appModel.talkMode.gatewayTalkVoiceModeSubtitle,
onEnableTalk: {
self.showTalkPermissionPrompt = true
},

View File

@@ -612,7 +612,7 @@ struct SettingsTab: View {
private var shouldShowRealtimeVoicePicker: Bool {
let providerSelection = TalkModeProviderSelection.resolved(self.talkProviderSelectionRaw)
return providerSelection == .openAIRealtime
|| self.appModel.talkMode.gatewayTalkUsesRealtimeRelay
|| self.appModel.talkMode.gatewayTalkUsesRealtime
}
private func talkVoiceSettingsView() -> AnyView {
@@ -633,6 +633,16 @@ struct SettingsTab: View {
}
}
}
LabeledContent("Voice Mode") {
VStack(alignment: .trailing, spacing: 2) {
Text(self.appModel.talkMode.gatewayTalkVoiceModeTitle)
if let subtitle = self.appModel.talkMode.gatewayTalkVoiceModeSubtitle {
Text(subtitle)
.font(.caption)
.foregroundStyle(.secondary)
}
}
}
LabeledContent(
"Active Provider",
value: self.appModel.talkMode.gatewayTalkProviderLabel)

View File

@@ -3,9 +3,107 @@ import OpenClawKit
enum TalkModeExecutionMode {
case native
case realtimeClient
case realtimeRelay
}
struct TalkVoiceModeDescriptor: Equatable {
let title: String
let subtitle: String?
let providerId: String?
let modelId: String?
let voiceId: String?
let transport: String?
let isRealtime: Bool
var accessibilityValue: String {
if let subtitle, !subtitle.isEmpty {
return "\(self.title), \(subtitle)"
}
return self.title
}
}
enum TalkVoiceModeDescriptorBuilder {
static func build(
providerId: String,
providerLabel: String,
modelId: String?,
voiceId: String?,
transport: String?,
isRealtime: Bool) -> TalkVoiceModeDescriptor
{
let normalizedProvider = providerId.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
let trimmedModel = Self.trimmed(modelId)
let trimmedVoice = Self.trimmed(voiceId)
let trimmedTransport = Self.trimmed(transport)
let title = if isRealtime, normalizedProvider == "openai", trimmedModel == "gpt-realtime-2" {
"GPT Realtime 2.0"
} else if isRealtime, normalizedProvider == "openai" {
"OpenAI Realtime"
} else if isRealtime {
providerLabel.isEmpty ? "Realtime Voice" : providerLabel
} else if normalizedProvider == "system" {
"iOS System Voice"
} else {
providerLabel.isEmpty ? "Talk Voice" : providerLabel
}
var details: [String] = []
if isRealtime, normalizedProvider != "openai", !providerLabel.isEmpty, providerLabel != title {
details.append(providerLabel)
}
if let trimmedTransport {
details.append(Self.transportLabel(trimmedTransport))
}
if let trimmedModel, title != "GPT Realtime 2.0" || trimmedModel != "gpt-realtime-2" {
details.append(trimmedModel)
}
if let trimmedVoice {
details.append(Self.voiceLabel(trimmedVoice))
}
return TalkVoiceModeDescriptor(
title: title,
subtitle: details.isEmpty ? nil : details.joined(separator: ""),
providerId: normalizedProvider.isEmpty ? nil : normalizedProvider,
modelId: trimmedModel,
voiceId: trimmedVoice,
transport: trimmedTransport,
isRealtime: isRealtime)
}
private static func trimmed(_ value: String?) -> String? {
let trimmed = (value ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
return trimmed.isEmpty ? nil : trimmed
}
private static func voiceLabel(_ voice: String) -> String {
TalkModeRealtimeVoiceSelection.voices.contains(voice)
? TalkModeRealtimeVoiceSelection.label(for: voice)
: voice
}
private static func transportLabel(_ transport: String) -> String {
switch transport.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() {
case "webrtc":
"Native WebRTC"
case "gateway-relay":
"Gateway Relay"
case "provider-websocket":
"Provider WebSocket"
case "managed-room":
"Managed Room"
case "native":
"Native"
case let value where !value.isEmpty:
value
default:
"Native"
}
}
}
enum TalkModeProviderSelection: String, CaseIterable, Identifiable {
case gatewayDefault = "gateway"
case nativeElevenLabs = "elevenlabs"
@@ -112,8 +210,9 @@ enum TalkModeGatewayConfigParser {
let defaultVoiceId = Self.firstString(activeConfig, keys: ["voiceId", "voice"])
let defaultOutputFormat = Self.firstString(activeConfig, keys: ["outputFormat"])
let realtime = talk?["realtime"]?.dictionaryValue
let realtimeProvider = Self.firstString(realtime, keys: ["provider"])
let realtimeProviders = realtime?["providers"]?.dictionaryValue
let realtimeProvider = Self.firstString(realtime, keys: ["provider"])
?? Self.singleRealtimeProviderId(realtimeProviders)
let realtimeProviderConfig = Self.realtimeProviderConfig(
providers: realtimeProviders,
provider: realtimeProvider)
@@ -164,12 +263,24 @@ enum TalkModeGatewayConfigParser {
let mode = Self.firstString(realtime, keys: ["mode"])?.lowercased()
let transport = Self.firstString(realtime, keys: ["transport"])?.lowercased()
let brain = Self.firstString(realtime, keys: ["brain"])?.lowercased()
if mode == "realtime", transport == "gateway-relay", brain == nil || brain == "agent-consult" {
guard mode == "realtime", brain == nil || brain == "agent-consult" else {
return .native
}
if transport == "gateway-relay" {
return .realtimeRelay
}
if transport == nil || transport == "webrtc" {
return .realtimeClient
}
return .native
}
private static func singleRealtimeProviderId(_ providers: [String: AnyCodable]?) -> String? {
guard let providers, providers.count == 1 else { return nil }
let provider = providers.keys.first?.trimmingCharacters(in: .whitespacesAndNewlines)
return provider?.isEmpty == false ? provider : nil
}
private static func realtimeProviderConfig(
providers: [String: AnyCodable]?,
provider: String?) -> [String: AnyCodable]?

View File

@@ -116,10 +116,14 @@ final class TalkModeManager: NSObject {
var gatewayTalkDefaultVoiceId: String?
var gatewayTalkProviderLabel: String = "Not loaded"
var gatewayTalkTransportLabel: String = "Not loaded"
var gatewayTalkUsesRealtime: Bool = false
var gatewayTalkUsesRealtimeRelay: Bool = false
var gatewayTalkRealtimeProviderLabel: String?
var gatewayTalkRealtimeModelId: String?
var gatewayTalkRealtimeVoiceId: String?
var gatewayTalkVoiceModeTitle: String = "Not loaded"
var gatewayTalkVoiceModeSubtitle: String?
var gatewayTalkVoiceModeAccessibilityValue: String = "Not loaded"
var gatewayTalkPermissionState: TalkGatewayPermissionState = .unknown
private enum CaptureMode {
@@ -145,6 +149,7 @@ final class TalkModeManager: NSObject {
private var recognitionTask: SFSpeechRecognitionTask?
private var silenceTask: Task<Void, Never>?
private var realtimeSession: TalkRealtimeWebRTCSession?
private var realtimeRelaySession: RealtimeTalkRelaySession?
private var prefetchedRealtimeSession: TalkRealtimeClientSession?
private var realtimePrefetchTask: Task<Void, Never>?
@@ -167,6 +172,14 @@ final class TalkModeManager: NSObject {
private var realtimeProvider: String?
private var realtimeModelId: String?
private var realtimeVoiceId: String?
private var configuredVoiceModeDescriptor = TalkVoiceModeDescriptor(
title: "Not loaded",
subtitle: nil,
providerId: nil,
modelId: nil,
voiceId: nil,
transport: nil,
isRealtime: false)
private var apiKey: String?
private var voiceAliases: [String: String] = [:]
private var interruptOnSpeech: Bool = true
@@ -304,8 +317,13 @@ final class TalkModeManager: NSObject {
GatewayDiagnostics.log("talk.timeline manager start blocked gateway permission")
return
}
if self.realtimeWebRTCEnabled, await self.startRealtimeIfAvailable() {
return
if self.realtimeWebRTCEnabled {
let started = self.executionMode == .realtimeRelay
? await self.startRealtimeRelayIfAvailable()
: await self.startRealtimeIfAvailable()
if started {
return
}
}
let speechOk = await Self.requestSpeechPermission()
@@ -419,6 +437,7 @@ final class TalkModeManager: NSObject {
if let realtimeSession {
realtimeSession.cancelResponse()
}
self.realtimeRelaySession?.cancelOutput()
self.stopSpeaking()
}
@@ -1046,9 +1065,68 @@ final class TalkModeManager: NSObject {
}
}
private func startRealtimeRelayIfAvailable() async -> Bool {
guard let gateway else { return false }
GatewayDiagnostics.log("talk.timeline realtime relay start attempt sessionKey=\(self.mainSessionKey)")
let startedAt = Self.nowSeconds()
let relaySession = RealtimeTalkRelaySession(
gateway: gateway,
options: RealtimeTalkRelaySession.Options(
sessionKey: self.mainSessionKey,
provider: self.realtimeProvider,
model: self.realtimeModelId,
voice: self.realtimeVoiceId),
pcmPlayer: self.pcmPlayer,
onStatus: { [weak self] status in
guard let self else { return }
self.statusText = status
self.isListening = status.localizedCaseInsensitiveContains("listening")
if status.localizedCaseInsensitiveContains("thinking") {
self.isListening = false
self.isSpeaking = false
self.isUserSpeechDetected = false
}
},
onSpeakingChanged: { [weak self] speaking in
guard let self else { return }
self.isSpeaking = speaking
if speaking {
self.isListening = false
}
})
self.realtimeRelaySession = relaySession
do {
try Self.configureAudioSession()
try await relaySession.start()
guard self.realtimeRelaySession === relaySession, self.isEnabled else {
relaySession.stop()
return true
}
self.isListening = true
self.captureMode = .continuous
GatewayDiagnostics.log(
"talk.timeline realtime relay start ready elapsedMs=\(Self.elapsedMs(since: startedAt))")
return true
} catch {
guard self.realtimeRelaySession === relaySession, self.isEnabled else {
relaySession.stop()
return true
}
self.realtimeRelaySession = nil
GatewayDiagnostics.log(
"talk.timeline realtime relay start failed elapsedMs=\(Self.elapsedMs(since: startedAt)) "
+ "error=\(error.localizedDescription)")
return false
}
}
func prefetchRealtimeSessionIfReady(reason: String) async {
guard self.gatewayConnected, self.realtimeSession == nil, !self.isEnabled else { return }
guard self.realtimeWebRTCEnabled else { return }
guard self.gatewayConnected,
self.realtimeSession == nil,
self.realtimeRelaySession == nil,
!self.isEnabled
else { return }
guard self.realtimeWebRTCEnabled, self.executionMode != .realtimeRelay else { return }
guard self.gatewayTalkPermissionState == .ready else { return }
guard self.consumePrefetchedRealtimeSession(peekOnly: true) == nil else { return }
guard self.realtimePrefetchTask == nil else { return }
@@ -1116,6 +1194,8 @@ final class TalkModeManager: NSObject {
private func stopRealtimeSession() {
self.realtimeSession?.stop()
self.realtimeSession = nil
self.realtimeRelaySession?.stop()
self.realtimeRelaySession = nil
}
private func subscribeChatIfNeeded(sessionKey: String) async {
@@ -1305,6 +1385,14 @@ final class TalkModeManager: NSObject {
if canUseElevenLabs, let voiceId, let apiKey {
GatewayDiagnostics.log("talk tts: provider=elevenlabs voiceId=\(voiceId)")
let modelId = directive?.modelId ?? self.currentModelId ?? self.defaultModelId
self.applyVoiceModeDescriptor(TalkVoiceModeDescriptorBuilder.build(
providerId: "elevenlabs",
providerLabel: Self.displayName(forProvider: "elevenlabs"),
modelId: modelId,
voiceId: voiceId,
transport: "native",
isRealtime: false))
let desiredOutputFormat = (directive?.outputFormat ?? self.defaultOutputFormat)?
.trimmingCharacters(in: .whitespacesAndNewlines)
let requestedOutputFormat = (desiredOutputFormat?.isEmpty == false) ? desiredOutputFormat : nil
@@ -1315,7 +1403,6 @@ final class TalkModeManager: NSObject {
"talk output_format unsupported for local playback: \(requestedOutputFormat, privacy: .public)")
}
let modelId = directive?.modelId ?? self.currentModelId ?? self.defaultModelId
if let modelId {
GatewayDiagnostics.log("talk tts: modelId=\(modelId)")
}
@@ -1384,6 +1471,13 @@ final class TalkModeManager: NSObject {
} else {
self.logger.warning("tts unavailable; falling back to system voice (missing key or voiceId)")
GatewayDiagnostics.log("talk tts: provider=system (missing key or voiceId)")
self.applyVoiceModeDescriptor(TalkVoiceModeDescriptorBuilder.build(
providerId: "system",
providerLabel: Self.displayName(forProvider: "system"),
modelId: nil,
voiceId: language,
transport: "native",
isRealtime: false))
if self.interruptOnSpeech {
do {
try self.startRecognition()
@@ -1400,6 +1494,14 @@ final class TalkModeManager: NSObject {
"tts failed: \(error.localizedDescription, privacy: .public); falling back to system voice")
GatewayDiagnostics.log("talk tts: provider=system (error) msg=\(error.localizedDescription)")
do {
let language = ElevenLabsTTSClient.validatedLanguage(directive?.language)
self.applyVoiceModeDescriptor(TalkVoiceModeDescriptorBuilder.build(
providerId: "system",
providerLabel: Self.displayName(forProvider: "system"),
modelId: nil,
voiceId: language,
transport: "native",
isRealtime: false))
if self.interruptOnSpeech {
do {
try self.startRecognition()
@@ -1409,7 +1511,6 @@ final class TalkModeManager: NSObject {
}
}
self.statusText = "Speaking (System)…"
let language = ElevenLabsTTSClient.validatedLanguage(directive?.language)
try await TalkSystemSpeechSynthesizer.shared.speak(text: cleaned, language: language)
} catch {
self.statusText = "Speak failed: \(error.localizedDescription)"
@@ -1419,6 +1520,7 @@ final class TalkModeManager: NSObject {
self.stopRecognition()
self.isSpeaking = false
self.restoreConfiguredVoiceModeDescriptor()
}
private func stopSpeaking(storeInterruption: Bool = true) {
@@ -1441,6 +1543,7 @@ final class TalkModeManager: NSObject {
TalkSystemSpeechSynthesizer.shared.stop()
self.cancelIncrementalSpeech()
self.isSpeaking = false
self.restoreConfiguredVoiceModeDescriptor()
}
private func shouldInterrupt(with transcript: String) -> Bool {
@@ -2256,6 +2359,10 @@ extension TalkModeManager {
"OpenAI"
case "google":
"Google"
case "system":
"iOS System Voice"
case "realtime":
"Realtime Voice"
case let provider where !provider.isEmpty:
provider
default:
@@ -2263,6 +2370,36 @@ extension TalkModeManager {
}
}
private func applyVoiceModeDescriptor(_ descriptor: TalkVoiceModeDescriptor, persistAsConfigured: Bool = false) {
if persistAsConfigured {
self.configuredVoiceModeDescriptor = descriptor
}
self.gatewayTalkVoiceModeTitle = descriptor.title
self.gatewayTalkVoiceModeSubtitle = descriptor.subtitle
self.gatewayTalkVoiceModeAccessibilityValue = descriptor.accessibilityValue
}
private func restoreConfiguredVoiceModeDescriptor() {
self.applyVoiceModeDescriptor(self.configuredVoiceModeDescriptor)
}
private func buildConfiguredVoiceModeDescriptor(
provider: String,
providerLabel: String,
modelId: String?,
voiceId: String?,
transport: String,
isRealtime: Bool) -> TalkVoiceModeDescriptor
{
TalkVoiceModeDescriptorBuilder.build(
providerId: provider,
providerLabel: providerLabel,
modelId: modelId,
voiceId: voiceId,
transport: transport,
isRealtime: isRealtime)
}
private func ensureTalkConfigLoadedForStart() async {
if self.gatewayTalkConfigLoaded || self.gatewayTalkPermissionState.isApprovalRequestInProgress {
GatewayDiagnostics.log(
@@ -2341,7 +2478,7 @@ extension TalkModeManager {
realtimeProvider = realtimeProvider ?? "openai"
realtimeModelId = realtimeModelId ?? Self.defaultRealtimeModelIdFallback
}
let usesRealtimeConfig = activeProvider != Self.defaultTalkProvider || executionMode == .realtimeRelay
let usesRealtimeConfig = activeProvider != Self.defaultTalkProvider || executionMode != .native
self.activeTalkProvider = activeProvider
self.executionMode = executionMode
self.realtimeWebRTCEnabled = usesRealtimeConfig
@@ -2377,14 +2514,31 @@ extension TalkModeManager {
}
self.gatewayTalkDefaultVoiceId = usesRealtimeConfig ? realtimeVoiceId : self.defaultVoiceId
self.gatewayTalkDefaultModelId = usesRealtimeConfig ? realtimeModelId : self.defaultModelId
self.gatewayTalkProviderLabel = providerSelection == .gatewayDefault
let providerLabel = providerSelection == .gatewayDefault
? Self.displayName(forProvider: activeProvider)
: providerSelection.label
self.gatewayTalkUsesRealtimeRelay = false
self.gatewayTalkTransportLabel = usesRealtimeConfig ? "Native WebRTC" : "Native"
let usesRealtimeRelay = executionMode == .realtimeRelay
let transport = usesRealtimeConfig ? (usesRealtimeRelay ? "gateway-relay" : "webrtc") : "native"
let transportLabel = usesRealtimeRelay ? "Gateway Relay" : (usesRealtimeConfig ? "Native WebRTC" : "Native")
self.gatewayTalkProviderLabel = providerLabel
self.gatewayTalkUsesRealtime = usesRealtimeConfig
self.gatewayTalkUsesRealtimeRelay = usesRealtimeRelay
self.gatewayTalkTransportLabel = transportLabel
self.gatewayTalkRealtimeProviderLabel = realtimeProvider.map { Self.displayName(forProvider: $0) }
self.gatewayTalkRealtimeModelId = realtimeModelId
self.gatewayTalkRealtimeVoiceId = realtimeVoiceId
let voiceModeProvider = usesRealtimeConfig ? (realtimeProvider ?? "realtime") : activeProvider
let voiceModeLabel = usesRealtimeConfig
? Self.displayName(forProvider: voiceModeProvider)
: Self.displayName(forProvider: activeProvider)
let voiceModeDescriptor = self.buildConfiguredVoiceModeDescriptor(
provider: voiceModeProvider,
providerLabel: voiceModeLabel,
modelId: usesRealtimeConfig ? realtimeModelId : self.defaultModelId,
voiceId: usesRealtimeConfig ? realtimeVoiceId : self.defaultVoiceId,
transport: transport,
isRealtime: usesRealtimeConfig)
self.applyVoiceModeDescriptor(voiceModeDescriptor, persistAsConfigured: true)
self.gatewayTalkApiKeyConfigured = gatewayOwnedVoiceProvider || (self.apiKey?.isEmpty == false)
self.gatewayTalkConfigLoaded = true
self.talkConfigLoadedAt = Date()
@@ -2416,10 +2570,19 @@ extension TalkModeManager {
self.realtimeVoiceId = nil
self.gatewayTalkProviderLabel = "Not loaded"
self.gatewayTalkTransportLabel = "Not loaded"
self.gatewayTalkUsesRealtime = false
self.gatewayTalkUsesRealtimeRelay = false
self.gatewayTalkRealtimeProviderLabel = nil
self.gatewayTalkRealtimeModelId = nil
self.gatewayTalkRealtimeVoiceId = nil
self.applyVoiceModeDescriptor(TalkVoiceModeDescriptor(
title: "Not loaded",
subtitle: nil,
providerId: nil,
modelId: nil,
voiceId: nil,
transport: nil,
isRealtime: false), persistAsConfigured: true)
self.defaultModelId = Self.defaultModelIdFallback
if !self.modelOverrideActive {
self.currentModelId = self.defaultModelId

View File

@@ -48,6 +48,46 @@ import Testing
#expect(parsed.realtimeVoiceId == "marin")
}
@Test func infersRealtimeProviderWhenProviderMapHasSingleEntry() {
let config: [String: Any] = [
"talk": [
"realtime": [
"mode": "realtime",
"transport": "webrtc",
"providers": [
"openai": [
"model": "gpt-realtime-2",
],
],
],
],
]
let parsed = TalkModeGatewayConfigParser.parse(
config: config,
defaultProvider: "elevenlabs",
defaultModelIdFallback: "eleven_v3",
defaultRealtimeModelIdFallback: "gpt-realtime-2",
defaultSilenceTimeoutMs: 900)
#expect(parsed.executionMode == .realtimeClient)
#expect(parsed.realtimeProvider == "openai")
#expect(parsed.realtimeModelId == "gpt-realtime-2")
}
@Test func formatsGenericRealtimeVoiceModeWithoutNativeProviderFallback() {
let descriptor = TalkVoiceModeDescriptorBuilder.build(
providerId: "realtime",
providerLabel: "Realtime Voice",
modelId: "gpt-realtime-2",
voiceId: nil,
transport: "webrtc",
isRealtime: true)
#expect(descriptor.title == "Realtime Voice")
#expect(descriptor.subtitle == "Native WebRTC • gpt-realtime-2")
}
@Test func defaultsOpenAIRealtimeModelWhenProviderOmitsModel() {
let config: [String: Any] = [
"talk": [
@@ -79,7 +119,60 @@ import Testing
#expect(TalkModeRealtimeVoiceSelection.resolvedOverride("unknown") == nil)
}
@Test func leavesNativeModeWhenRealtimeTransportIsNotGatewayRelay() {
@Test func formatsOpenAIRealtimeVoiceMode() {
let descriptor = TalkVoiceModeDescriptorBuilder.build(
providerId: "openai",
providerLabel: "OpenAI",
modelId: "gpt-realtime-2",
voiceId: "marin",
transport: "webrtc",
isRealtime: true)
#expect(descriptor.title == "GPT Realtime 2.0")
#expect(descriptor.subtitle == "Native WebRTC • Marin")
#expect(descriptor.accessibilityValue == "GPT Realtime 2.0, Native WebRTC • Marin")
}
@Test func formatsGatewayRelayRealtimeVoiceMode() {
let descriptor = TalkVoiceModeDescriptorBuilder.build(
providerId: "google",
providerLabel: "Google Live Voice",
modelId: "gemini-live-2.5-flash-preview",
voiceId: nil,
transport: "gateway-relay",
isRealtime: true)
#expect(descriptor.title == "Google Live Voice")
#expect(descriptor.subtitle == "Gateway Relay • gemini-live-2.5-flash-preview")
}
@Test func formatsElevenLabsVoiceMode() {
let descriptor = TalkVoiceModeDescriptorBuilder.build(
providerId: "elevenlabs",
providerLabel: "ElevenLabs",
modelId: "eleven_v3",
voiceId: "voice-id",
transport: "native",
isRealtime: false)
#expect(descriptor.title == "ElevenLabs")
#expect(descriptor.subtitle == "Native • eleven_v3 • voice-id")
}
@Test func formatsSystemVoiceFallbackMode() {
let descriptor = TalkVoiceModeDescriptorBuilder.build(
providerId: "system",
providerLabel: "iOS System Voice",
modelId: nil,
voiceId: "en-US",
transport: "native",
isRealtime: false)
#expect(descriptor.title == "iOS System Voice")
#expect(descriptor.subtitle == "Native • en-US")
}
@Test func usesRealtimeClientModeForWebRTCTransport() {
let config: [String: Any] = [
"talk": [
"realtime": [
@@ -97,7 +190,7 @@ import Testing
defaultRealtimeModelIdFallback: "gpt-realtime-2",
defaultSilenceTimeoutMs: 900)
#expect(parsed.executionMode == .native)
#expect(parsed.executionMode == .realtimeClient)
}
@Test func detectsPCMFormatRejectionFromElevenLabsError() {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 300 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

View File

@@ -1,3 +1,3 @@
{
"version": "2026.5.25"
"version": "2026.5.26"
}

View File

@@ -6,7 +6,6 @@ import Foundation
enum HostEnvSecurityPolicy {
static let blockedInheritedKeys: Set<String> = [
"_JAVA_OPTIONS",
"AMQP_URL",
"ANSIBLE_CALLBACK_PLUGINS",
"ANSIBLE_COLLECTIONS_PATH",
@@ -31,12 +30,11 @@ enum HostEnvSecurityPolicy {
"AZURE_CLIENT_SECRET",
"BASH_ENV",
"BROWSER",
"BUN_CONFIG_REGISTRY",
"BUNDLE_GEMFILE",
"BUN_CONFIG_REGISTRY",
"BZR_EDITOR",
"BZR_PLUGIN_PATH",
"BZR_SSH",
"C_INCLUDE_PATH",
"CARGO_BUILD_RUSTC",
"CARGO_BUILD_RUSTC_WRAPPER",
"CARGO_HOME",
@@ -46,8 +44,8 @@ enum HostEnvSecurityPolicy {
"CGO_CFLAGS",
"CGO_LDFLAGS",
"CLASSPATH",
"CMAKE_C_COMPILER",
"CMAKE_CXX_COMPILER",
"CMAKE_C_COMPILER",
"CMAKE_TOOLCHAIN_FILE",
"COMPOSER_HOME",
"CONFIG_SHELL",
@@ -58,6 +56,7 @@ enum HostEnvSecurityPolicy {
"CPLUS_INCLUDE_PATH",
"CURL_HOME",
"CXX",
"C_INCLUDE_PATH",
"DATABASE_URL",
"DENO_DIR",
"DOTNET_ADDITIONAL_DEPS",
@@ -75,6 +74,8 @@ enum HostEnvSecurityPolicy {
"GEM_HOME",
"GEM_PATH",
"GH_TOKEN",
"GITHUB_TOKEN",
"GITLAB_TOKEN",
"GIT_ALTERNATE_OBJECT_DIRECTORIES",
"GIT_ASKPASS",
"GIT_COMMON_DIR",
@@ -95,8 +96,6 @@ enum HostEnvSecurityPolicy {
"GIT_SSL_NO_VERIFY",
"GIT_TEMPLATE_DIR",
"GIT_WORK_TREE",
"GITHUB_TOKEN",
"GITLAB_TOKEN",
"GLIBC_TUNABLES",
"GOENV",
"GOFLAGS",
@@ -145,8 +144,8 @@ enum HostEnvSecurityPolicy {
"PERL5DBCMD",
"PERL5LIB",
"PERL5OPT",
"PHP_INI_SCAN_DIR",
"PHPRC",
"PHP_INI_SCAN_DIR",
"PIP_CONFIG_FILE",
"PIP_EXTRA_INDEX_URL",
"PIP_FIND_LINKS",
@@ -160,17 +159,17 @@ enum HostEnvSecurityPolicy {
"PYTHONPATH",
"PYTHONSTARTUP",
"PYTHONUSERBASE",
"R_ENVIRON",
"R_ENVIRON_USER",
"R_LIBS_USER",
"R_PROFILE",
"R_PROFILE_USER",
"REDIS_URL",
"RUBYLIB",
"RUBYOPT",
"RUBYSHELL",
"RUSTC_WRAPPER",
"RUSTFLAGS",
"R_ENVIRON",
"R_ENVIRON_USER",
"R_LIBS_USER",
"R_PROFILE",
"R_PROFILE_USER",
"SBT_OPTS",
"SHELL",
"SHELLOPTS",
@@ -192,7 +191,8 @@ enum HostEnvSecurityPolicy {
"VIRTUAL_ENV",
"VISUAL",
"WGETRC",
"YARN_RC_FILENAME"
"YARN_RC_FILENAME",
"_JAVA_OPTIONS"
]
static let blockedInheritedPrefixes: [String] = [
@@ -202,7 +202,6 @@ enum HostEnvSecurityPolicy {
]
static let blockedKeys: Set<String> = [
"_JAVA_OPTIONS",
"ANT_OPTS",
"BASH_ENV",
"BROWSER",
@@ -213,8 +212,8 @@ enum HostEnvSecurityPolicy {
"CARGO_BUILD_RUSTC_WRAPPER",
"CATALINA_OPTS",
"CC",
"CMAKE_C_COMPILER",
"CMAKE_CXX_COMPILER",
"CMAKE_C_COMPILER",
"CMAKE_TOOLCHAIN_FILE",
"CONFIG_SHELL",
"CONFIG_SITE",
@@ -275,14 +274,14 @@ enum HostEnvSecurityPolicy {
"PYTHONBREAKPOINT",
"PYTHONHOME",
"PYTHONPATH",
"R_ENVIRON",
"R_ENVIRON_USER",
"R_PROFILE",
"R_PROFILE_USER",
"RUBYLIB",
"RUBYOPT",
"RUBYSHELL",
"RUSTC_WRAPPER",
"R_ENVIRON",
"R_ENVIRON_USER",
"R_PROFILE",
"R_PROFILE_USER",
"SBT_OPTS",
"SHELL",
"SHELLOPTS",
@@ -291,7 +290,8 @@ enum HostEnvSecurityPolicy {
"SVN_EDITOR",
"SVN_SSH",
"VAGRANT_VAGRANTFILE",
"VIMINIT"
"VIMINIT",
"_JAVA_OPTIONS"
]
static let blockedOverrideKeys: Set<String> = [
@@ -321,9 +321,8 @@ enum HostEnvSecurityPolicy {
"AZURE_AUTH_LOCATION",
"AZURE_CLIENT_ID",
"AZURE_CLIENT_SECRET",
"BUN_CONFIG_REGISTRY",
"BUNDLE_GEMFILE",
"C_INCLUDE_PATH",
"BUN_CONFIG_REGISTRY",
"CARGO_BUILD_RUSTC_WRAPPER",
"CARGO_HOME",
"CFLAGS",
@@ -336,6 +335,7 @@ enum HostEnvSecurityPolicy {
"CPLUS_INCLUDE_PATH",
"CURL_CA_BUNDLE",
"CURL_HOME",
"C_INCLUDE_PATH",
"DATABASE_URL",
"DENO_DIR",
"DOCKER_CERT_PATH",
@@ -347,6 +347,8 @@ enum HostEnvSecurityPolicy {
"GEM_HOME",
"GEM_PATH",
"GH_TOKEN",
"GITHUB_TOKEN",
"GITLAB_TOKEN",
"GIT_ALTERNATE_OBJECT_DIRECTORIES",
"GIT_ASKPASS",
"GIT_COMMON_DIR",
@@ -362,8 +364,6 @@ enum HostEnvSecurityPolicy {
"GIT_SSL_CAPATH",
"GIT_SSL_NO_VERIFY",
"GIT_WORK_TREE",
"GITHUB_TOKEN",
"GITLAB_TOKEN",
"GOENV",
"GOFLAGS",
"GONOPROXY",
@@ -378,8 +378,8 @@ enum HostEnvSecurityPolicy {
"HGRCPATH",
"HISTFILE",
"HOME",
"HTTP_PROXY",
"HTTPS_PROXY",
"HTTP_PROXY",
"KUBECONFIG",
"LDFLAGS",
"LESSCLOSE",
@@ -391,10 +391,10 @@ enum HostEnvSecurityPolicy {
"MANPAGER",
"MFLAGS",
"MONGODB_URI",
"NO_PROXY",
"NODE_AUTH_TOKEN",
"NODE_EXTRA_CA_CERTS",
"NODE_TLS_REJECT_UNAUTHORIZED",
"NO_PROXY",
"NPM_TOKEN",
"OBJC_INCLUDE_PATH",
"OPENSSL_CONF",
@@ -402,8 +402,8 @@ enum HostEnvSecurityPolicy {
"PAGER",
"PERL5DB",
"PERL5DBCMD",
"PHP_INI_SCAN_DIR",
"PHPRC",
"PHP_INI_SCAN_DIR",
"PIP_CONFIG_FILE",
"PIP_EXTRA_INDEX_URL",
"PIP_FIND_LINKS",
@@ -413,11 +413,11 @@ enum HostEnvSecurityPolicy {
"PROMPT_COMMAND",
"PYTHONSTARTUP",
"PYTHONUSERBASE",
"R_LIBS_USER",
"REDIS_URL",
"REQUESTS_CA_BUNDLE",
"RUSTC_WRAPPER",
"RUSTFLAGS",
"R_LIBS_USER",
"SSH_ASKPASS",
"SSH_AUTH_SOCK",
"SSL_CERT_DIR",

View File

@@ -15,9 +15,9 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>2026.5.25</string>
<string>2026.5.26</string>
<key>CFBundleVersion</key>
<string>2026052500</string>
<string>2026052600</string>
<key>CFBundleIconFile</key>
<string>OpenClaw</string>
<key>CFBundleURLTypes</key>

View File

@@ -499,6 +499,45 @@
"lines"
]
},
"transcripts": {
"emoji": "📝",
"title": "Transcripts",
"actions": {
"start": {
"label": "start",
"detailKeys": [
"providerId",
"accountId",
"guildId",
"channelId",
"title"
]
},
"stop": {
"label": "stop",
"detailKeys": [
"sessionId"
]
},
"status": {
"label": "status"
},
"import": {
"label": "import",
"detailKeys": [
"providerId",
"title",
"speakerLabel"
]
},
"summarize": {
"label": "summarize",
"detailKeys": [
"sessionId"
]
}
}
},
"web_search": {
"emoji": "🔎",
"title": "Web Search",

View File

@@ -1,4 +1,4 @@
e8e71a715bd33405280b5a9f6b6e788abed636fba66ec5f3a4f9b9a768a1637f config-baseline.json
003183db53a41905c540f37b1d30b3006ef8906915e13eac844b643cd210fdfe config-baseline.core.json
859b021f65400df22c95ae55b074cf26c83d3a0bfadb3fceeaca522f6ea391ae config-baseline.channel.json
1d2c2fa07a2d3c4d046d2defe2eb48b27011be25e75db205b19e3da37e9fd0a0 config-baseline.json
5b12e247f4375de2d454802d3af188a840851dd69e9d15a1a92a4815c7ef7d97 config-baseline.core.json
c766614db5c416910fb6cdd454efb0738779af80ddd58a4fb06d8b1ca6484ce2 config-baseline.channel.json
74441e331aabb3026784c148d4ee5ce3f489a15ed87ffd9b7ba0c5e2a7bc93be config-baseline.plugin.json

View File

@@ -1,2 +1,2 @@
390681a3d97af8c004db89ead136bd6cff693af5a0ddfe86a8e3c55a29a077eb plugin-sdk-api-baseline.json
8dfaf69ee3d0a946bfdd1d8d97ef85262824d52c20854249f900db61f2a7f7b4 plugin-sdk-api-baseline.jsonl
1f1824af61c8885360ff99dad3fe1d7b75565e540cdef57474e9f220f9876f78 plugin-sdk-api-baseline.json
4f29099d81398cb76331b618c39d298b3c9398efd84291dfb93c2098cb4ae443 plugin-sdk-api-baseline.jsonl

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

View File

@@ -1,11 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="800" height="420" viewBox="0 0 800 420" role="img" aria-label="padel-cli availability output">
<rect width="800" height="420" rx="24" fill="#0b0f14" />
<rect x="24" y="24" width="752" height="372" rx="18" fill="#0f172a" stroke="#263246" stroke-width="2" />
<text x="48" y="72" fill="#9ca3af" font-size="18" font-family="Fragment Mono, ui-monospace, SFMono-Regular, Menlo, monospace">
<tspan x="48" dy="0">$ padel search --location "Barcelona" --date 2026-01-08 --time 18:00-22:00</tspan>
<tspan x="48" dy="30" fill="#e5e7eb">Available courts (3):</tspan>
<tspan x="48" dy="28" fill="#e5e7eb">- Vall d'Hebron 19:00 Court 2 (90m) EUR 34</tspan>
<tspan x="48" dy="28" fill="#e5e7eb">- Badalona 20:30 Court 1 (60m) EUR 28</tspan>
<tspan x="48" dy="28" fill="#e5e7eb">- Gracia 21:00 Court 4 (90m) EUR 36</tspan>
</text>
</svg>

Before

Width:  |  Height:  |  Size: 897 B

View File

@@ -1,13 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="800" height="420" viewBox="0 0 800 420" role="img" aria-label="GoHome Roborock status output">
<rect width="800" height="420" rx="24" fill="#0b0f14" />
<rect x="24" y="24" width="752" height="372" rx="18" fill="#111827" stroke="#263246" stroke-width="2" />
<text x="48" y="72" fill="#9ca3af" font-size="18" font-family="Fragment Mono, ui-monospace, SFMono-Regular, Menlo, monospace">
<tspan x="48" dy="0">$ gohome roborock status --device "Living Room"</tspan>
<tspan x="48" dy="30" fill="#e5e7eb">Device: Roborock Q Revo</tspan>
<tspan x="48" dy="28" fill="#e5e7eb">State: cleaning (zone)</tspan>
<tspan x="48" dy="28" fill="#e5e7eb">Battery: 78%</tspan>
<tspan x="48" dy="28" fill="#e5e7eb">Dustbin: 42%</tspan>
<tspan x="48" dy="28" fill="#e5e7eb">Water tank: 61%</tspan>
<tspan x="48" dy="28" fill="#e5e7eb">Last clean: 2026-01-06 19:42</tspan>
</text>
</svg>

Before

Width:  |  Height:  |  Size: 947 B

View File

@@ -436,7 +436,7 @@ Model override note:
cron: {
enabled: true,
store: "~/.openclaw/cron/jobs.json",
maxConcurrentRuns: 1,
maxConcurrentRuns: 8,
retry: {
maxAttempts: 3,
backoffMs: [60000, 120000, 300000],
@@ -449,7 +449,7 @@ Model override note:
}
```
`maxConcurrentRuns` limits both scheduled cron dispatch and isolated agent-turn execution. Isolated cron agent turns use the queue's dedicated `cron-nested` execution lane internally, so raising this value lets independent cron LLM runs progress in parallel instead of only starting their outer cron wrappers. The shared non-cron `nested` lane is not widened by this setting.
`maxConcurrentRuns` limits both scheduled cron dispatch and isolated agent-turn execution, and defaults to 8. Isolated cron agent turns use the queue's dedicated `cron-nested` execution lane internally, so raising this value lets independent cron LLM runs progress in parallel instead of only starting their outer cron wrappers. The shared non-cron `nested` lane is not widened by this setting.
The runtime state sidecar is derived from `cron.store`: a `.json` store such as `~/clawd/cron/jobs.json` uses `~/clawd/cron/jobs-state.json`, while a store path without a `.json` suffix appends `-state.json`.

View File

@@ -102,7 +102,7 @@ Not every agent run creates a task. Heartbeat turns and normal interactive chat
<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 `image_generate`, `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. Generated-media completion events require message-tool delivery: the agent must send the finished media with the `message` tool, then reply `NO_REPLY`. If the requester session is no longer active and the completion agent misses some or all generated media, OpenClaw sends an idempotent direct fallback with only the missing media to the original channel target.
Session-backed `image_generate`, `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. Generated-media completion events require message-tool delivery: the agent must send the finished media with the `message` tool, then reply `NO_REPLY`. If the requester session is no longer active or its active wake fails, and the completion agent misses some or all generated media, OpenClaw sends an idempotent direct fallback with only the missing media to the original channel target.
</Accordion>
<Accordion title="Concurrent media-generation guardrail">

View File

@@ -1234,7 +1234,7 @@ Notes:
- In `stt-tts` mode, STT uses `tools.media.audio`; `voice.model` does not affect transcription.
- In realtime modes, `voice.realtime.provider`, `voice.realtime.model`, and `voice.realtime.voice` configure the realtime audio session. For OpenAI Realtime 2 plus the Codex brain, use `voice.realtime.model: "gpt-realtime-2"` and `voice.model: "openai-codex/gpt-5.5"`.
- Realtime voice modes include small `IDENTITY.md`, `USER.md`, and `SOUL.md` profile files in the realtime provider instructions by default so fast direct turns keep the same identity, user grounding, and persona as the routed OpenClaw agent. Set `voice.realtime.bootstrapContextFiles` to a subset to customize this, or `[]` to disable it. The supported realtime bootstrap files are limited to those profile files; `AGENTS.md` stays in the normal agent context. The injected profile context does not replace `openclaw_agent_consult` for workspace work, current facts, memory lookup, or tool-backed actions.
- In OpenAI `agent-proxy` realtime mode, set `voice.realtime.requireWakeName: true` to keep Discord realtime voice silent until a transcript contains a wake name. If `voice.realtime.wakeNames` is unset, OpenClaw uses the routed agent `name` plus `OpenClaw`, falling back to the agent id plus `OpenClaw`. Wake-name gating disables realtime provider auto-response and routes accepted turns through the OpenClaw agent consult path.
- In OpenAI `agent-proxy` realtime mode, set `voice.realtime.requireWakeName: true` to keep Discord realtime voice silent until a transcript starts or ends with a wake name. Configured wake names must be one or two words. If `voice.realtime.wakeNames` is unset, OpenClaw uses the routed agent `name` plus `OpenClaw`, falling back to the agent id plus `OpenClaw`. Wake-name gating disables realtime provider auto-response, routes accepted turns through the OpenClaw agent consult path, and gives a short spoken acknowledgement when a leading wake name is recognized from partial transcription before the final transcript arrives.
- The OpenAI realtime provider accepts current Realtime 2 event names and legacy Codex-compatible aliases for output audio and transcript events, so compatible provider snapshots can drift without dropping assistant audio.
- `voice.realtime.bargeIn` controls whether Discord speaker-start events interrupt active realtime playback. If unset, it follows the realtime provider's input-audio interruption setting.
- `voice.realtime.minBargeInAudioEndMs` controls the minimum assistant playback duration before an OpenAI realtime barge-in truncates audio. Default: `250`. Set `0` for immediate interruption in low-echo rooms, or raise it for echo-heavy speaker setups.
@@ -1247,12 +1247,12 @@ Notes:
- `voice.allowedChannels` is an optional residency allowlist. Leave it unset to allow `/vc join` into any authorized Discord voice channel. When set, `/vc join`, startup auto-join, and bot voice-state moves are restricted to the listed `{ guildId, channelId }` entries. Set it to an empty array to deny all Discord voice joins. If Discord moves the bot outside the allowlist, OpenClaw leaves that channel and rejoins the configured auto-join target when one is available.
- `voice.daveEncryption` and `voice.decryptionFailureTolerance` pass through to `@discordjs/voice` join options.
- `@discordjs/voice` defaults are `daveEncryption=true` and `decryptionFailureTolerance=24` if unset.
- OpenClaw defaults to the pure-JS `opusscript` decoder for Discord voice receive. The optional native `@discordjs/opus` package is ignored by the repo pnpm install policy so normal installs, Docker lanes, and unrelated tests do not compile a native addon. Dedicated voice-performance hosts can opt in with `OPENCLAW_DISCORD_OPUS_DECODER=native` after installing the native addon.
- OpenClaw uses the bundled `libopus-wasm` codec for Discord voice receive and realtime raw PCM playback. It ships a pinned libopus WebAssembly build and does not require native opus addons.
- `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`.
- In `stt-tts` mode, voice playback does not stop just because another user starts speaking. To avoid feedback loops, OpenClaw ignores new voice capture while TTS is playing; speak after playback finishes for the next turn. Realtime modes forward speaker starts as barge-in signals to the realtime provider.
- In realtime modes, echo from speakers into an open mic can look like barge-in and interrupt playback. For echo-heavy Discord rooms, set `voice.realtime.providers.openai.interruptResponseOnInputAudio: false` to keep OpenAI from auto-interrupting on input audio. Add `voice.realtime.bargeIn: true` if you still want Discord speaker-start events to interrupt active playback. The OpenAI realtime bridge ignores playback truncations shorter than `voice.realtime.minBargeInAudioEndMs` as likely echo/noise and logs them as skipped instead of clearing Discord playback.
- `voice.captureSilenceGraceMs` controls how long OpenClaw waits after Discord reports a speaker has stopped before finalizing that audio segment for STT. Default: `2500`; raise this if Discord splits normal pauses into choppy partial transcripts.
- `voice.captureSilenceGraceMs` controls how long OpenClaw waits after Discord reports a speaker has stopped before finalizing that audio segment for STT. Default: `2000`; raise this if Discord splits normal pauses into choppy partial transcripts.
- When ElevenLabs is the selected TTS provider, Discord voice playback uses streaming TTS and starts from the provider response stream. Providers without streaming support fall back to the synthesized temp-file path.
- 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.
@@ -1301,22 +1301,11 @@ Choose between the join modes:
- Use `autoJoin` for fixed-room bots that should be present even when no tracked user is in voice.
- Use `/vc join` for one-off joins or rooms where automatic voice presence would be surprising.
Native opus setup for source checkouts:
Discord voice codec:
```bash
pnpm install
mise exec node@22 -- pnpm discord:opus:install
```
Use Node 22 for the gateway when you want the upstream macOS arm64 prebuilt native addon. If you use another Node runtime, the opt-in installer may need a local `node-gyp` source-build toolchain.
After installing the native addon, start the Gateway with:
```bash
OPENCLAW_DISCORD_OPUS_DECODER=native pnpm gateway:watch
```
Verbose voice logs should show `discord voice: opus decoder: @discordjs/opus`. Without the env opt-in, or if the native addon is missing or cannot load on the host, OpenClaw logs `discord voice: opus decoder: opusscript` and keeps receiving voice through the pure-JS fallback.
- Voice receive logs show `discord voice: opus decoder: libopus-wasm`.
- Realtime playback encodes raw 48 kHz stereo PCM to Opus with the same bundled `libopus-wasm` package before handing packets to `@discordjs/voice`.
- File and provider-stream playback transcodes to raw 48 kHz stereo PCM with ffmpeg, then uses `libopus-wasm` for the Opus packet stream sent to Discord.
STT plus TTS pipeline:

View File

@@ -442,6 +442,13 @@ See [ACP Agents](/tools/acp-agents) for shared ACP binding behavior.
Each account can override fields such as `cliPath`, `dbPath`, `allowFrom`, `groupPolicy`, `mediaMaxMb`, history settings, and attachment root allowlists.
</Accordion>
<Accordion title="Direct-message history">
Set `channels.imessage.dmHistoryLimit` to seed new direct-message sessions with recent decoded `imsg` history for that conversation. Use `channels.imessage.dms["<sender>"].historyLimit` for per-sender overrides, including `0` to disable history for a sender.
iMessage DM history is fetched on demand from `imsg`. Leaving `dmHistoryLimit` unset disables global DM history seeding, but a positive per-sender `channels.imessage.dms["<sender>"].historyLimit` still enables seeding for that sender.
</Accordion>
</AccordionGroup>
## Media, chunking, and delivery targets

View File

@@ -306,6 +306,21 @@ Config:
- `minimal`/`extensive` enables agent reactions and sets the guidance level.
- Per-account overrides: `channels.signal.accounts.<id>.actions.reactions`, `channels.signal.accounts.<id>.reactionLevel`.
## Approval reactions
Signal exec and plugin approval prompts use the top-level `approvals.exec` and
`approvals.plugin` routing blocks. Signal does not have a
`channels.signal.execApprovals` block.
- `👍` approves once.
- `👎` denies.
- Use `/approve <id> allow-always` when a request offers persistent approval.
Approval reaction resolution requires explicit Signal approvers from
`channels.signal.allowFrom`, `channels.signal.defaultTo`, or the matching account-level fields.
Direct same-chat exec approval prompts can still suppress the duplicate local `/approve` fallback
without explicit approvers; no-approver group approvals keep the local fallback visible.
## Delivery targets (CLI/cron)
- DMs: `signal:+15551234567` (or plain E.164).

View File

@@ -522,6 +522,7 @@ Ack reactions are gated by `reactionLevel` — they are suppressed when `reactio
Behavior notes:
- sent immediately after inbound is accepted (pre-reply)
- if `ackReaction` is present without `emoji`, WhatsApp uses the routed agent's identity emoji, falling back to "👀"; omit `ackReaction` or set `emoji: ""` to send no ack reaction
- failures are logged but do not block normal reply delivery
- group mode `mentions` reacts on mention-triggered turns; group activation `always` acts as bypass for this check
- WhatsApp uses `channels.whatsapp.ackReaction` (legacy `messages.ackReaction` is not used here)
@@ -548,6 +549,7 @@ Set `messages.statusReactions.enabled: true` to let WhatsApp replace the ack rea
Behavior notes:
- `channels.whatsapp.ackReaction` still controls whether status reactions are eligible for direct messages and groups.
- The queued status reaction uses the same effective ack emoji as plain ack reactions.
- WhatsApp has one bot reaction slot per message, so lifecycle updates replace the current reaction in place.
- `messages.removeAckAfterReply: true` clears the final status reaction after the configured done/error hold.
- Tool emoji categories include `tool`, `coding`, `web`, `deploy`, `build`, and `concierge`.

View File

@@ -43,7 +43,7 @@ OpenClaw CI runs on every push to `main` and every pull request. The `preflight`
GitHub may mark superseded jobs as `cancelled` when a newer push lands on the same PR or `main` ref. Treat that as CI noise unless the newest run for the same ref is also failing. Matrix jobs use `fail-fast: false`, and `build-artifacts` reports embedded channel, core-support-boundary, and gateway-watch failures directly instead of queuing tiny verifier jobs. The automatic CI concurrency key is versioned (`CI-v7-*`) so a GitHub-side zombie in an old queue group cannot indefinitely block newer main runs. Manual full-suite runs use `CI-manual-v1-*` and do not cancel in-progress runs.
The `ci-timings-summary` job uploads a compact `ci-timings-summary` artifact for each non-draft CI run. It records wall time, queue time, slowest jobs, and failed jobs for the current run, so CI health checks do not need to scrape the full Actions payload repeatedly.
The `ci-timings-summary` job uploads a compact `ci-timings-summary` artifact for each non-draft CI run. It records wall time, queue time, slowest jobs, and failed jobs for the current run, so CI health checks do not need to scrape the full Actions payload repeatedly. The `build-artifacts` job also runs the blocking startup-memory smoke and uploads a `startup-memory` artifact with per-command RSS values for `--help`, `status --json`, and `gateway status`.
## Real behavior proof
@@ -157,6 +157,8 @@ node scripts/ci-run-timings.mjs --latest-main # ignore issue/comment noise and c
node scripts/ci-run-timings.mjs --recent 10 # compare recent successful main CI runs
pnpm test:perf:groups --full-suite --allow-failures --output .artifacts/test-perf/baseline-before.json
pnpm test:perf:groups:compare .artifacts/test-perf/baseline-before.json .artifacts/test-perf/after-agent.json
pnpm test:startup:memory
pnpm test:extensions:memory -- --json .artifacts/openclaw-performance/source/mock-provider/extension-memory.json
pnpm perf:kova:summary --report .artifacts/kova/reports/mock-provider/report.json --output .artifacts/kova/summary.md
```
@@ -178,7 +180,7 @@ The workflow installs OCM from a pinned release and Kova from `openclaw/Kova` at
- `mock-deep-profile`: CPU/heap/trace profiling for startup, gateway, and agent-turn hotspots.
- `live-openai-candidate`: a real OpenAI `openai/gpt-5.5` agent turn, skipped when `OPENAI_API_KEY` is unavailable.
The mock-provider lane also runs OpenClaw-native source probes after the Kova pass: gateway boot timing and memory across default, hook, and 50-plugin startup cases; repeated mock-OpenAI `channel-chat-baseline` hello loops; and CLI startup commands against the booted gateway. The source probe Markdown summary lives at `source/index.md` in the report bundle, with raw JSON beside it.
The mock-provider lane also runs OpenClaw-native source probes after the Kova pass: gateway boot timing and memory across default, hook, and 50-plugin startup cases; bundled plugin import RSS, repeated mock-OpenAI `channel-chat-baseline` hello loops, and CLI startup commands against the booted gateway. When the previous published mock-provider source report is available for the tested ref, the source summary compares current RSS and heap values against that baseline and marks large RSS increases as `watch`. The source probe Markdown summary lives at `source/index.md` in the report bundle, with raw JSON beside it.
Every lane uploads GitHub artifacts. When `CLAWGRIT_REPORTS_TOKEN` is configured, the workflow also commits `report.json`, `report.md`, bundles, `index.md`, and source-probe artifacts into `openclaw/clawgrit-reports` under `openclaw-performance/<tested-ref>/<run-id>-<attempt>/<lane>/`. The current tested-ref pointer is written as `openclaw-performance/<tested-ref>/latest-<lane>.json`.
@@ -503,7 +505,7 @@ The `Docs Agent` workflow is an event-driven Codex maintenance lane for keeping
### Test Performance Agent
The `Test Performance Agent` workflow is an event-driven Codex maintenance lane for slow tests. It has no pure schedule: a successful non-bot push CI run on `main` can trigger it, but it skips if another workflow-run invocation already ran or is running that UTC day. Manual dispatch bypasses that daily activity gate. The lane builds a full-suite grouped Vitest performance report, lets Codex make only small coverage-preserving test performance fixes instead of broad refactors, then reruns the full-suite report and rejects changes that reduce the passing baseline test count. If the baseline has failing tests, Codex may fix only obvious failures and the after-agent full-suite report must pass before anything is committed. When `main` advances before the bot push lands, the lane rebases the validated patch, reruns `pnpm check:changed`, and retries the push; conflicting stale patches are skipped. It uses GitHub-hosted Ubuntu so the Codex action can keep the same drop-sudo safety posture as the docs agent.
The `Test Performance Agent` workflow is an event-driven Codex maintenance lane for slow tests. It has no pure schedule: a successful non-bot push CI run on `main` can trigger it, but it skips if another workflow-run invocation already ran or is running that UTC day. Manual dispatch bypasses that daily activity gate. The lane builds a full-suite grouped Vitest performance report, lets Codex make only small coverage-preserving test performance fixes instead of broad refactors, then reruns the full-suite report and rejects changes that reduce the passing baseline test count. The grouped report records per-config wall time and max RSS on Linux and macOS, so the before/after comparison surfaces test memory deltas beside duration deltas. If the baseline has failing tests, Codex may fix only obvious failures and the after-agent full-suite report must pass before anything is committed. When `main` advances before the bot push lands, the lane rebases the validated patch, reruns `pnpm check:changed`, and retries the push; conflicting stale patches are skipped. It uses GitHub-hosted Ubuntu so the Codex action can keep the same drop-sudo safety posture as the docs agent.
### Duplicate PRs After Merge
@@ -574,7 +576,7 @@ pnpm crabbox:run -- --provider blacksmith-testbox \
--ttl 240m \
--timing-json \
--shell -- \
"env CI=1 NODE_OPTIONS=--max-old-space-size=4096 OPENCLAW_TEST_PROJECTS_PARALLEL=6 OPENCLAW_VITEST_MAX_WORKERS=1 OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=900000 pnpm check:changed"
"corepack pnpm check:changed"
```
Focused test rerun:
@@ -589,7 +591,7 @@ pnpm crabbox:run -- --provider blacksmith-testbox \
--ttl 240m \
--timing-json \
--shell -- \
"env CI=1 NODE_OPTIONS=--max-old-space-size=4096 OPENCLAW_VITEST_MAX_WORKERS=1 OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=900000 pnpm test <path-or-filter>"
"corepack pnpm test <path-or-filter>"
```
Full suite:
@@ -604,7 +606,7 @@ pnpm crabbox:run -- --provider blacksmith-testbox \
--ttl 240m \
--timing-json \
--shell -- \
"env CI=1 NODE_OPTIONS=--max-old-space-size=4096 OPENCLAW_TEST_PROJECTS_PARALLEL=6 OPENCLAW_VITEST_MAX_WORKERS=1 OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=900000 pnpm test"
"corepack pnpm test"
```
Read the final JSON summary. The useful fields are `provider`, `leaseId`, `syncDelegated`, `exitCode`, `commandMs`, and `totalMs`. One-shot Blacksmith-backed Crabbox runs should stop the Testbox automatically; if a run is interrupted or cleanup is unclear, inspect live boxes and stop only the boxes you created:
@@ -639,7 +641,7 @@ Escalate to owned Crabbox capacity only when Blacksmith is down, quota-limited,
CRABBOX_CAPACITY_REGIONS=eu-west-1,eu-west-2,eu-central-1,us-east-1,us-west-2 \
pnpm crabbox:warmup -- --provider aws --class standard --market on-demand --idle-timeout 90m
pnpm crabbox:hydrate -- --id <cbx_id-or-slug>
pnpm crabbox:run -- --id <cbx_id-or-slug> --timing-json --shell -- "env NODE_OPTIONS=--max-old-space-size=4096 OPENCLAW_TEST_PROJECTS_PARALLEL=6 OPENCLAW_VITEST_MAX_WORKERS=1 OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=900000 pnpm check:changed"
pnpm crabbox:run -- --id <cbx_id-or-slug> --timing-json --shell -- "pnpm check:changed"
pnpm crabbox:stop -- <cbx_id-or-slug>
```

View File

@@ -30,12 +30,12 @@ Use the setup commands by intent:
| Models and inference | [`models`](/cli/models) · [`infer`](/cli/infer) · `capability` (alias for [`infer`](/cli/infer)) · [`memory`](/cli/memory) · [`commitments`](/cli/commitments) · [`wiki`](/cli/wiki) |
| Network and nodes | [`directory`](/cli/directory) · [`nodes`](/cli/nodes) · [`devices`](/cli/devices) · [`node`](/cli/node) |
| Runtime and sandbox | [`approvals`](/cli/approvals) · `exec-policy` (see [`approvals`](/cli/approvals)) · [`sandbox`](/cli/sandbox) · [`tui`](/cli/tui) · `chat`/`terminal` (aliases for [`tui --local`](/cli/tui)) · [`browser`](/cli/browser) |
| Automation | [`cron`](/cli/cron) · [`tasks`](/cli/tasks) · [`hooks`](/cli/hooks) · [`webhooks`](/cli/webhooks) |
| Automation | [`cron`](/cli/cron) · [`tasks`](/cli/tasks) · [`hooks`](/cli/hooks) · [`webhooks`](/cli/webhooks) · [`transcripts`](/cli/transcripts) |
| Discovery and docs | [`dns`](/cli/dns) · [`docs`](/cli/docs) |
| Pairing and channels | [`pairing`](/cli/pairing) · [`qr`](/cli/qr) · [`channels`](/cli/channels) |
| Security and plugins | [`security`](/cli/security) · [`secrets`](/cli/secrets) · [`skills`](/cli/skills) · [`plugins`](/cli/plugins) · [`proxy`](/cli/proxy) |
| Legacy aliases | [`daemon`](/cli/daemon) (gateway service) · [`clawbot`](/cli/clawbot) (namespace) |
| Plugins (optional) | [`meeting-notes`](/cli/meeting-notes) · [`path`](/cli/path) · [`policy`](/cli/policy) · [`voicecall`](/cli/voicecall) (if installed) |
| Plugins (optional) | [`path`](/cli/path) · [`policy`](/cli/policy) · [`voicecall`](/cli/voicecall) (if installed) |
## Global flags
@@ -128,7 +128,7 @@ openclaw [--dev] [--profile <name>] <command>
status
index
search
meeting-notes
transcripts
list
show
path

View File

@@ -172,6 +172,22 @@ present in `policy.jsonc`. The observed state is existing OpenClaw config or
workspace metadata; policy reports drift but does not rewrite runtime behavior
unless a repair path is explicitly available and enabled.
Agent-specific policy overlays keep broad `tools.*` and `agents.workspace`
posture global, then let named scope blocks add stricter normal policy sections
for explicit `agentIds` under `scopes.<scopeName>`. The initial scoped
sections are `tools` and `agents.workspace`; sandbox and ingress can use the
same container once their evidence is attributable to an agent. Scoped fields
carry strictness metadata such as allowlist subset, denylist superset, required
boolean, and exact-list semantics so future policy-file conformance can reuse
the same rule inventory instead of guessing. The overlay is additive: global
claims still run, and a scoped claim can emit its own finding against the same
observed config. See [Agent-scoped policy overlays](/plan/policy-agent-scoped-overlays).
Every scope present in `policy.jsonc` must be valid and enforceable. Scopes
currently require `agentIds`, and that selector supports only `tools.*` and
`agents.workspace.*`. If an `agentIds` entry is not present in `agents.list[]`,
the scoped rule is evaluated against the inherited global/default posture for
that runtime agent id instead of being skipped.
#### Channels
| Policy field | Observed state | Use when |
@@ -250,6 +266,7 @@ unless a repair path is explicitly available and enabled.
| `tools.exec.requireAsk` | `tools.exec.ask` and per-agent exec ask mode | Require approval posture such as `always`. |
| `tools.exec.allowHosts` | `tools.exec.host` and per-agent exec host routing | Allow only exec host routing modes such as `sandbox`. |
| `tools.elevated.allow` | `tools.elevated.enabled` and per-agent elevated posture | Set to `false` to require elevated tool mode to stay disabled. |
| `tools.alsoAllow.expected` | `tools.alsoAllow` and per-agent `tools.alsoAllow` | Require exact `alsoAllow` entries and report missing or unexpected additive tool grants. |
| `tools.denyTools` | `tools.deny` and `agents.list[].tools.deny` | Require configured tool deny lists to include tool ids or groups such as `group:runtime` and `group:fs`. |
Run policy-only checks during authoring:
@@ -533,6 +550,8 @@ Policy currently verifies:
| `policy/tools-exec-ask-unapproved` | Exec ask mode is outside the policy allowlist. |
| `policy/tools-exec-host-unapproved` | Exec host routing is outside the policy allowlist. |
| `policy/tools-elevated-enabled` | Elevated tool mode is enabled when policy denies it. |
| `policy/tools-also-allow-missing` | A configured `alsoAllow` list is missing an entry required by policy. |
| `policy/tools-also-allow-unexpected` | A configured `alsoAllow` list includes an entry not expected by policy. |
| `policy/tools-required-deny-missing` | A global or per-agent tool deny list does not include a required denied tool. |
| `policy/secrets-unmanaged-provider` | A config SecretRef references a provider not declared under `secrets.providers`. |
| `policy/secrets-denied-provider-source` | A config secret provider or SecretRef uses a source denied by policy. |

View File

@@ -1,18 +1,17 @@
---
summary: "CLI reference for `openclaw meeting-notes` (list, show, and locate stored meeting notes)"
summary: "CLI reference for `openclaw transcripts` (list, show, and locate stored transcripts)"
read_when:
- You want to read stored meeting note summaries from the terminal
- You need the path to a meeting notes markdown summary
- You are debugging the meeting-notes plugin storage layout
title: "Meeting Notes CLI"
- You want to read stored transcript summaries from the terminal
- You need the path to a transcripts markdown summary
- You are debugging the core transcripts storage layout
title: "Transcripts CLI"
---
# `openclaw meeting-notes`
# `openclaw transcripts`
Inspect meeting notes written by the external `meeting-notes` plugin. This CLI
is read-only and is available when that plugin is installed or loaded from
source. Capture, import, and summarization are owned by the `meeting_notes`
agent tool and by configured auto-start sources.
Inspect transcripts written by OpenClaw's core `transcripts` tool. This CLI is
read-only; capture, import, and summarization are owned by the agent tool and
configured auto-start sources.
Use the CLI when you want to find yesterday's notes, open the Markdown file in
an editor, feed a transcript to another tool, or debug where a session landed on
@@ -21,7 +20,7 @@ disk. It does not start or stop capture.
Artifacts live under the OpenClaw state directory:
```text
$OPENCLAW_STATE_DIR/meeting-notes/YYYY-MM-DD/<session>/
$OPENCLAW_STATE_DIR/transcripts/YYYY-MM-DD/<session>/
metadata.json
transcript.jsonl
summary.json
@@ -35,17 +34,17 @@ session directory is a safe filesystem segment derived from the session id.
## Commands
```bash
openclaw meeting-notes list
openclaw meeting-notes show <session>
openclaw meeting-notes show YYYY-MM-DD/<session>
openclaw meeting-notes path <session>
openclaw meeting-notes path YYYY-MM-DD/<session>
openclaw meeting-notes path <session> --dir
openclaw meeting-notes path <session> --metadata
openclaw meeting-notes path <session> --transcript
openclaw meeting-notes list --json
openclaw meeting-notes show <session> --json
openclaw meeting-notes path <session> --json
openclaw transcripts list
openclaw transcripts show <session>
openclaw transcripts show YYYY-MM-DD/<session>
openclaw transcripts path <session>
openclaw transcripts path YYYY-MM-DD/<session>
openclaw transcripts path <session> --dir
openclaw transcripts path <session> --metadata
openclaw transcripts path <session> --transcript
openclaw transcripts list --json
openclaw transcripts show <session> --json
openclaw transcripts path <session> --json
```
- `list`: list stored sessions, date-qualified selector, start time, title, and `summary.md` path.
@@ -57,7 +56,7 @@ openclaw meeting-notes path <session> --json
- `--json`: print machine-readable output.
When a human session id repeats across days, use the date-qualified selector
from `list`, for example `openclaw meeting-notes show 2026-05-22/standup`.
from `list`, for example `openclaw transcripts show 2026-05-22/standup`.
Default session ids include a timestamp and random suffix; configure fixed
session ids only when they are unique within the day.
@@ -66,7 +65,7 @@ session ids only when they are unique within the day.
`list` prints one session per line:
```text
2026-05-22/standup 2026-05-22T09:00:00.000Z Weekly standup /Users/alex/.openclaw/meeting-notes/2026-05-22/standup/summary.md
2026-05-22/standup 2026-05-22T09:00:00.000Z Weekly standup /Users/alex/.openclaw/transcripts/2026-05-22/standup/summary.md
```
The output is tab-separated. The columns are selector, start time, title, and
@@ -91,13 +90,13 @@ and whether that file exists.
## Many meetings per day
Meeting Notes groups sessions by date, then by session id. Ten meetings on one
Transcripts groups sessions by date, then by session id. Ten meetings on one
day become ten sibling folders:
```text
~/.openclaw/meeting-notes/2026-05-22/
meeting-2026-05-22T09-00-00-000Z-a1b2c3d4/
meeting-2026-05-22T10-30-00-000Z-b2c3d4e5/
~/.openclaw/transcripts/2026-05-22/
transcript-2026-05-22T09-00-00-000Z-a1b2c3d4/
transcript-2026-05-22T10-30-00-000Z-b2c3d4e5/
standup/
```
@@ -112,7 +111,41 @@ write `summary.md` immediately after import. A session can still appear in
or metadata was written before any utterances arrived.
Use `path <session> --transcript` to inspect the append-only transcript, and use
the `meeting_notes` tool action `summarize` to regenerate the Markdown summary.
the `transcripts` tool action `summarize` to regenerate the Markdown summary.
See [Meeting Notes](/plugins/meeting-notes) for configuration, auto-start, and
source-provider details.
## Configuration
Transcript capture is opt-in because live sources can join and record meeting
audio. Enable the tool with top-level `transcripts.enabled`:
```json
{
"transcripts": {
"enabled": true,
"maxUtterances": 2000
}
}
```
Configure auto-start sources with `transcripts.autoStart` in `openclaw.json`.
Each entry is enabled by being present; omit an entry to disable that source.
```json
{
"transcripts": {
"enabled": true,
"autoStart": [
{
"providerId": "discord-voice",
"guildId": "1234567890",
"channelId": "2345678901"
},
{
"providerId": "slack-huddle",
"accountId": "workspace",
"channelId": "C123"
}
]
}
}
```

View File

@@ -106,14 +106,14 @@ The byte guard requires `truncateAfterCompaction: true`. Without transcript rota
### Successor transcripts
When `agents.defaults.compaction.truncateAfterCompaction` is enabled, OpenClaw does not rewrite the existing transcript in place. It creates a new active successor transcript from the compaction summary, preserved state, and unsummarized tail, then keeps the previous JSONL as the archived checkpoint source.
When `agents.defaults.compaction.truncateAfterCompaction` is enabled, OpenClaw does not rewrite the existing transcript in place. It creates a new active successor transcript from the compaction summary, preserved state, and unsummarized tail, then records checkpoint metadata that points branch/restore flows at that compacted successor.
Successor transcripts also drop exact duplicate long user turns that arrive
inside a short retry window, so channel retry storms are not carried into the
next active transcript after compaction.
Pre-compaction checkpoints are retained only while they stay below OpenClaw's
checkpoint size cap; oversized active transcripts still compact, but OpenClaw
skips the large debug snapshot instead of doubling disk usage.
OpenClaw no longer writes separate `.checkpoint.*.jsonl` copies for new
compactions. Existing legacy checkpoint files can still be used while referenced
and are pruned by normal session cleanup.
### Compaction notices

View File

@@ -99,7 +99,7 @@ OpenClaw ships with the pi-ai catalog. These providers require **no** `models.pr
- Use `params.serviceTier` when you want an explicit tier instead of the shared `/fast` toggle
- Hidden OpenClaw attribution headers (`originator`, `version`, `User-Agent`) apply only on native OpenAI traffic to `api.openai.com`, not generic OpenAI-compatible proxies
- Native OpenAI routes also keep Responses `store`, prompt-cache hints, and OpenAI reasoning-compat payload shaping; proxy routes do not
- `openai/gpt-5.3-codex-spark` is intentionally suppressed in OpenClaw because live OpenAI API requests reject it and the current Codex catalog does not expose it
- `openai/gpt-5.3-codex-spark` is intentionally suppressed in OpenClaw because live OpenAI API requests reject it; use `openai-codex/gpt-5.3-codex-spark` only when the Codex catalog exposes it for your account
```json5
{
@@ -150,6 +150,7 @@ Anthropic staff told us OpenClaw-style Claude CLI usage is allowed again, so Ope
- For the common subscription plus native Codex runtime route, sign in with `openai-codex` auth but configure `openai/gpt-5.5`; OpenAI agent turns select Codex by default.
- Use provider/model `agentRuntime.id: "pi"` only when you want a compatibility route through PI; otherwise keep `openai/gpt-5.5` on the default Codex harness.
- `openai-codex/gpt-*` refs remain a legacy PI route. Prefer `openai/gpt-5.5` on the native Codex runtime for new agent config, and run `openclaw doctor --fix` when you want to migrate old `openai-codex/*` refs to canonical `openai/*` refs.
- `openai-codex/gpt-5.3-codex-spark` remains available only through Codex catalog discovery when the signed-in account advertises it; direct `openai/*` and Azure refs for that model stay suppressed.
```json5
{

View File

@@ -93,13 +93,26 @@ That script starts a local OTLP/HTTP receiver, runs the `otel-trace-smoke` QA
scenario with the `diagnostics-otel` plugin enabled, then asserts traces,
metrics, and logs are exported. It decodes the exported protobuf trace spans
and checks the release-critical shape:
`openclaw.run`, `openclaw.harness.run`, `openclaw.model.call`,
`openclaw.context.assembled`, and `openclaw.message.delivery` must be present;
`openclaw.run`, `openclaw.harness.run`, a latest GenAI semantic-convention
model-call span, `openclaw.context.assembled`, and `openclaw.message.delivery`
must be present. The smoke forces
`OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_latest_experimental`, so the model-call
span must use the `{gen_ai.operation.name} {gen_ai.request.model}` name;
model calls must not export `StreamAbandoned` on successful turns; raw diagnostic IDs and
`openclaw.content.*` attributes must stay out of the trace. The raw OTLP
payloads must not contain the prompt sentinel, response sentinel, or QA session
key. It writes `otel-smoke-summary.json` next to the QA suite artifacts.
For a collector-backed OpenTelemetry smoke, run:
```bash
pnpm qa:otel:collector-smoke
```
That lane puts a real OpenTelemetry Collector Docker container in front of the
same local receiver. Use it when changing endpoint wiring, collector
compatibility, or OTLP export behavior that the in-process receiver could mask.
For the protected Prometheus scrape smoke, run:
```bash
@@ -118,6 +131,13 @@ To run both observability smokes back to back, use:
pnpm qa:observability:smoke
```
For the collector-backed OpenTelemetry lane plus the protected Prometheus scrape
smoke, use:
```bash
pnpm qa:observability:collector-smoke
```
Observability QA stays source-checkout only. The npm tarball intentionally omits
QA Lab, so package Docker release lanes do not run `qa` commands. Use
`pnpm qa:otel:smoke`, `pnpm qa:prometheus:smoke`, or

View File

@@ -49,10 +49,12 @@ effective tool list.
token counts, and timestamps. Filter by kind (`main`, `group`, `cron`, `hook`,
`node`), exact `label`, exact `agentId`, search text, or recency
(`activeMinutes`). When you need mailbox-style triage, it can also ask for a
visibility-scoped derived title, a last-message preview snippet, or bounded
recent messages on each row. Derived titles and previews are produced only for
sessions the caller can already see under the configured session tool
visibility policy, so unrelated sessions stay hidden.
visibility-scoped derived title, a last-message preview snippet, or bounded recent
messages on each row. Derived titles and previews are produced only for sessions
the caller can already see under the configured session tool visibility policy, so
unrelated sessions stay hidden. When visibility is restricted, `sessions_list`
returns optional `visibility` metadata showing the effective mode and a warning that
results may be scope-limited.
`sessions_history` fetches the conversation transcript for a specific session.
By default, tool results are excluded -- pass `includeTools: true` to see them.

View File

@@ -50,6 +50,50 @@ Disable all flags:
OPENCLAW_DIAGNOSTICS=0
```
`OPENCLAW_DIAGNOSTICS=0` is a process-level disable override: it disables
flags from both env and config for that process.
## Profiling flags
Profiler flags enable targeted timing spans without raising global logging
levels. They are disabled by default.
Enable all profiler-gated spans for one gateway run:
```bash
OPENCLAW_DIAGNOSTICS=profiler openclaw gateway run
```
Enable only reply-dispatch profiler spans:
```bash
OPENCLAW_DIAGNOSTICS=reply.profiler openclaw gateway run
```
Enable only Codex app-server startup/tool/thread profiler spans:
```bash
OPENCLAW_DIAGNOSTICS=codex.profiler openclaw gateway run
```
Enable profiler flags from config:
```json
{
"diagnostics": {
"flags": ["reply.profiler", "codex.profiler"]
}
}
```
Restart the gateway after changing config flags. To disable a profiler flag,
remove it from `diagnostics.flags` and restart. To temporarily disable every
diagnostics flag even when config enables profiler flags, start the process with:
```bash
OPENCLAW_DIAGNOSTICS=0 openclaw gateway run
```
## Timeline artifacts
The `timeline` flag writes structured startup and runtime timing events for

View File

@@ -1212,7 +1212,6 @@
"plugins/codex-native-plugins",
"plugins/codex-computer-use",
"plugins/google-meet",
"plugins/meeting-notes",
"plugins/webhooks",
"plugins/admin-http-rpc",
"plugins/voice-call",

View File

@@ -170,13 +170,15 @@ the prompt. Skill env/API key overrides are still applied by OpenClaw to the
child process environment for the run.
Claude CLI also has its own noninteractive permission mode. OpenClaw maps that
to the existing exec policy instead of adding Claude-specific config: when the
effective requested exec policy is YOLO (`tools.exec.security: "full"` and
`tools.exec.ask: "off"`), OpenClaw adds `--permission-mode bypassPermissions`.
Per-agent `agents.list[].tools.exec` settings override global `tools.exec` for
that agent. To force a different Claude mode, set explicit raw backend args
such as `--permission-mode default` or `--permission-mode acceptEdits` under
`agents.defaults.cliBackends.claude-cli.args` and matching `resumeArgs`.
to the existing exec policy instead of adding Claude-specific policy config.
For OpenClaw-managed Claude live sessions, the effective OpenClaw exec policy is
authoritative: YOLO (`tools.exec.security: "full"` and
`tools.exec.ask: "off"`) launches Claude with
`--permission-mode bypassPermissions`, while restrictive effective exec policy
launches Claude with `--permission-mode default`. Per-agent
`agents.list[].tools.exec` settings override global `tools.exec` for that
agent. Raw Claude backend args may still include `--permission-mode`, but live
Claude launches normalize that flag to match the effective OpenClaw exec policy.
The bundled Anthropic `claude-cli` backend also maps OpenClaw `/think` levels
to Claude Code's native `--effort` flag for non-off levels. `minimal` and

View File

@@ -235,7 +235,6 @@ Shared defaults for bounded runtime context surfaces.
contextLimits: {
memoryGetMaxChars: 12000,
memoryGetDefaultLines: 120,
toolResultMaxChars: 16000,
postCompactionMaxChars: 1800,
},
},
@@ -247,8 +246,12 @@ Shared defaults for bounded runtime context surfaces.
metadata and continuation notice are added.
- `memoryGetDefaultLines`: default `memory_get` line window when `lines` is
omitted.
- `toolResultMaxChars`: live tool-result cap used for persisted results and
overflow recovery.
- `toolResultMaxChars`: advanced live tool-result ceiling used for persisted
results and overflow recovery. Leave unset for the model-context auto cap:
`16000` chars below 100K tokens, `32000` chars at 100K+ tokens, and `64000`
chars at 200K+ tokens. The effective cap is still limited to about 30% of the
model context window. `openclaw doctor --deep` prints the effective cap, and
doctor warns only when an explicit override is stale or has no effect.
- `postCompactionMaxChars`: AGENTS.md excerpt cap used during post-compaction
refresh injection.
@@ -263,7 +266,6 @@ from `agents.defaults.contextLimits`.
defaults: {
contextLimits: {
memoryGetMaxChars: 12000,
toolResultMaxChars: 16000,
},
},
list: [
@@ -271,7 +273,7 @@ from `agents.defaults.contextLimits`.
id: "tiny-local",
contextLimits: {
memoryGetMaxChars: 6000,
toolResultMaxChars: 8000,
toolResultMaxChars: 8000, // advanced ceiling for this agent
},
},
],
@@ -503,16 +505,16 @@ Time format in system prompt. Default: `auto` (OS preference).
**Built-in alias shorthands** (only apply when the model is in `agents.defaults.models`):
| Alias | Model |
| ------------------- | -------------------------------------- |
| `opus` | `anthropic/claude-opus-4-6` |
| `sonnet` | `anthropic/claude-sonnet-4-6` |
| `gpt` | `openai/gpt-5.5` |
| `gpt-mini` | `openai/gpt-5.4-mini` |
| `gpt-nano` | `openai/gpt-5.4-nano` |
| `gemini` | `google/gemini-3.1-pro-preview` |
| `gemini-flash` | `google/gemini-3-flash-preview` |
| `gemini-flash-lite` | `google/gemini-3.1-flash-lite-preview` |
| Alias | Model |
| ------------------- | ------------------------------- |
| `opus` | `anthropic/claude-opus-4-6` |
| `sonnet` | `anthropic/claude-sonnet-4-6` |
| `gpt` | `openai/gpt-5.5` |
| `gpt-mini` | `openai/gpt-5.4-mini` |
| `gpt-nano` | `openai/gpt-5.4-nano` |
| `gemini` | `google/gemini-3.1-pro-preview` |
| `gemini-flash` | `google/gemini-3-flash-preview` |
| `gemini-flash-lite` | `google/gemini-3.1-flash-lite` |
Your configured aliases always win over defaults.
@@ -575,7 +577,7 @@ Replace the entire OpenClaw-assembled system prompt with a fixed string. Set at
### `agents.defaults.promptOverlays`
Provider-independent prompt overlays applied by model family on OpenClaw-assembled prompt surfaces. GPT-5-family model ids receive the shared behavior contract across PI/provider routes; `personality` controls only the friendly interaction-style layer. Native Codex app-server routes keep Codex-owned base/model/personality instructions instead of this OpenClaw GPT-5 overlay.
Provider-independent prompt overlays applied by model family on OpenClaw-assembled prompt surfaces. GPT-5-family model ids receive the shared behavior contract across PI/provider routes; `personality` controls only the friendly interaction-style layer. Native Codex app-server routes keep Codex-owned base/model instructions instead of this OpenClaw GPT-5 overlay, and OpenClaw disables Codex's built-in personality for native threads.
```json5
{

View File

@@ -357,6 +357,9 @@ Default: `tree` (current session + sessions spawned by it, such as subagents).
- `agent`: any session belonging to the current agent id (can include other users if you run per-sender sessions under the same agent id).
- `all`: any session. Cross-agent targeting still requires `tools.agentToAgent`.
- Sandbox clamp: when the current session is sandboxed and `agents.defaults.sandbox.sessionToolsVisibility="spawned"`, visibility is forced to `tree` even if `tools.sessions.visibility="all"`.
- When not `all`, `sessions_list` includes a compact `visibility` field
describing the effective mode and a warning that some sessions may be
omitted outside the current scope.
</Accordion>
</AccordionGroup>

View File

@@ -385,7 +385,7 @@ Save to `~/.openclaw/openclaw.json` and you can DM the bot from that number.
cron: {
enabled: true,
store: "~/.openclaw/cron/cron.json",
maxConcurrentRuns: 2, // cron dispatch + isolated cron agent-turn execution
maxConcurrentRuns: 8, // default; cron dispatch + isolated cron agent-turn execution
sessionRetention: "24h",
runLog: {
maxBytes: "2mb",

View File

@@ -1052,6 +1052,7 @@ Notes:
toolInputs: false,
toolOutputs: false,
systemPrompt: false,
toolDefinitions: false,
},
},
@@ -1080,8 +1081,8 @@ Notes:
- `otel.traces` / `otel.metrics` / `otel.logs`: enable trace, metrics, or log export.
- `otel.sampleRate`: trace sampling rate `0`-`1`.
- `otel.flushIntervalMs`: periodic telemetry flush interval in ms.
- `otel.captureContent`: opt-in raw content capture for OTEL span attributes. Defaults to off. Boolean `true` captures non-system message/tool content; the object form lets you enable `inputMessages`, `outputMessages`, `toolInputs`, `toolOutputs`, and `systemPrompt` explicitly.
- `OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_latest_experimental`: environment toggle for latest experimental GenAI span provider attributes. By default spans keep the legacy `gen_ai.system` attribute for compatibility; GenAI metrics use bounded semantic attributes.
- `otel.captureContent`: opt-in raw content capture for OTEL span attributes. Defaults to off. Boolean `true` captures non-system message/tool content; the object form lets you enable `inputMessages`, `outputMessages`, `toolInputs`, `toolOutputs`, `systemPrompt`, and `toolDefinitions` explicitly.
- `OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_latest_experimental`: environment toggle for latest experimental GenAI inference span shape, including `{gen_ai.operation.name} {gen_ai.request.model}` span names, `CLIENT` span kind, and `gen_ai.provider.name` instead of legacy `gen_ai.system`. By default spans keep `openclaw.model.call` and `gen_ai.system` for compatibility; GenAI metrics use bounded semantic attributes.
- `OPENCLAW_OTEL_PRELOADED=1`: environment toggle for hosts that already registered a global OpenTelemetry SDK. OpenClaw then skips plugin-owned SDK startup/shutdown while keeping diagnostic listeners active.
- `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`, `OTEL_EXPORTER_OTLP_METRICS_ENDPOINT`, and `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT`: signal-specific endpoint env vars used when the matching config key is unset.
- `cacheTrace.enabled`: log cache trace snapshots for embedded runs (default: `false`).
@@ -1240,7 +1241,7 @@ Current builds no longer include the TCP bridge. Nodes connect over the Gateway
{
cron: {
enabled: true,
maxConcurrentRuns: 2, // cron dispatch + isolated cron agent-turn execution
maxConcurrentRuns: 8, // default; cron dispatch + isolated cron agent-turn execution
webhook: "https://example.invalid/legacy", // deprecated fallback for stored notify:true jobs
webhookToken: "replace-with-dedicated-token", // optional bearer token for outbound webhook auth
sessionRetention: "24h", // duration string or false

View File

@@ -419,7 +419,7 @@ candidate contains redacted secret placeholders such as `***`.
{
cron: {
enabled: true,
maxConcurrentRuns: 2, // cron dispatch + isolated cron agent-turn execution
maxConcurrentRuns: 8, // default; cron dispatch + isolated cron agent-turn execution
sessionRetention: "24h",
runLog: {
maxBytes: "2mb",

View File

@@ -413,7 +413,7 @@ That stages grounded durable candidates into the short-term dreaming store while
- short cooldowns (rate limits/timeouts/auth failures)
- longer disables (billing/credit failures)
Legacy Codex OAuth profiles whose tokens live in macOS Keychain (older onboarding before the file-based sidecar layout) are not picked up by the embedded runtime path — that path runs with `allowKeychainPrompt: false` and cannot trigger a Keychain prompt. Run `openclaw doctor --fix` once to migrate Keychain-backed legacy tokens inline into `auth-profiles.json`; after that, embedded turns (Telegram, cron, sub-agent dispatch) resolve them like any other inline OAuth profile.
Legacy Codex OAuth profiles whose tokens live in macOS Keychain (older onboarding before the file-based sidecar layout) are not picked up by the embedded runtime path — that path runs with `allowKeychainPrompt: false` and cannot trigger a Keychain prompt. Affected users will see a one-shot `log.warn` from the legacy sidecar loader naming `openclaw doctor --fix` and macOS Keychain (instead of the credential silently falling through to a downstream `No API key found for provider "openai-codex"`). Run `openclaw doctor --fix` once from an interactive terminal to migrate Keychain-backed legacy tokens inline into `auth-profiles.json`; after that, embedded turns (Telegram, cron, sub-agent dispatch) resolve them like any other inline OAuth profile.
</Accordion>
<Accordion title="6. Hooks model validation">

View File

@@ -219,8 +219,11 @@ Set `stream: true` to receive Server-Sent Events (SSE):
- `max_tokens`: number; legacy alias accepted for backwards compatibility. Ignored when `max_completion_tokens` is also present.
- `temperature`: number; best-effort sampling temperature forwarded to the upstream provider via the agent stream-param channel.
- `top_p`: number; best-effort nucleus sampling forwarded to the upstream provider via the agent stream-param channel.
- `frequency_penalty`: number; best-effort frequency penalty forwarded to the upstream provider via the agent stream-param channel. Validated range: -2.0 to 2.0. Returns `400 invalid_request_error` for out-of-range values.
- `presence_penalty`: number; best-effort presence penalty forwarded to the upstream provider via the agent stream-param channel. Validated range: -2.0 to 2.0. Returns `400 invalid_request_error` for out-of-range values.
- `seed`: number (integer); best-effort seed forwarded to the upstream provider via the agent stream-param channel. Returns `400 invalid_request_error` for non-integer values.
When either token-cap field is set, the value is forwarded to the upstream provider via the agent stream-param channel. The actual wire field name sent to the upstream provider is chosen by the provider transport: `max_completion_tokens` for OpenAI-family endpoints, and `max_tokens` for providers that only accept the legacy name (such as Mistral and Chutes). Sampling fields (`temperature`, `top_p`) follow the same stream-param channel; the ChatGPT-based Codex Responses backend strips them server-side since it uses fixed sampling.
When either token-cap field is set, the value is forwarded to the upstream provider via the agent stream-param channel. The actual wire field name sent to the upstream provider is chosen by the provider transport: `max_completion_tokens` for OpenAI-family endpoints, and `max_tokens` for providers that only accept the legacy name (such as Mistral and Chutes). Sampling fields (`temperature`, `top_p`, `frequency_penalty`, `presence_penalty`, `seed`) follow the same stream-param channel; the ChatGPT-based Codex Responses backend strips them server-side since it uses fixed sampling.
### Unsupported variants

View File

@@ -70,14 +70,15 @@ openclaw plugins enable diagnostics-otel
## Signals exported
| Signal | What goes in it |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Metrics** | Counters and histograms for token usage, cost, run duration, skill usage, message flow, Talk events, queue lanes, session state/recovery, tool execution, exec, and memory pressure. |
| **Traces** | Spans for model usage, model calls, harness lifecycle, skill usage, tool execution, exec, webhook/message processing, context assembly, and tool loops. |
| **Logs** | Structured `logging.file` records exported over OTLP when `diagnostics.otel.logs` is enabled; log bodies are withheld unless content capture is explicitly enabled. |
| Signal | What goes in it |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Metrics** | Counters and histograms for token usage, cost, run duration, failover, skill usage, message flow, Talk events, queue lanes, session state/recovery, tool execution, oversized payloads, exec, and memory pressure. |
| **Traces** | Spans for model usage, model calls, harness lifecycle, skill usage, tool execution, exec, webhook/message processing, context assembly, and tool loops. |
| **Logs** | Structured `logging.file` records exported over OTLP when `diagnostics.otel.logs` is enabled; log bodies are withheld unless content capture is explicitly enabled. |
Toggle `traces`, `metrics`, and `logs` independently. All three default to on
when `diagnostics.otel.enabled` is true.
Toggle `traces`, `metrics`, and `logs` independently. Traces and metrics
default to on when `diagnostics.otel.enabled` is true. Logs default to off and
are exported only when `diagnostics.otel.logs` is explicitly `true`.
## Configuration reference
@@ -106,6 +107,7 @@ when `diagnostics.otel.enabled` is true.
toolInputs: false,
toolOutputs: false,
systemPrompt: false,
toolDefinitions: false,
},
},
},
@@ -114,14 +116,14 @@ when `diagnostics.otel.enabled` is true.
### Environment variables
| Variable | Purpose |
| ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `OTEL_EXPORTER_OTLP_ENDPOINT` | Override `diagnostics.otel.endpoint`. If the value already contains `/v1/traces`, `/v1/metrics`, or `/v1/logs`, it is used as-is. |
| `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` / `OTEL_EXPORTER_OTLP_METRICS_ENDPOINT` / `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT` | Signal-specific endpoint overrides used when the matching `diagnostics.otel.*Endpoint` config key is unset. Signal-specific config wins over signal-specific env, which wins over the shared endpoint. |
| `OTEL_SERVICE_NAME` | Override `diagnostics.otel.serviceName`. |
| `OTEL_EXPORTER_OTLP_PROTOCOL` | Override the wire protocol (only `http/protobuf` is honored today). |
| `OTEL_SEMCONV_STABILITY_OPT_IN` | Set to `gen_ai_latest_experimental` to emit the latest experimental GenAI span attribute (`gen_ai.provider.name`) instead of the legacy `gen_ai.system`. GenAI metrics always use bounded, low-cardinality semantic attributes regardless. |
| `OPENCLAW_OTEL_PRELOADED` | Set to `1` when another preload or host process already registered the global OpenTelemetry SDK. The plugin then skips its own NodeSDK lifecycle but still wires diagnostic listeners and honors `traces`/`metrics`/`logs`. |
| Variable | Purpose |
| ----------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `OTEL_EXPORTER_OTLP_ENDPOINT` | Override `diagnostics.otel.endpoint`. If the value already contains `/v1/traces`, `/v1/metrics`, or `/v1/logs`, it is used as-is. |
| `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` / `OTEL_EXPORTER_OTLP_METRICS_ENDPOINT` / `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT` | Signal-specific endpoint overrides used when the matching `diagnostics.otel.*Endpoint` config key is unset. Signal-specific config wins over signal-specific env, which wins over the shared endpoint. |
| `OTEL_SERVICE_NAME` | Override `diagnostics.otel.serviceName`. |
| `OTEL_EXPORTER_OTLP_PROTOCOL` | Override the wire protocol (only `http/protobuf` is honored today). |
| `OTEL_SEMCONV_STABILITY_OPT_IN` | Set to `gen_ai_latest_experimental` to emit the latest experimental GenAI inference span shape, including `{gen_ai.operation.name} {gen_ai.request.model}` span names, `CLIENT` span kind, and `gen_ai.provider.name` instead of the legacy `gen_ai.system`. GenAI metrics always use bounded, low-cardinality semantic attributes regardless. |
| `OPENCLAW_OTEL_PRELOADED` | Set to `1` when another preload or host process already registered the global OpenTelemetry SDK. The plugin then skips its own NodeSDK lifecycle but still wires diagnostic listeners and honors `traces`/`metrics`/`logs`. |
## Privacy and content capture
@@ -152,6 +154,7 @@ text. Each subkey is opt-in independently:
- `toolInputs` - tool argument payloads.
- `toolOutputs` - tool result payloads.
- `systemPrompt` - assembled system/developer prompt.
- `toolDefinitions` - model tool names, descriptions, and schemas.
When any subkey is enabled, model and tool spans get bounded, redacted
`openclaw.content.*` attributes for that class only. Use boolean
@@ -189,6 +192,7 @@ message bodies are also approved for export.
- `openclaw.model_call.request_bytes` (histogram, UTF-8 byte size of the final model request payload; no raw payload content)
- `openclaw.model_call.response_bytes` (histogram, UTF-8 byte size of streamed model response events; no raw response content)
- `openclaw.model_call.time_to_first_byte_ms` (histogram, elapsed time before the first streamed response event)
- `openclaw.model.failover` (counter, attrs: `openclaw.provider`, `openclaw.model`, `openclaw.failover.to_provider`, `openclaw.failover.to_model`, `openclaw.failover.reason`, `openclaw.failover.suspended`, `openclaw.lane`)
- `openclaw.skill.used` (counter, attrs: `openclaw.skill.name`, `openclaw.skill.source`, `openclaw.skill.activation`, optional `openclaw.agent`, optional `openclaw.toolName`)
### Message flow
@@ -260,16 +264,31 @@ unchanged, so dashboards should alert on sustained increases rather than every
heartbeat tick. For the config knob and defaults, see
[Configuration reference](/gateway/configuration-reference#diagnostics).
Liveness warnings also emit:
- `openclaw.liveness.warning` (counter, attrs: `openclaw.liveness.reason`)
- `openclaw.liveness.event_loop_delay_p99_ms` (histogram, attrs: `openclaw.liveness.reason`)
- `openclaw.liveness.event_loop_delay_max_ms` (histogram, attrs: `openclaw.liveness.reason`)
- `openclaw.liveness.event_loop_utilization` (histogram, attrs: `openclaw.liveness.reason`)
- `openclaw.liveness.cpu_core_ratio` (histogram, attrs: `openclaw.liveness.reason`)
### Harness lifecycle
- `openclaw.harness.duration_ms` (histogram, attrs: `openclaw.harness.id`, `openclaw.harness.plugin`, `openclaw.outcome`, `openclaw.harness.phase` on errors)
### Tool execution
- `openclaw.tool.execution.duration_ms` (histogram, attrs: `gen_ai.tool.name`, `openclaw.toolName`, `openclaw.tool.source`, `openclaw.tool.owner`, `openclaw.tool.params.kind`, plus `openclaw.errorCategory` on errors)
- `openclaw.tool.execution.blocked` (counter, attrs: `gen_ai.tool.name`, `openclaw.toolName`, `openclaw.tool.source`, `openclaw.tool.owner`, `openclaw.tool.params.kind`, `openclaw.deniedReason`)
### Exec
- `openclaw.exec.duration_ms` (histogram, attrs: `openclaw.exec.target`, `openclaw.exec.mode`, `openclaw.outcome`, `openclaw.failureKind`)
### Diagnostics internals (memory and tool loop)
- `openclaw.payload.large` (counter, attrs: `openclaw.payload.surface`, `openclaw.payload.action`, `openclaw.channel`, `openclaw.plugin`, `openclaw.reason`)
- `openclaw.payload.large_bytes` (histogram, attrs: same as `openclaw.payload.large`)
- `openclaw.memory.heap_used_bytes` (histogram, attrs: `openclaw.memory.kind`)
- `openclaw.memory.rss_bytes` (histogram)
- `openclaw.memory.pressure` (counter, attrs: `openclaw.memory.level`)
@@ -291,6 +310,7 @@ heartbeat tick. For the config knob and defaults, see
- `openclaw.errorCategory` and optional `openclaw.failureKind` on errors
- `openclaw.model_call.request_bytes`, `openclaw.model_call.response_bytes`, `openclaw.model_call.time_to_first_byte_ms`
- `openclaw.provider.request_id_hash` (bounded SHA-based hash of the upstream provider request id; raw ids are not exported)
- With `OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_latest_experimental`, model-call spans use the latest GenAI inference span name `{gen_ai.operation.name} {gen_ai.request.model}` and `CLIENT` span kind instead of `openclaw.model.call`.
- `openclaw.harness.run`
- `openclaw.harness.id`, `openclaw.harness.plugin`, `openclaw.outcome`, `openclaw.provider`, `openclaw.model`, `openclaw.channel`
- On completion: `openclaw.harness.result_classification`, `openclaw.harness.yield_detected`, `openclaw.harness.items.started`, `openclaw.harness.items.completed`, `openclaw.harness.items.active`

View File

@@ -8,7 +8,7 @@ read_when:
- You want metrics without running an OpenTelemetry collector
---
OpenClaw can expose diagnostics metrics through the official `diagnostics-prometheus` plugin. It listens to trusted internal diagnostics and renders a Prometheus text endpoint at:
OpenClaw can expose diagnostics metrics through the official `diagnostics-prometheus` plugin. It listens to trusted diagnostics plus core-emitted gateway stability events, then renders a Prometheus text endpoint at:
```text
GET /api/diagnostics/prometheus
@@ -87,44 +87,59 @@ For traces, logs, OTLP push, and OpenTelemetry GenAI semantic attributes, see [O
## Metrics exported
| Metric | Type | Labels |
| --------------------------------------------- | --------- | ----------------------------------------------------------------------------------------- |
| `openclaw_run_completed_total` | counter | `channel`, `model`, `outcome`, `provider`, `trigger` |
| `openclaw_run_duration_seconds` | histogram | `channel`, `model`, `outcome`, `provider`, `trigger` |
| `openclaw_model_call_total` | counter | `api`, `error_category`, `model`, `outcome`, `provider`, `transport` |
| `openclaw_model_call_duration_seconds` | histogram | `api`, `error_category`, `model`, `outcome`, `provider`, `transport` |
| `openclaw_model_tokens_total` | counter | `agent`, `channel`, `model`, `provider`, `token_type` |
| `openclaw_gen_ai_client_token_usage` | histogram | `model`, `provider`, `token_type` |
| `openclaw_model_cost_usd_total` | counter | `agent`, `channel`, `model`, `provider` |
| `openclaw_skill_used_total` | counter | `activation`, `agent`, `skill`, `source` |
| `openclaw_tool_execution_total` | counter | `error_category`, `outcome`, `params_kind`, `tool`, `tool_owner`, `tool_source` |
| `openclaw_tool_execution_duration_seconds` | histogram | `error_category`, `outcome`, `params_kind`, `tool`, `tool_owner`, `tool_source` |
| `openclaw_harness_run_total` | counter | `channel`, `error_category`, `harness`, `model`, `outcome`, `phase`, `plugin`, `provider` |
| `openclaw_harness_run_duration_seconds` | histogram | `channel`, `error_category`, `harness`, `model`, `outcome`, `phase`, `plugin`, `provider` |
| `openclaw_message_received_total` | counter | `channel`, `source` |
| `openclaw_message_dispatch_started_total` | counter | `channel`, `source` |
| `openclaw_message_dispatch_completed_total` | counter | `channel`, `outcome`, `reason`, `source` |
| `openclaw_message_dispatch_duration_seconds` | histogram | `channel`, `outcome`, `reason`, `source` |
| `openclaw_message_processed_total` | counter | `channel`, `outcome`, `reason` |
| `openclaw_message_processed_duration_seconds` | histogram | `channel`, `outcome`, `reason` |
| `openclaw_message_delivery_started_total` | counter | `channel`, `delivery_kind` |
| `openclaw_message_delivery_total` | counter | `channel`, `delivery_kind`, `error_category`, `outcome` |
| `openclaw_message_delivery_duration_seconds` | histogram | `channel`, `delivery_kind`, `error_category`, `outcome` |
| `openclaw_talk_event_total` | counter | `brain`, `event_type`, `mode`, `provider`, `transport` |
| `openclaw_talk_event_duration_seconds` | histogram | `brain`, `event_type`, `mode`, `provider`, `transport` |
| `openclaw_talk_audio_bytes` | histogram | `brain`, `event_type`, `mode`, `provider`, `transport` |
| `openclaw_queue_lane_size` | gauge | `lane` |
| `openclaw_queue_lane_wait_seconds` | histogram | `lane` |
| `openclaw_session_state_total` | counter | `reason`, `state` |
| `openclaw_session_queue_depth` | gauge | `state` |
| `openclaw_session_turn_created_total` | counter | `agent`, `channel`, `trigger` |
| `openclaw_session_recovery_total` | counter | `action`, `active_work_kind`, `state`, `status` |
| `openclaw_session_recovery_age_seconds` | histogram | `action`, `active_work_kind`, `state`, `status` |
| `openclaw_memory_bytes` | gauge | `kind` |
| `openclaw_memory_rss_bytes` | histogram | none |
| `openclaw_memory_pressure_total` | counter | `level`, `reason` |
| `openclaw_telemetry_exporter_total` | counter | `exporter`, `reason`, `signal`, `status` |
| `openclaw_prometheus_series_dropped_total` | counter | none |
| Metric | Type | Labels |
| ------------------------------------------------ | --------- | ----------------------------------------------------------------------------------------- |
| `openclaw_run_completed_total` | counter | `channel`, `model`, `outcome`, `provider`, `trigger` |
| `openclaw_run_duration_seconds` | histogram | `channel`, `model`, `outcome`, `provider`, `trigger` |
| `openclaw_model_call_total` | counter | `api`, `error_category`, `model`, `outcome`, `provider`, `transport` |
| `openclaw_model_call_duration_seconds` | histogram | `api`, `error_category`, `model`, `outcome`, `provider`, `transport` |
| `openclaw_model_failover_total` | counter | `from_model`, `from_provider`, `lane`, `reason`, `suspended`, `to_model`, `to_provider` |
| `openclaw_model_tokens_total` | counter | `agent`, `channel`, `model`, `provider`, `token_type` |
| `openclaw_gen_ai_client_token_usage` | histogram | `model`, `provider`, `token_type` |
| `openclaw_model_cost_usd_total` | counter | `agent`, `channel`, `model`, `provider` |
| `openclaw_skill_used_total` | counter | `activation`, `agent`, `skill`, `source` |
| `openclaw_tool_execution_total` | counter | `error_category`, `outcome`, `params_kind`, `tool`, `tool_owner`, `tool_source` |
| `openclaw_tool_execution_duration_seconds` | histogram | `error_category`, `outcome`, `params_kind`, `tool`, `tool_owner`, `tool_source` |
| `openclaw_tool_execution_blocked_total` | counter | `denied_reason`, `params_kind`, `tool`, `tool_owner`, `tool_source` |
| `openclaw_harness_run_total` | counter | `channel`, `error_category`, `harness`, `model`, `outcome`, `phase`, `plugin`, `provider` |
| `openclaw_harness_run_duration_seconds` | histogram | `channel`, `error_category`, `harness`, `model`, `outcome`, `phase`, `plugin`, `provider` |
| `openclaw_webhook_received_total` | counter | `channel`, `webhook` |
| `openclaw_webhook_error_total` | counter | `channel`, `webhook` |
| `openclaw_webhook_duration_seconds` | histogram | `channel`, `webhook` |
| `openclaw_message_received_total` | counter | `channel`, `source` |
| `openclaw_message_dispatch_started_total` | counter | `channel`, `source` |
| `openclaw_message_dispatch_completed_total` | counter | `channel`, `outcome`, `reason`, `source` |
| `openclaw_message_dispatch_duration_seconds` | histogram | `channel`, `outcome`, `reason`, `source` |
| `openclaw_message_processed_total` | counter | `channel`, `outcome`, `reason` |
| `openclaw_message_processed_duration_seconds` | histogram | `channel`, `outcome`, `reason` |
| `openclaw_message_delivery_started_total` | counter | `channel`, `delivery_kind` |
| `openclaw_message_delivery_total` | counter | `channel`, `delivery_kind`, `error_category`, `outcome` |
| `openclaw_message_delivery_duration_seconds` | histogram | `channel`, `delivery_kind`, `error_category`, `outcome` |
| `openclaw_talk_event_total` | counter | `brain`, `event_type`, `mode`, `provider`, `transport` |
| `openclaw_talk_event_duration_seconds` | histogram | `brain`, `event_type`, `mode`, `provider`, `transport` |
| `openclaw_talk_audio_bytes` | histogram | `brain`, `event_type`, `mode`, `provider`, `transport` |
| `openclaw_queue_lane_size` | gauge | `lane` |
| `openclaw_queue_lane_wait_seconds` | histogram | `lane` |
| `openclaw_session_state_total` | counter | `reason`, `state` |
| `openclaw_session_queue_depth` | gauge | `state` |
| `openclaw_session_turn_created_total` | counter | `agent`, `channel`, `trigger` |
| `openclaw_session_stuck_total` | counter | `reason`, `state` |
| `openclaw_session_stuck_age_seconds` | histogram | `reason`, `state` |
| `openclaw_session_recovery_total` | counter | `action`, `active_work_kind`, `state`, `status` |
| `openclaw_session_recovery_age_seconds` | histogram | `action`, `active_work_kind`, `state`, `status` |
| `openclaw_liveness_warning_total` | counter | `reason` |
| `openclaw_liveness_sessions` | gauge | `state` |
| `openclaw_liveness_event_loop_delay_p99_seconds` | histogram | `reason` |
| `openclaw_liveness_event_loop_delay_max_seconds` | histogram | `reason` |
| `openclaw_liveness_event_loop_utilization_ratio` | histogram | `reason` |
| `openclaw_liveness_cpu_core_ratio` | histogram | `reason` |
| `openclaw_payload_large_total` | counter | `action`, `channel`, `plugin`, `reason`, `surface` |
| `openclaw_payload_large_bytes` | histogram | `action`, `channel`, `plugin`, `reason`, `surface` |
| `openclaw_memory_bytes` | gauge | `kind` |
| `openclaw_memory_rss_bytes` | histogram | none |
| `openclaw_memory_pressure_total` | counter | `level`, `reason` |
| `openclaw_telemetry_exporter_total` | counter | `exporter`, `reason`, `signal`, `status` |
| `openclaw_prometheus_series_dropped_total` | counter | none |
## Label policy

View File

@@ -289,7 +289,7 @@ troubleshooting, see the main [FAQ](/help/faq).
- `gpt-nano` → `openai/gpt-5.4-nano`
- `gemini` → `google/gemini-3.1-pro-preview`
- `gemini-flash` → `google/gemini-3-flash-preview`
- `gemini-flash-lite` → `google/gemini-3.1-flash-lite-preview`
- `gemini-flash-lite` → `google/gemini-3.1-flash-lite`
If you set your own alias with the same name, your value wins.

View File

@@ -509,8 +509,8 @@ Think of the suites as "increasing realism" (and increasing flakiness/cost):
Native dependency policy:
- Default test installs skip optional native Discord opus builds. Discord voice receive uses the pure-JS `opusscript` decoder, and `@discordjs/opus` stays disabled in `allowBuilds` so local tests and Testbox lanes do not compile the native addon.
- Use a dedicated Discord voice performance or live lane if you intentionally need to compare a native opus build. Do not set `@discordjs/opus` to `true` in the default `allowBuilds`; that makes unrelated install/test loops compile native code.
- Default test installs skip optional native Discord opus builds. Discord voice uses bundled `libopus-wasm`, and `@discordjs/opus` stays disabled in `allowBuilds` so local tests and Testbox lanes do not compile the native addon.
- Compare native opus performance in the `libopus-wasm` benchmark repo, not in default OpenClaw install/test loops. Do not set `@discordjs/opus` to `true` in the default `allowBuilds`; that makes unrelated install/test loops compile native code.
<AccordionGroup>
<Accordion title="Projects, shards, and scoped lanes">
@@ -557,6 +557,10 @@ Native dependency policy:
processes by default to reduce V8 compile churn during big local runs.
Set `OPENCLAW_VITEST_ENABLE_MAGLEV=1` to compare against stock V8
behavior.
- `scripts/run-vitest.mjs` terminates explicit non-watch Vitest runs after
5 minutes with no stdout or stderr output. Set
`OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=0` to disable the watchdog for an
intentionally silent investigation.
</Accordion>
@@ -746,6 +750,7 @@ These Docker runners split into two buckets:
- 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:release-user-journey`, `test:docker:release-typed-onboarding`, `test:docker:release-media-memory`, `test:docker:release-upgrade-user-journey`, `test:docker:release-plugin-marketplace`, `test:docker:skill-install`, `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`, `test:docker:plugin-lifecycle-matrix`, and `test:docker:config-reload` boot one or more real containers and verify higher-level integration paths.
- Docker/Bash E2E lanes that install the packed OpenClaw tarball through `scripts/lib/openclaw-e2e-instance.sh` cap `npm install` at `OPENCLAW_E2E_NPM_INSTALL_TIMEOUT` (default `600s`; set `0` to disable the wrapper for debugging).
The live-model Docker runners also bind-mount only the needed CLI auth homes (or all supported ones when the run is not narrowed), then copy them into the container home before the run so external-CLI OAuth can refresh tokens without mutating the host auth store:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 657 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

View File

@@ -72,9 +72,10 @@ Recommended for most interactive installs on macOS/Linux/WSL.
</Step>
<Step title="Ensure Node.js 24 by default">
Checks Node version and installs Node 24 if needed (Homebrew on macOS, NodeSource setup scripts on Linux apt/dnf/yum). OpenClaw still supports Node 22 LTS, currently `22.19+`, for compatibility.
On Alpine/musl Linux, the installer uses apk packages instead of NodeSource; the configured Alpine repositories must provide Node `22.19+` (Alpine 3.21 or newer at the time of writing).
</Step>
<Step title="Ensure Git">
Installs Git if missing.
Installs Git if missing using the detected package manager, including apk on Alpine.
</Step>
<Step title="Install OpenClaw">
- `npm` method (default): global npm install
@@ -187,10 +188,10 @@ by default, plus git-checkout installs under the same prefix flow.
<Steps>
<Step title="Install local Node runtime">
Downloads a pinned supported Node LTS tarball (the version is embedded in the script and updated independently) to `<prefix>/tools/node-v<version>` and verifies SHA-256.
On Alpine/musl Linux, where Node does not publish compatible tarballs for the pinned runtime, installs `nodejs` and `npm` with `apk` and links that runtime into the prefix wrapper path.
On Alpine/musl Linux, where Node does not publish compatible tarballs for the pinned runtime, installs `nodejs` and `npm` with `apk` and links that runtime into the prefix wrapper path. The Alpine repositories must provide Node `22.19+`; use Alpine 3.21 or newer if older repositories only provide Node 20 or 21.
</Step>
<Step title="Ensure Git">
If Git is missing, attempts install via apt/dnf/yum on Linux or Homebrew on macOS.
If Git is missing, attempts install via apt/dnf/yum/apk on Linux or Homebrew on macOS.
</Step>
<Step title="Install OpenClaw under prefix">
- `npm` method (default): installs under the prefix with npm, then writes wrapper to `<prefix>/bin/openclaw`

View File

@@ -0,0 +1,205 @@
---
summary: "Per-agent Policy plugin overlays layered on top of global policy rules."
read_when:
- You are designing per-agent policy requirements
- You need to distinguish tool posture policy from workspace policy
- You are configuring stricter policy for one named agent
title: "Agent-scoped policy overlays"
---
# Agent-scoped policy overlays
OpenClaw policy supports global requirements and stricter requirements for
explicit runtime agent ids. Some deployments need one agent to use a tighter
workspace and tool posture than other agents, but deployment-wide rules should
not force every agent to use the same posture.
This page describes the agent-scoped overlay model. The field reference remains
[`openclaw policy`](/cli/policy).
## Design goals
- Keep global policy as the deployment baseline.
- Let a named agent add stricter requirements without weakening global rules.
- Reuse existing policy section shapes where the evidence can be attributed to
an agent.
- Avoid making `agents.workspace` a second tool-permission system.
- Leave global-only checks global until their evidence can be mapped to an
agent.
## Shape
Use `scopes.<scopeName>` for purpose-named agent policy scopes. Each
scope lists the runtime `agentIds` it applies to, then reuses the normal
top-level policy section grammar where the section evidence can be attributed to
those agents. The initial shipped scoped sections are `tools` and
`agents.workspace`; sandbox and ingress stay out of this PR and can join the
same container once those policy PRs land and their evidence carries agent
identity. The scoped field inventory is backed by policy rule metadata that
records each field's strictness semantics for later policy-file conformance.
```jsonc
{
"tools": {
"denyTools": ["process"],
},
"agents": {
"workspace": {
"allowedAccess": ["none", "ro"],
},
},
"scopes": {
"release-agent-lockdown": {
"agentIds": ["release-agent"],
"agents": {
"workspace": {
"allowedAccess": ["none", "ro"],
},
},
"tools": {
"profiles": { "allow": ["minimal", "messaging"] },
"fs": { "requireWorkspaceOnly": true },
"exec": {
"allowSecurity": ["deny", "allowlist"],
"requireAsk": ["always"],
"allowHosts": ["sandbox"],
},
"elevated": { "allow": false },
"alsoAllow": { "expected": ["message", "read"] },
"denyTools": ["exec", "process", "write", "edit", "apply_patch"],
},
},
},
}
```
`agents.workspace` remains the existing all-agent workspace baseline.
`scopes.<scopeName>` is a scoped overlay, not a replacement for global
policy. The scope name is descriptive only; matching uses `agentIds`, not
display names. It deliberately contains normal section names instead of a
bespoke per-agent mini-grammar.
Every scope present in `policy.jsonc` must be valid and enforceable. In this
PR, the only supported selector is `agentIds`, and it supports only `tools.*`
and `agents.workspace.*`.
## Layering semantics
Policy evaluation is additive:
1. Top-level policy applies to all matching evidence.
2. Existing `agents.workspace` applies to defaults and every listed agent.
3. `scopes.<scopeName>` applies to evidence for each normalized runtime
id in `agentIds`.
4. Multiple scope blocks may target the same agent when they govern
different fields, or when a later value for the same field is equally or
more restrictive according to policy metadata.
5. A named-agent overlay can tighten policy, but it cannot make a global
violation acceptable.
If both global and agent-scoped rules fail, findings should point at the rule
that was violated:
```text
oc://policy.jsonc/tools/denyTools
oc://policy.jsonc/scopes/release-agent-lockdown/tools/denyTools
oc://policy.jsonc/scopes/release-agent-lockdown/agents/workspace/allowedAccess
```
That keeps broad tool posture, named-agent tool posture, and workspace posture
auditable as separate requirements even when they observe the same config
fields.
Exact-list claims such as `tools.alsoAllow.expected` compare the configured list
to the expected list and report both missing expected entries and unexpected
extra entries. This is intended for additive posture such as `alsoAllow`, where
one extra entry can widen an agent beyond its reviewed role.
## Policy and config layering
The overlay model separates where policy is authored from where OpenClaw config
is observed:
| Policy scope | Observed config | Applies to | Example result |
| --------------------------------------- | ---------------------------------------------------- | --------------------------------- | ----------------------------------------------------------------------------- |
| Top-level `tools.*` | Global `tools.*` and inherited agent tool posture | All agents using matching posture | Deny `gateway` exec host for every agent unless the global policy allows it. |
| Top-level `tools.*` | `agents.list[].tools.*` overrides | Any agent with an override | Flag one agent that overrides `tools.exec.host` to an unapproved value. |
| `scopes.<scopeName>.tools.*` | Matching `agents.list[]` entry and inherited posture | Only that named agent | Let most agents use `node` exec host while one agent must use only `sandbox`. |
| `agents.workspace` | Defaults and every listed agent workspace posture | Defaults and all listed agents | Require every agent workspace access to be `none` or `ro`. |
| `scopes.<scopeName>.agents.workspace.*` | Matching `agents.list[]` workspace posture | Only that named agent | Require one agent to be read-only without requiring the same for `main`. |
Per-agent overlays are additive. A named-agent rule can be stricter than the
top-level rule, but it cannot make a global violation acceptable. For allow-list
rules, the effective allowed set is the intersection of the global rule and the
named-agent overlay when both are present.
For example, if top-level `tools.exec.allowHosts` permits `["sandbox", "node"]`
and `scopes.release-agent-lockdown.tools.exec.allowHosts` permits only
`["sandbox"]`, `release-agent` fails when its effective exec host is `node`;
another agent can still pass
with `node`.
## Tool posture versus workspace posture
Tool posture belongs under `tools` because it describes what tool behavior a
configuration may expose. The existing `tools.*` policy observes both global
`tools.*` config and per-agent `agents.list[].tools.*` overrides.
Workspace posture belongs under `workspace` because it describes sandbox mode
and workspace access. The workspace section should not grow into a general tool
policy namespace. If one agent needs stricter tool restrictions to make its
workspace posture meaningful, put those restrictions in the same agent overlay
under `scopes.<scopeName>.tools`.
For a restricted release agent, the intended split is:
```jsonc
{
"scopes": {
"release-agent-lockdown": {
"agentIds": ["release-agent"],
"agents": {
"workspace": { "allowedAccess": ["none", "ro"] },
},
"tools": {
"denyTools": ["exec", "process", "write", "edit", "apply_patch"],
},
},
},
}
```
## Section eligibility
An agent-scoped section should be added only when policy evidence carries an
agent id or can be attributed to one without guessing.
| Section | Initial agent-scoped status | Reason |
| ----------- | --------------------------- | ------------------------------------------------------------------------ |
| `workspace` | Include | Agent sandbox/workspace evidence already has agent identity. |
| `tools` | Include | Tool posture evidence includes global and per-agent tool config. |
| `sandbox` | Pipeline follow-up | Keep out until the sandbox posture PR lands and evidence can be scoped. |
| `ingress` | Pipeline follow-up | Keep out until ingress/channel posture lands with agent attribution. |
| `models` | Include when mapped | Selected model refs can be agent-specific. |
| `mcp` | Include when mapped | Use only when MCP server evidence is attributable to an agent. |
| `auth` | Defer | Auth profile metadata is a config catalog unless agent binding is clear. |
| `channels` | Defer | Channel provider posture is deployment-level until routing is scoped. |
| `gateway` | Keep global | Gateway exposure/auth/http posture is process-level. |
| `network` | Keep global | Private-network SSRF posture is runtime-level. |
| `secrets` | Keep global first | Secret provider posture is shared unless refs are agent-attributed. |
## Compatibility
The implementation is additive:
- keep all existing top-level policy fields valid;
- keep `agents.workspace` semantics unchanged;
- validate `scopes` before evaluating scoped rules;
- reject unsupported scoped sections clearly until their evidence and policy
contracts are implemented;
- do not reinterpret top-level `tools.requireMetadata` as agent-scoped, because
tool metadata describes the declared workspace tool catalog;
- include agent-scoped evidence in the attestation hash when any scoped rule is
present.
This lets broad tool posture remain a top-level policy contract while named
agents add stricter observable claims without weakening the global baseline.

View File

@@ -42,7 +42,7 @@ Capabilities are the public **native plugin** model inside OpenClaw. Every nativ
| Realtime transcription | `api.registerRealtimeTranscriptionProvider(...)` | `openai` |
| Realtime voice | `api.registerRealtimeVoiceProvider(...)` | `openai` |
| Media understanding | `api.registerMediaUnderstandingProvider(...)` | `openai`, `google` |
| Meeting notes source | `api.registerMeetingNotesSourceProvider(...)` | `discord`, `meeting-notes` |
| Transcripts source | `api.registerTranscriptSourceProvider(...)` | `discord` |
| Image generation | `api.registerImageGenerationProvider(...)` | `openai`, `google`, `fal`, `minimax` |
| Music generation | `api.registerMusicGenerationProvider(...)` | `google`, `minimax` |
| Video generation | `api.registerVideoGenerationProvider(...)` | `qwen` |

View File

@@ -353,17 +353,17 @@ If discovery fails or times out, OpenClaw uses a bundled fallback catalog for:
- GPT-5.4 mini
- GPT-5.2
The current bundled harness is `@openai/codex` `0.132.0`. A `model/list` probe
The current bundled harness is `@openai/codex` `0.133.0`. A `model/list` probe
against that bundled app-server returned:
| Model id | Default | Hidden | Input modalities | Reasoning efforts |
| ------------------- | ------- | ------ | ---------------- | ------------------------ |
| `gpt-5.5` | Yes | No | text, image | low, medium, high, xhigh |
| `gpt-5.4` | No | No | text, image | low, medium, high, xhigh |
| `gpt-5.4-mini` | No | No | text, image | low, medium, high, xhigh |
| `gpt-5.3-codex` | No | No | text, image | low, medium, high, xhigh |
| `gpt-5.2` | No | No | text, image | low, medium, high, xhigh |
| `codex-auto-review` | No | Yes | text, image | low, medium, high, xhigh |
| Model id | Default | Hidden | Input modalities | Reasoning efforts |
| --------------------- | ------- | ------ | ---------------- | ------------------------ |
| `gpt-5.5` | Yes | No | text, image | low, medium, high, xhigh |
| `gpt-5.4` | No | No | text, image | low, medium, high, xhigh |
| `gpt-5.4-mini` | No | No | text, image | low, medium, high, xhigh |
| `gpt-5.3-codex` | No | No | text, image | low, medium, high, xhigh |
| `gpt-5.3-codex-spark` | No | No | text | low, medium, high, xhigh |
| `gpt-5.2` | No | No | text, image | low, medium, high, xhigh |
Hidden models can be returned by the app-server catalog for internal or
specialized flows, but they are not normal model-picker choices.

View File

@@ -27,8 +27,10 @@ native Codex turn receives Codex app-server developer instructions, while an
explicit PI compatibility route keeps the normal OpenClaw/PI system prompt even
when it uses Codex-flavored OpenAI auth or transport.
Native Codex keeps Codex-owned base/model/personality instructions and
project-doc behavior according to the active Codex thread config. Lightweight
Native Codex keeps Codex-owned base/model instructions and project-doc behavior
according to the active Codex thread config. OpenClaw starts and resumes native
Codex threads with Codex's built-in personality disabled so workspace
personality files and OpenClaw agent identity stay authoritative. Lightweight
OpenClaw runs still preserve their existing project-doc suppression. OpenClaw
developer instructions cover OpenClaw runtime concerns such as source-channel
delivery, OpenClaw dynamic tools, ACP delegation, adapter context, and the
@@ -115,19 +117,19 @@ They do not invoke OpenClaw plugin hooks.
Supported in Codex runtime v1:
| Surface | Support | Why |
| --------------------------------------------- | -------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| OpenAI model loop through Codex | Supported | Codex app-server owns the OpenAI turn, native thread resume, and native tool continuation. |
| OpenClaw channel routing and delivery | Supported | Telegram, Discord, Slack, WhatsApp, iMessage, and other channels stay outside the model runtime. |
| OpenClaw dynamic tools | Supported | Codex asks OpenClaw to execute these tools, so OpenClaw stays in the execution path. |
| Prompt and context plugins | Supported | OpenClaw projects OpenClaw-specific prompt/context into the Codex turn while leaving Codex-owned base, model, personality, and configured project-doc prompts in the native Codex lane. Native Codex developer instructions accept only command guidance explicitly scoped to `codex_app_server`; legacy global command hints remain for non-Codex prompt surfaces. |
| Context engine lifecycle | Supported | Assemble, ingest, and after-turn maintenance run around Codex turns. Context engines do not replace native Codex compaction. |
| Dynamic tool hooks | Supported | `before_tool_call`, `after_tool_call`, and tool-result middleware run around OpenClaw-owned dynamic tools. |
| Lifecycle hooks | Supported as adapter observations | `llm_input`, `llm_output`, `agent_end`, `before_compaction`, and `after_compaction` fire with honest Codex-mode payloads. |
| Final-answer revision gate | Supported through native hook relay | Codex `Stop` is relayed to `before_agent_finalize`; `revise` asks Codex for one more model pass before finalization. |
| Native shell, patch, and MCP block or observe | Supported through native hook relay | Codex `PreToolUse` and `PostToolUse` are relayed for committed native tool surfaces, including MCP payloads on Codex app-server `0.125.0` or newer. Blocking is supported; argument rewriting is not. |
| Native permission policy | Supported through Codex app-server approvals and compatibility native hook relay | Codex app-server approval requests route through OpenClaw after Codex review. The `PermissionRequest` native hook relay is opt-in for native approval modes because Codex emits it before guardian review. |
| App-server trajectory capture | Supported | OpenClaw records the request it sent to app-server and the app-server notifications it receives. |
| Surface | Support | Why |
| --------------------------------------------- | -------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| OpenAI model loop through Codex | Supported | Codex app-server owns the OpenAI turn, native thread resume, and native tool continuation. |
| OpenClaw channel routing and delivery | Supported | Telegram, Discord, Slack, WhatsApp, iMessage, and other channels stay outside the model runtime. |
| OpenClaw dynamic tools | Supported | Codex asks OpenClaw to execute these tools, so OpenClaw stays in the execution path. |
| Prompt and context plugins | Supported | OpenClaw projects OpenClaw-specific prompt/context into the Codex turn while leaving Codex-owned base, model, and configured project-doc prompts in the native Codex lane. OpenClaw disables Codex's built-in personality for native threads so agent workspace personality files remain authoritative. Native Codex developer instructions accept only command guidance explicitly scoped to `codex_app_server`; legacy global command hints remain for non-Codex prompt surfaces. |
| Context engine lifecycle | Supported | Assemble, ingest, and after-turn maintenance run around Codex turns. Context engines do not replace native Codex compaction. |
| Dynamic tool hooks | Supported | `before_tool_call`, `after_tool_call`, and tool-result middleware run around OpenClaw-owned dynamic tools. |
| Lifecycle hooks | Supported as adapter observations | `llm_input`, `llm_output`, `agent_end`, `before_compaction`, and `after_compaction` fire with honest Codex-mode payloads. |
| Final-answer revision gate | Supported through native hook relay | Codex `Stop` is relayed to `before_agent_finalize`; `revise` asks Codex for one more model pass before finalization. |
| Native shell, patch, and MCP block or observe | Supported through native hook relay | Codex `PreToolUse` and `PostToolUse` are relayed for committed native tool surfaces, including MCP payloads on Codex app-server `0.125.0` or newer. Blocking is supported; argument rewriting is not. |
| Native permission policy | Supported through Codex app-server approvals and compatibility native hook relay | Codex app-server approval requests route through OpenClaw after Codex review. The `PermissionRequest` native hook relay is opt-in for native approval modes because Codex emits it before guardian review. |
| App-server trajectory capture | Supported | OpenClaw records the request it sent to app-server and the app-server notifications it receives. |
Not supported in Codex runtime v1:

View File

@@ -649,7 +649,7 @@ Each list is optional:
| `realtimeVoiceProviders` | `string[]` | Realtime-voice provider ids this plugin owns. |
| `memoryEmbeddingProviders` | `string[]` | Memory embedding provider ids this plugin owns. |
| `mediaUnderstandingProviders` | `string[]` | Media-understanding provider ids this plugin owns. |
| `meetingNotesSourceProviders` | `string[]` | Meeting-notes source provider ids this plugin owns. |
| `transcriptSourceProviders` | `string[]` | Transcript source provider ids this plugin owns. |
| `imageGenerationProviders` | `string[]` | Image-generation provider ids this plugin owns. |
| `videoGenerationProviders` | `string[]` | Video-generation provider ids this plugin owns. |
| `webFetchProviders` | `string[]` | Web-fetch provider ids this plugin owns. |
@@ -865,6 +865,11 @@ Fields:
| `modelPrefixes` | `string[]` | Prefixes matched with `startsWith` against shorthand model ids. |
| `modelPatterns` | `string[]` | Regex sources matched against shorthand model ids after profile suffix removal. |
`modelPatterns` entries are compiled through `compileSafeRegex`, which rejects
patterns containing nested repetition (for example `(a+)+$`). Patterns that fail
the safety check are silently skipped, the same as syntactically invalid regex.
Keep patterns simple and avoid nested quantifiers.
## modelCatalog reference
Use `modelCatalog` when OpenClaw should know provider model metadata before

View File

@@ -1,359 +0,0 @@
---
summary: "Meeting Notes plugin: capture transcripts from Discord voice and imported meeting sources, then write summaries"
read_when:
- You want OpenClaw to take meeting notes
- You are wiring Discord voice, Google Meet, Slack huddles, or another meeting source into notes
- You need the meeting_notes tool contract
title: "Meeting Notes plugin"
---
The Meeting Notes plugin is the generic notes layer for live calls and imported
meeting transcripts. It owns transcript storage, summary rendering, and the
`meeting_notes` tool. Channel plugins own capture, authentication, and
platform-specific meeting joins.
Use this page when you want OpenClaw to capture Discord voice notes today, when
you want to import a transcript from another meeting system, or when you are
building a Google Meet, Slack huddle, Zoom, or calendar-owned source provider.
## Source model
Meeting sources register `meetingNotesSourceProviders` through the plugin SDK.
The first live provider is `discord-voice`; the built-in `manual-transcript`
provider imports post-meeting transcripts.
- `live-audio`: source joins or listens to a call and streams final utterances.
- `live-caption`: source reads captions from a browser or meeting surface.
- `posthoc-transcript`: source imports a transcript or notes artifact after the meeting.
- `recording-stt`: source transcribes a recording before importing utterances.
This keeps Discord, Google Meet, Slack huddles, and future meeting surfaces out
of the notes engine. Each source supplies speaker-labeled utterances; Meeting
Notes writes the artifacts and summary.
## Install and enable
Meeting Notes is an external source plugin in this repository. It is not part of
the core OpenClaw npm package and becomes available only when the plugin is
installed as a plugin or loaded from a source checkout that contains
`extensions/meeting-notes`.
Once the plugin is loaded, it is enabled by default unless one of these settings
blocks it:
- `plugins.enabled: false` disables all plugins.
- `plugins.deny` contains `meeting-notes`.
- `plugins.allow` is set and does not contain `meeting-notes`.
- `plugins.entries.meeting-notes.enabled: false` disables this plugin entry.
- `plugins.entries.meeting-notes.config.enabled: false` keeps the plugin loaded
but disables the `meeting_notes` tool and auto-start service.
The normal user config file is `~/.openclaw/openclaw.json`. The `plugins`
section controls plugin loading, and the nested `entries.<pluginId>.config`
object is passed to that plugin as plugin-specific config. A separate
`config: { ... }` block under `meeting-notes` is expected; it is how plugins
receive their own options without adding core config keys.
Use this shape when your config has a plugin allowlist:
```json5
{
plugins: {
allow: ["discord", "meeting-notes"],
entries: {
"meeting-notes": {
enabled: true,
config: {
enabled: true,
maxUtterances: 2000,
autoStart: [],
},
},
},
},
}
```
Run a config check after editing:
```bash
openclaw config validate
```
Gateway config hot reload applies plugin allowlist and plugin-entry changes.
Restart the Gateway if you are also changing the source plugin itself, installing
new plugin files, or changing Discord voice credentials.
## Configuration
Meeting Notes has three plugin config fields:
- `enabled`: `true` by default. Set `false` to leave the plugin installed but
disable the tool and auto-start service.
- `maxUtterances`: `2000` by default. Summary generation reads only the newest
N utterances from `transcript.jsonl`; valid values are clamped to `1` through
`10000`.
- `autoStart`: empty by default. Each entry starts a live notes source when the
Gateway starts or reloads the plugin.
An `autoStart` entry accepts:
- `providerId`: required. Use `discord-voice` for Discord voice.
- `enabled`: optional, default `true`. Set `false` to keep an entry without
starting it.
- `sessionId`: optional. If omitted, OpenClaw generates a timestamped id.
- `title`: optional human-readable title for summaries and CLI output.
- `accountId`: optional source account id when more than one account exists.
- `guildId`: provider-specific Discord guild id.
- `channelId`: provider-specific Discord voice channel id.
- `meetingUrl`: provider-specific meeting URL for browser or calendar sources.
Use `autoStart` when OpenClaw should begin notes capture automatically on
gateway startup:
```json5
{
plugins: {
entries: {
"meeting-notes": {
config: {
autoStart: [
{
providerId: "discord-voice",
guildId: "123",
channelId: "456",
title: "Weekly planning",
},
],
},
},
},
},
}
```
Auto-start retries startup failures up to 12 times with a five-second delay.
This lets the notes service wait for channel plugins such as Discord to finish
initializing. Sessions that were started by auto-start are stopped and summarized
when the plugin service stops cleanly.
Discord voice capture still needs normal Discord voice setup and permissions.
See [Discord voice](/channels/discord#voice-mode).
## Discord voice
Discord is the first live source. The Discord plugin owns the voice connection,
speaker detection, audio decoding, and transcription. Meeting Notes receives
final speaker-labeled utterances and persists them.
For Discord live capture:
- Enable and configure the Discord plugin first.
- Configure Discord voice mode so OpenClaw can join the target voice channel.
- Use `providerId: "discord-voice"`.
- Provide `guildId` and `channelId`.
- Add `accountId` only when you run more than one Discord account.
The transcription model is not chosen by Meeting Notes. In Discord `stt-tts`
voice mode, STT uses `tools.media.audio`; `voice.model` controls the agent reply
model, not transcription. In realtime voice modes, transcription follows the
configured realtime provider and model. See [Discord voice](/channels/discord#voice-mode)
for the current Discord voice model and provider knobs.
## Google Meet, Slack huddles, and other sources
Meeting Notes is intentionally source-neutral. Google Meet, Slack huddles, Zoom,
calendar recordings, or browser caption capture should be separate source
providers that register with the plugin SDK.
Recommended source choices:
- Google Meet live browser/caption support: implement a `live-caption` provider
that accepts `meetingUrl` and emits final caption utterances.
- Google Meet recordings or downloaded transcripts: implement
`posthoc-transcript` or use `manual-transcript` until a provider exists.
- Slack huddles today: import post-meeting huddle notes or transcript artifacts.
Slack does not expose a general bot-join live huddle audio API.
- Slack huddles later: keep the Slack-owned source provider responsible for
Slack auth, artifact lookup, and transcript normalization.
The notes engine should not contain platform joins, browser automation, Slack
API polling, or Discord voice logic. Those belong to the owning source plugin.
## Tool
Use `meeting_notes` with an `action`:
- `status`: list registered providers and active sessions.
- `start`: start a live notes session.
- `stop`: stop a live session and write `summary.md`.
- `import`: import a transcript and write `summary.md`.
- `summarize`: regenerate a summary for an existing session.
Discord live notes require `providerId: "discord-voice"`, plus `guildId` and
`channelId`. `accountId` is optional when only one Discord account is active.
```json
{
"action": "start",
"providerId": "discord-voice",
"guildId": "123",
"channelId": "456",
"title": "Weekly planning"
}
```
Stop by session id:
```json
{
"action": "stop",
"sessionId": "meeting-2026-05-22T10-00-00-000Z-a1b2c3d4"
}
```
Import a transcript:
```json
{
"action": "import",
"providerId": "manual-transcript",
"title": "Design review",
"transcript": "Alex: We decided to ship the Discord source first.\nSam: Action item: add Slack huddle import later."
}
```
`manual-transcript` splits plain transcript text into utterances. Use it for
copied Google Meet notes, Slack huddle summaries, calendar transcripts, or any
source that already produced text.
## Storage layout
Artifacts are stored under the OpenClaw state directory:
```text
$OPENCLAW_STATE_DIR/meeting-notes/YYYY-MM-DD/<session>/
metadata.json
transcript.jsonl
summary.json
summary.md
```
If `OPENCLAW_STATE_DIR` is unset, the default state directory is
`~/.openclaw`. A normal local install therefore writes notes under
`~/.openclaw/meeting-notes/...`.
Each file has one job:
- `metadata.json`: session id, source provider, title, start time, stop time,
and provider metadata.
- `transcript.jsonl`: append-only speaker utterances. Each line is one JSON
object with the utterance text and the session id.
- `summary.json`: structured summary data used by tooling, including the
speaker-labeled transcript window used for the generated summary.
- `summary.md`: human-readable notes for terminals, editors, and document
workflows, including a speaker-labeled transcript section.
The date directory comes from the session start time, so multiple meetings per
day stay grouped. If a human session id repeats across days, use the
date-qualified selector from `openclaw meeting-notes list`, such as
`2026-05-22/standup`.
By default, OpenClaw generates timestamped session ids:
```text
meeting-2026-05-22T10-00-00-000Z-a1b2c3d4
```
That means ten meetings on the same day become ten sibling directories:
```text
~/.openclaw/meeting-notes/2026-05-22/
meeting-2026-05-22T09-00-00-000Z-a1b2c3d4/
meeting-2026-05-22T10-30-00-000Z-b2c3d4e5/
meeting-2026-05-22T13-00-00-000Z-c3d4e5f6/
```
Configure `sessionId` only when that id is unique for the day. Human ids such as
`standup` are fine for one recurring meeting per day. If the same id appears on
multiple days, use the date-qualified selector in the CLI.
## CLI access
Use the read-only CLI to find or print stored summaries:
```bash
openclaw meeting-notes list
openclaw meeting-notes show <session>
openclaw meeting-notes path <session>
openclaw meeting-notes path <session> --transcript
```
See [Meeting Notes CLI](/cli/meeting-notes) for the full command reference.
## Long meetings
For long meetings, utterances are appended to `transcript.jsonl` as they arrive.
Summary generation reads a bounded window controlled by
`plugins.entries.meeting-notes.config.maxUtterances` (default: `2000`) so a
multi-hour call does not require unbounded summary memory.
This means the transcript can keep growing on disk, while summarization stays
bounded. Increase `maxUtterances` when you need more of a multi-hour meeting in
the generated summary and speaker-labeled transcript section. Decrease it when
summaries are too slow or too large.
Current summaries are generated when a session stops, after an import, or when
the `summarize` action runs. They are not continuously rewritten for every
utterance.
## Troubleshooting
### `meeting_notes` is missing
Check that the plugin is installed or loaded from source, and that plugin
loading does not exclude it:
```bash
openclaw config validate
openclaw meeting-notes list
```
If `plugins.allow` is set, it must include `meeting-notes`. If `plugins.deny`
contains `meeting-notes`, remove it.
### Auto-start does not join Discord
Confirm the `autoStart` entry uses `providerId: "discord-voice"` and includes
both `guildId` and `channelId`. If you run multiple Discord accounts, include
`accountId`. Also verify Discord voice works outside Meeting Notes by joining
the same voice channel through the Discord voice commands.
### Summary is missing
Live sessions write `summary.md` when stopped. Stop the session with
`meeting_notes` action `stop`, then inspect it:
```bash
openclaw meeting-notes list
openclaw meeting-notes path <session>
```
Use `meeting_notes` action `summarize` to regenerate `summary.md` for an
existing stored session.
### Selector is ambiguous
If you reused a human session id such as `standup`, use the date-qualified
selector shown by `openclaw meeting-notes list`:
```bash
openclaw meeting-notes show 2026-05-22/standup
```
## Related
- [Meeting Notes CLI](/cli/meeting-notes)
- [Discord voice](/channels/discord#voice-mode)
- [Plugin management](/tools/plugin)
- [Plugin architecture](/plugins/architecture)

View File

@@ -151,7 +151,7 @@ commands.
| [diagnostics-otel](/plugins/reference/diagnostics-otel) | OpenClaw diagnostics OpenTelemetry exporter. | `@openclaw/diagnostics-otel`<br />npm; ClawHub: `clawhub:@openclaw/diagnostics-otel` | plugin |
| [diagnostics-prometheus](/plugins/reference/diagnostics-prometheus) | OpenClaw diagnostics Prometheus exporter. | `@openclaw/diagnostics-prometheus`<br />npm; ClawHub: `clawhub:@openclaw/diagnostics-prometheus` | plugin |
| [diffs](/plugins/reference/diffs) | Read-only diff viewer and file renderer for agents. | `@openclaw/diffs`<br />npm; ClawHub | contracts: tools; skills |
| [discord](/plugins/reference/discord) | Adds the Discord channel surface for sending and receiving OpenClaw messages. | `@openclaw/discord`<br />npm; ClawHub | channels: discord; contracts: meetingNotesSourceProviders |
| [discord](/plugins/reference/discord) | Adds the Discord channel surface for sending and receiving OpenClaw messages. | `@openclaw/discord`<br />npm; ClawHub | channels: discord; contracts: transcriptSourceProviders |
| [feishu](/plugins/reference/feishu) | Adds the Feishu channel surface for sending and receiving OpenClaw messages. | `@openclaw/feishu`<br />npm; ClawHub | channels: feishu; contracts: tools; skills |
| [google-meet](/plugins/reference/google-meet) | Join Google Meet calls through Chrome or Twilio transports. | `@openclaw/google-meet`<br />npm; ClawHub | contracts: tools |
| [googlechat](/plugins/reference/googlechat) | Adds the Google Chat channel surface for sending and receiving OpenClaw messages. | `@openclaw/googlechat`<br />npm; ClawHub | channels: googlechat |
@@ -175,9 +175,8 @@ commands.
## Source checkout only
| Plugin | Description | Distribution | Surface |
| ------------------------------------------------- | --------------------------------------------------------------------------- | --------------------------------------------------- | --------------------------------------------- |
| [meeting-notes](/plugins/reference/meeting-notes) | Capture meeting transcripts from channel-owned sources and write summaries. | `@openclaw/meeting-notes`<br />source checkout only | contracts: meetingNotesSourceProviders, tools |
| [qa-channel](/plugins/reference/qa-channel) | Adds the QA Channel surface for sending and receiving OpenClaw messages. | `@openclaw/qa-channel`<br />source checkout only | channels: qa-channel |
| [qa-lab](/plugins/reference/qa-lab) | OpenClaw QA lab plugin with private debugger UI and scenario runner. | `@openclaw/qa-lab`<br />source checkout only | plugin |
| [qa-matrix](/plugins/reference/qa-matrix) | Matrix QA transport runner and substrate. | `@openclaw/qa-matrix`<br />source checkout only | plugin |
| Plugin | Description | Distribution | Surface |
| ------------------------------------------- | ------------------------------------------------------------------------ | ------------------------------------------------ | -------------------- |
| [qa-channel](/plugins/reference/qa-channel) | Adds the QA Channel surface for sending and receiving OpenClaw messages. | `@openclaw/qa-channel`<br />source checkout only | channels: qa-channel |
| [qa-lab](/plugins/reference/qa-lab) | OpenClaw QA lab plugin with private debugger UI and scenario runner. | `@openclaw/qa-lab`<br />source checkout only | plugin |
| [qa-matrix](/plugins/reference/qa-matrix) | Matrix QA transport runner and substrate. | `@openclaw/qa-matrix`<br />source checkout only | plugin |

View File

@@ -44,7 +44,7 @@ pnpm plugins:inventory:gen
| [diagnostics-otel](/plugins/reference/diagnostics-otel) | OpenClaw diagnostics OpenTelemetry exporter. | `@openclaw/diagnostics-otel`<br />npm; ClawHub: `clawhub:@openclaw/diagnostics-otel` | plugin |
| [diagnostics-prometheus](/plugins/reference/diagnostics-prometheus) | OpenClaw diagnostics Prometheus exporter. | `@openclaw/diagnostics-prometheus`<br />npm; ClawHub: `clawhub:@openclaw/diagnostics-prometheus` | plugin |
| [diffs](/plugins/reference/diffs) | Read-only diff viewer and file renderer for agents. | `@openclaw/diffs`<br />npm; ClawHub | contracts: tools; skills |
| [discord](/plugins/reference/discord) | Adds the Discord channel surface for sending and receiving OpenClaw messages. | `@openclaw/discord`<br />npm; ClawHub | channels: discord; contracts: meetingNotesSourceProviders |
| [discord](/plugins/reference/discord) | Adds the Discord channel surface for sending and receiving OpenClaw messages. | `@openclaw/discord`<br />npm; ClawHub | channels: discord; contracts: transcriptSourceProviders |
| [document-extract](/plugins/reference/document-extract) | Extract text and fallback page images from local document attachments. | `@openclaw/document-extract-plugin`<br />included in OpenClaw | contracts: documentExtractors |
| [duckduckgo](/plugins/reference/duckduckgo) | Adds web search provider support. | `@openclaw/duckduckgo-plugin`<br />included in OpenClaw | contracts: webSearchProviders |
| [elevenlabs](/plugins/reference/elevenlabs) | Adds media understanding provider support. Adds realtime transcription provider support. Adds text-to-speech provider support. | `@openclaw/elevenlabs-speech`<br />included in OpenClaw | contracts: mediaUnderstandingProviders, realtimeTranscriptionProviders, speechProviders |
@@ -73,7 +73,6 @@ pnpm plugins:inventory:gen
| [lobster](/plugins/reference/lobster) | Typed workflow tool with resumable approvals. | `@openclaw/lobster`<br />npm; ClawHub | contracts: tools |
| [matrix](/plugins/reference/matrix) | Adds the Matrix channel surface for sending and receiving OpenClaw messages. | `@openclaw/matrix`<br />ClawHub: `clawhub:@openclaw/matrix`; npm | channels: matrix |
| [mattermost](/plugins/reference/mattermost) | Adds the Mattermost channel surface for sending and receiving OpenClaw messages. | `@openclaw/mattermost`<br />included in OpenClaw | channels: mattermost |
| [meeting-notes](/plugins/reference/meeting-notes) | Capture meeting transcripts from channel-owned sources and write summaries. | `@openclaw/meeting-notes`<br />source checkout only | contracts: meetingNotesSourceProviders, tools |
| [memory-core](/plugins/reference/memory-core) | Adds memory embedding provider support. Adds agent-callable tools. | `@openclaw/memory-core`<br />included in OpenClaw | contracts: memoryEmbeddingProviders, tools |
| [memory-lancedb](/plugins/reference/memory-lancedb) | Adds agent-callable tools. | `@openclaw/memory-lancedb`<br />npm; ClawHub | contracts: tools |
| [memory-wiki](/plugins/reference/memory-wiki) | Persistent wiki compiler and Obsidian-friendly knowledge vault for OpenClaw. | `@openclaw/memory-wiki`<br />included in OpenClaw | contracts: tools; skills |

View File

@@ -16,7 +16,7 @@ Adds the Discord channel surface for sending and receiving OpenClaw messages.
## Surface
channels: discord; contracts: meetingNotesSourceProviders
channels: discord; contracts: transcriptSourceProviders
## Related docs

View File

@@ -1,23 +0,0 @@
---
summary: "Capture meeting transcripts from channel-owned sources and write summaries."
read_when:
- You are installing, configuring, or auditing the meeting-notes plugin
title: "Meeting Notes plugin"
---
# Meeting Notes plugin
Capture meeting transcripts from channel-owned sources and write summaries.
## Distribution
- Package: `@openclaw/meeting-notes`
- Install route: source checkout only
## Surface
contracts: meetingNotesSourceProviders, tools
## Related docs
- [meeting-notes](/plugins/meeting-notes)

View File

@@ -626,7 +626,7 @@ releases.
| `plugin-sdk/speech` | Speech helpers | Speech provider types plus provider-facing directive, registry, validation helpers, and OpenAI-compatible TTS builder |
| `plugin-sdk/speech-core` | Shared speech core | Speech provider types, registry, directives, normalization |
| `plugin-sdk/realtime-transcription` | Realtime transcription helpers | Provider types, registry helpers, and shared WebSocket session helper |
| `plugin-sdk/realtime-voice` | Realtime voice helpers | Provider types, registry/resolution helpers, bridge session helpers, shared agent talk-back queues, active-run voice control, transcript/event health, echo suppression, and fast context consult helpers |
| `plugin-sdk/realtime-voice` | Realtime voice helpers | Provider types, registry/resolution helpers, bridge session helpers, shared agent talk-back queues, active-run voice control, transcript/event health, echo suppression, consult question matching, forced-consult coordination, turn-context tracking, output activity tracking, and fast context consult helpers |
| `plugin-sdk/image-generation` | Image-generation helpers | Image generation provider types plus image asset/data URL helpers and the OpenAI-compatible image provider builder |
| `plugin-sdk/image-generation-core` | Shared image-generation core | Image-generation types, failover, auth, and registry helpers |
| `plugin-sdk/music-generation` | Music-generation helpers | Music-generation provider/request/result types |

View File

@@ -264,6 +264,7 @@ focused channel/runtime subpaths, `config-contracts`, `string-coerce-runtime`,
| `plugin-sdk/tool-plugin` | Define a simple typed agent-tool plugin and expose static metadata for manifest generation |
| `plugin-sdk/tool-payload` | Extract normalized payloads from tool result objects |
| `plugin-sdk/tool-send` | Extract canonical send target fields from tool args |
| `plugin-sdk/sandbox` | Sandbox backend types and SSH/OpenShell command helpers, including fail-fast exec command preflight |
| `plugin-sdk/temp-path` | Shared temp-download path helpers and private secure temp workspaces |
| `plugin-sdk/logging-core` | Subsystem logger and redaction helpers |
| `plugin-sdk/markdown-table-runtime` | Markdown table mode and conversion helpers |
@@ -321,23 +322,21 @@ focused channel/runtime subpaths, `config-contracts`, `string-coerce-runtime`,
| `plugin-sdk/media-mime` | Narrow MIME normalization, file-extension mapping, MIME detection, and media-kind helpers |
| `plugin-sdk/media-store` | Narrow media store helpers such as `saveMediaBuffer` and `saveMediaStream` |
| `plugin-sdk/media-generation-runtime` | Shared media-generation failover helpers, candidate selection, and missing-model messaging |
| `plugin-sdk/meeting-notes` | Meeting notes source provider types, registry lookup, and provider id normalization helpers |
| `plugin-sdk/media-understanding` | Media understanding provider types plus provider-facing image/audio/structured-extraction helper exports |
| `plugin-sdk/meeting-notes` | Meeting notes source provider types, registry helpers, and provider id normalization |
| `plugin-sdk/text-chunking` | Text and markdown chunking/render helpers, markdown table conversion, directive-tag stripping, and safe-text utilities |
| `plugin-sdk/text-chunking` | Outbound text chunking helper |
| `plugin-sdk/speech` | Speech provider types plus provider-facing directive, registry, validation, OpenAI-compatible TTS builder, and speech helper exports |
| `plugin-sdk/speech-core` | Shared speech provider types, registry, directive, normalization, and speech helper exports |
| `plugin-sdk/realtime-transcription` | Realtime transcription provider types, registry helpers, and shared WebSocket session helper |
| `plugin-sdk/realtime-bootstrap-context` | Realtime profile bootstrap helper for bounded `IDENTITY.md`, `USER.md`, and `SOUL.md` context injection |
| `plugin-sdk/realtime-voice` | Realtime voice provider types and registry helpers |
| `plugin-sdk/realtime-voice` | Realtime voice provider types, registry helpers, and shared realtime voice behavior helpers, including output activity tracking |
| `plugin-sdk/image-generation` | Image generation provider types plus image asset/data URL helpers and the OpenAI-compatible image provider builder |
| `plugin-sdk/image-generation-core` | Shared image-generation types, failover, auth, and registry helpers |
| `plugin-sdk/music-generation` | Music generation provider/request/result types |
| `plugin-sdk/music-generation-core` | Shared music-generation types, failover helpers, provider lookup, and model-ref parsing |
| `plugin-sdk/video-generation` | Video generation provider/request/result types |
| `plugin-sdk/video-generation-core` | Shared video-generation types, failover helpers, provider lookup, and model-ref parsing |
| `plugin-sdk/meeting-notes` | Shared meeting-notes source provider types, registry helpers, session descriptors, and utterance metadata |
| `plugin-sdk/transcripts` | Shared transcripts source provider types, registry helpers, session descriptors, and utterance metadata |
| `plugin-sdk/webhook-targets` | Webhook target registry and route-install helpers |
| `plugin-sdk/webhook-path` | Deprecated compatibility alias; use `plugin-sdk/webhook-ingress` |
| `plugin-sdk/web-media` | Shared remote/local media loading helpers |

View File

@@ -260,6 +260,25 @@ OPENCLAW_LIVE_TEST=1 OPENCLAW_LIVE_OLLAMA=1 OPENCLAW_LIVE_OLLAMA_WEB_SEARCH=0 \
pnpm test:live -- extensions/ollama/ollama.live.test.ts
```
For Ollama Cloud API-key smoke tests, point the live test at `https://ollama.com`
and choose a hosted model from the current catalog:
```bash
export OLLAMA_API_KEY='<your-ollama-cloud-api-key>'
OPENCLAW_LIVE_TEST=1 \
OPENCLAW_LIVE_OLLAMA=1 \
OPENCLAW_LIVE_OLLAMA_BASE_URL=https://ollama.com \
OPENCLAW_LIVE_OLLAMA_MODEL=glm-5.1:cloud \
OPENCLAW_LIVE_OLLAMA_WEB_SEARCH=1 \
pnpm test:live -- extensions/ollama/ollama.live.test.ts
```
The cloud smoke runs text, native stream, and web search. It skips embeddings by
default for `https://ollama.com` because Ollama Cloud API keys may not authorize
`/api/embed`. Set `OPENCLAW_LIVE_OLLAMA_EMBEDDINGS=1` when you explicitly want
the live test to fail if the configured cloud key cannot use the embed endpoint.
To add a new model, simply pull it with Ollama:
```bash

View File

@@ -193,7 +193,7 @@ Choose your preferred auth method and follow the setup steps.
model.
<Warning>
OpenClaw does **not** expose `openai/gpt-5.3-codex-spark`. Live OpenAI API requests reject that model, and the current Codex catalog does not expose it either.
OpenClaw does **not** expose `openai/gpt-5.3-codex-spark`. Live OpenAI API requests reject that direct provider route. Use `openai-codex/gpt-5.3-codex-spark` only when the Codex catalog exposes it for your signed-in account.
</Warning>
</Tab>
@@ -251,7 +251,9 @@ Choose your preferred auth method and follow the setup steps.
Prefer `openai/gpt-5.5` for new subscription-backed agent config. Older
`openai-codex/gpt-*` refs are legacy PI routes, not the native Codex runtime
path; run `openclaw doctor --fix` when you want to migrate them to canonical
`openai/*` refs.
`openai/*` refs. `openai-codex/gpt-5.3-codex-spark` is the exception for
accounts whose Codex catalog advertises that model; direct `openai/*` and
Azure refs for it remain suppressed.
</Warning>
<Note>
@@ -549,7 +551,7 @@ See [Video Generation](/tools/video-generation) for shared tool parameters, prov
OpenClaw adds a shared GPT-5 prompt contribution for GPT-5-family runs on OpenClaw-assembled prompt surfaces. It applies by model id, so PI/provider routes such as legacy pre-repair refs (`openai-codex/gpt-5.5`), `openrouter/openai/gpt-5.5`, `opencode/gpt-5.5`, and other compatible GPT-5 refs receive the same overlay. Older GPT-4.x models do not.
The bundled native Codex harness does not receive this OpenClaw GPT-5 overlay through Codex app-server developer instructions. Native Codex keeps Codex-owned base, model, personality, and project-doc behavior; OpenClaw contributes only runtime context such as channel delivery, OpenClaw dynamic tools, ACP delegation, workspace context, and OpenClaw skills.
The bundled native Codex harness does not receive this OpenClaw GPT-5 overlay through Codex app-server developer instructions. Native Codex keeps Codex-owned base, model, and project-doc behavior, while OpenClaw disables Codex's built-in personality for native threads so agent workspace personality files stay authoritative. OpenClaw contributes only runtime context such as channel delivery, OpenClaw dynamic tools, ACP delegation, workspace context, and OpenClaw skills.
The GPT-5 contribution adds a tagged behavior contract for persona persistence, execution safety, tool discipline, output shape, completion checks, and verification on matching OpenClaw-assembled prompts. Channel-specific reply and silent-message behavior stays in the shared OpenClaw system prompt and outbound delivery policy. The friendly interaction-style layer is separate and configurable.

View File

@@ -49,9 +49,10 @@ the maintainer-only release runbook.
1. Start from current `main`: pull latest, confirm the target commit is pushed,
and confirm current `main` CI is green enough to branch from it.
2. Rewrite the top `CHANGELOG.md` section from real commit history with
`/changelog`, keep entries user-facing, commit it, push it, and rebase/pull
once more before branching.
2. Generate the top `CHANGELOG.md` section from merged PRs and all direct
commits since the last reachable release tag. Keep entries user-facing,
dedupe overlapping PR/direct-commit entries, commit the rewrite, push it,
and rebase/pull once more before branching.
3. Review release compatibility records in
`src/plugins/compat/registry.ts` and
`src/commands/doctor/shared/deprecation-compat.ts`. Remove expired
@@ -196,6 +197,9 @@ vYYYY.M.D-beta.N` from the matching `release/YYYY.M.D` branch. The helper runs
QA-lab through a local OTLP/HTTP receiver and verifies trace, metric, and log
export plus bounded trace attributes and content/identifier redaction without
requiring Opik, Langfuse, or another external collector.
- Run `pnpm qa:otel:collector-smoke` when validating collector compatibility.
It routes the same QA-lab OTLP export through a real OpenTelemetry Collector
Docker container before the local receiver assertions.
- Run `pnpm qa:prometheus:smoke` when validating protected Prometheus scraping.
It exercises QA-lab, rejects unauthenticated scrapes, and verifies
release-critical metric families stay free of prompt content, raw identifiers,
@@ -528,7 +532,8 @@ Release QA Lab coverage includes:
baseline using the agentic parity pack
- fast live Matrix QA profile using the `qa-live-shared` environment
- live Telegram QA lane using Convex CI credential leases
- `pnpm qa:otel:smoke`, `pnpm qa:prometheus:smoke`, or
- `pnpm qa:otel:smoke`, `pnpm qa:otel:collector-smoke`,
`pnpm qa:prometheus:smoke`, or
`pnpm qa:observability:smoke` when release telemetry needs explicit local
proof

View File

@@ -50,9 +50,9 @@ OpenClaw persists sessions in two layers:
- Append-only transcript with tree structure (entries have `id` + `parentId`)
- Stores the actual conversation + tool calls + compaction summaries
- Used to rebuild the model context for future turns
- Large pre-compaction debug checkpoints are skipped once the active
transcript exceeds the checkpoint size cap, avoiding a second giant
`.checkpoint.*.jsonl` copy.
- Compaction checkpoints are metadata over the compacted successor
transcript. New compactions do not write a second `.checkpoint.*.jsonl`
copy.
Gateway history readers should avoid materializing the whole transcript unless
the surface explicitly needs arbitrary historical access. First-page history,
@@ -278,6 +278,10 @@ In the embedded Pi agent, auto-compaction triggers in two cases:
number of tokens`, `input token count exceeds the maximum number of input
tokens`, `input is too long for the model`, `ollama error: context length
exceeded`, and similar provider-shaped variants) → compact → retry.
When the provider reports the attempted token count, OpenClaw forwards that
observed count into overflow recovery compaction. If the provider confirms
overflow but does not expose a parseable count, OpenClaw passes a minimally
over-budget synthetic count to compaction engines and diagnostics.
If overflow recovery still fails, OpenClaw surfaces explicit guidance to the
user and preserves the current session mapping instead of silently rotating
the session key to a fresh session id. The next step is operator-controlled:
@@ -353,8 +357,8 @@ OpenClaw also enforces a safety floor for embedded runs:
disable.
- When `agents.defaults.compaction.truncateAfterCompaction` is enabled,
OpenClaw rotates the active transcript to a compacted successor JSONL after
compaction. The old full transcript remains archived and linked from the
compaction checkpoint instead of being rewritten in place.
compaction. Branch/restore checkpoint actions use that compacted successor;
legacy pre-compaction checkpoint files remain readable while referenced.
Why: leave enough headroom for multi-turn "housekeeping" (like memory writes) before compaction becomes unavoidable.

View File

@@ -43,7 +43,7 @@ title: "Tests"
- `pnpm test:e2e`: Runs the repo E2E aggregate: gateway end-to-end smoke tests plus the Control UI mocked browser E2E lane.
- `pnpm test:e2e:gateway`: Runs gateway end-to-end smoke tests (multi-instance WS/HTTP/node pairing). Defaults to `threads` + `isolate: false` with adaptive workers in `vitest.e2e.config.ts`; tune with `OPENCLAW_E2E_WORKERS=<n>` and set `OPENCLAW_E2E_VERBOSE=1` for verbose logs.
- `pnpm test:live`: Runs provider live tests (minimax/zai). Requires API keys and `LIVE=1` (or provider-specific `*_LIVE_TEST=1`) to unskip.
- `pnpm test:docker:all`: Builds the shared live-test image, packs OpenClaw once as an npm tarball, builds/reuses a bare Node/Git runner image plus a functional image that installs that tarball into `/app`, then runs Docker smoke lanes with `OPENCLAW_SKIP_DOCKER_BUILD=1` through a weighted scheduler. The bare image (`OPENCLAW_DOCKER_E2E_BARE_IMAGE`) is used for installer/update/plugin-dependency lanes; those lanes mount the prebuilt tarball instead of using copied repo sources. The functional image (`OPENCLAW_DOCKER_E2E_FUNCTIONAL_IMAGE`) is used for normal built-app functionality lanes. `scripts/package-openclaw-for-docker.mjs` is the single local/CI package packer and validates the tarball plus `dist/postinstall-inventory.json` before Docker consumes it. 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. `node scripts/test-docker-all.mjs --plan-json` emits the scheduler-owned CI plan for selected lanes, image kinds, package/live-image needs, state scenarios, and credential checks without building or running Docker. `OPENCLAW_DOCKER_ALL_PARALLELISM=<n>` controls process slots and defaults to 10; `OPENCLAW_DOCKER_ALL_TAIL_PARALLELISM=<n>` controls the provider-sensitive tail pool and defaults to 10. Heavy lane caps default to `OPENCLAW_DOCKER_ALL_LIVE_LIMIT=9`, `OPENCLAW_DOCKER_ALL_NPM_LIMIT=10`, and `OPENCLAW_DOCKER_ALL_SERVICE_LIMIT=7`; provider caps default to one heavy lane per provider via `OPENCLAW_DOCKER_ALL_LIVE_CLAUDE_LIMIT=4`, `OPENCLAW_DOCKER_ALL_LIVE_CODEX_LIMIT=4`, and `OPENCLAW_DOCKER_ALL_LIVE_GEMINI_LIMIT=4`. Use `OPENCLAW_DOCKER_ALL_WEIGHT_LIMIT` or `OPENCLAW_DOCKER_ALL_DOCKER_LIMIT` for larger hosts. If one lane exceeds the effective weight or resource cap on a low-parallelism host, it can still start from an empty pool and will run alone until it releases capacity. Lane starts are staggered by 2 seconds by default to avoid local Docker daemon create storms; override with `OPENCLAW_DOCKER_ALL_START_STAGGER_MS=<ms>`. The runner preflights Docker by default, cleans stale OpenClaw E2E containers, emits active-lane status every 30 seconds, shares provider CLI tool caches between compatible lanes, retries transient live-provider failures once by default (`OPENCLAW_DOCKER_ALL_LIVE_RETRIES=<n>`), and stores lane timings in `.artifacts/docker-tests/lane-timings.json` for longest-first ordering on later runs. Use `OPENCLAW_DOCKER_ALL_DRY_RUN=1` to print the lane manifest without running Docker, `OPENCLAW_DOCKER_ALL_STATUS_INTERVAL_MS=<ms>` to tune status output, or `OPENCLAW_DOCKER_ALL_TIMINGS=0` to disable timing reuse. Use `OPENCLAW_DOCKER_ALL_LIVE_MODE=skip` for deterministic/local lanes only or `OPENCLAW_DOCKER_ALL_LIVE_MODE=only` for live-provider lanes only; package aliases are `pnpm test:docker:local:all` and `pnpm test:docker:live:all`. Live-only mode merges main and tail live lanes into one longest-first pool so provider buckets can pack Claude, Codex, and Gemini work together. The runner stops scheduling new pooled lanes after the first failure unless `OPENCLAW_DOCKER_ALL_FAIL_FAST=0` is set, and each lane has a 120-minute fallback timeout overrideable with `OPENCLAW_DOCKER_ALL_LANE_TIMEOUT_MS`; selected live/tail lanes use tighter per-lane caps. CLI backend Docker setup commands have their own timeout via `OPENCLAW_LIVE_CLI_BACKEND_SETUP_TIMEOUT_SECONDS` (default 180). Per-lane logs, `summary.json`, `failures.json`, and phase timings are written under `.artifacts/docker-tests/<run-id>/`; use `pnpm test:docker:timings <summary.json>` to inspect slow lanes and `pnpm test:docker:rerun <run-id|summary.json|failures.json>` to print cheap targeted rerun commands.
- `pnpm test:docker:all`: Builds the shared live-test image, packs OpenClaw once as an npm tarball, builds/reuses a bare Node/Git runner image plus a functional image that installs that tarball into `/app`, then runs Docker smoke lanes with `OPENCLAW_SKIP_DOCKER_BUILD=1` through a weighted scheduler. The bare image (`OPENCLAW_DOCKER_E2E_BARE_IMAGE`) is used for installer/update/plugin-dependency lanes; those lanes mount the prebuilt tarball instead of using copied repo sources. The functional image (`OPENCLAW_DOCKER_E2E_FUNCTIONAL_IMAGE`) is used for normal built-app functionality lanes. `scripts/package-openclaw-for-docker.mjs` is the single local/CI package packer and validates the tarball plus `dist/postinstall-inventory.json` before Docker consumes it. 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. `node scripts/test-docker-all.mjs --plan-json` emits the scheduler-owned CI plan for selected lanes, image kinds, package/live-image needs, state scenarios, and credential checks without building or running Docker. `OPENCLAW_DOCKER_ALL_PARALLELISM=<n>` controls process slots and defaults to 10; `OPENCLAW_DOCKER_ALL_TAIL_PARALLELISM=<n>` controls the provider-sensitive tail pool and defaults to 10. Heavy lane caps default to `OPENCLAW_DOCKER_ALL_LIVE_LIMIT=9`, `OPENCLAW_DOCKER_ALL_NPM_LIMIT=10`, and `OPENCLAW_DOCKER_ALL_SERVICE_LIMIT=7`; provider caps default to one heavy lane per provider via `OPENCLAW_DOCKER_ALL_LIVE_CLAUDE_LIMIT=4`, `OPENCLAW_DOCKER_ALL_LIVE_CODEX_LIMIT=4`, and `OPENCLAW_DOCKER_ALL_LIVE_GEMINI_LIMIT=4`. Use `OPENCLAW_DOCKER_ALL_WEIGHT_LIMIT` or `OPENCLAW_DOCKER_ALL_DOCKER_LIMIT` for larger hosts. If one lane exceeds the effective weight or resource cap on a low-parallelism host, it can still start from an empty pool and will run alone until it releases capacity. Lane starts are staggered by 2 seconds by default to avoid local Docker daemon create storms; override with `OPENCLAW_DOCKER_ALL_START_STAGGER_MS=<ms>`. The runner preflights Docker by default, cleans stale OpenClaw E2E containers, emits active-lane status every 30 seconds, shares provider CLI tool caches between compatible lanes, retries transient live-provider failures once by default (`OPENCLAW_DOCKER_ALL_LIVE_RETRIES=<n>`), and stores lane timings in `.artifacts/docker-tests/lane-timings.json` for longest-first ordering on later runs. Use `OPENCLAW_DOCKER_ALL_DRY_RUN=1` to print the lane manifest without running Docker, `OPENCLAW_DOCKER_ALL_STATUS_INTERVAL_MS=<ms>` to tune status output, or `OPENCLAW_DOCKER_ALL_TIMINGS=0` to disable timing reuse. Use `OPENCLAW_DOCKER_ALL_LIVE_MODE=skip` for deterministic/local lanes only or `OPENCLAW_DOCKER_ALL_LIVE_MODE=only` for live-provider lanes only; package aliases are `pnpm test:docker:local:all` and `pnpm test:docker:live:all`. Live-only mode merges main and tail live lanes into one longest-first pool so provider buckets can pack Claude, Codex, and Gemini work together. The runner stops scheduling new pooled lanes after the first failure unless `OPENCLAW_DOCKER_ALL_FAIL_FAST=0` is set, and each lane has a 120-minute fallback timeout overridable with `OPENCLAW_DOCKER_ALL_LANE_TIMEOUT_MS`; selected live/tail lanes use tighter per-lane caps. CLI backend Docker setup commands have their own timeout via `OPENCLAW_LIVE_CLI_BACKEND_SETUP_TIMEOUT_SECONDS` (default 180). Per-lane logs, `summary.json`, `failures.json`, and phase timings are written under `.artifacts/docker-tests/<run-id>/`; use `pnpm test:docker:timings <summary.json>` to inspect slow lanes and `pnpm test:docker:rerun <run-id|summary.json|failures.json>` to print cheap targeted rerun commands.
- `pnpm test:docker:browser-cdp-snapshot`: Builds a Chromium-backed source E2E container, starts raw CDP plus an isolated Gateway, runs `browser doctor --deep`, and verifies CDP role snapshots include link URLs, cursor-promoted clickables, iframe refs, and frame metadata.
- `pnpm test:docker:skill-install`: Installs the packed OpenClaw tarball in a bare Docker runner, disables `skills.install.allowUploadedArchives`, resolves a current skill slug from live ClawHub search, installs it through `openclaw skills install`, and verifies `SKILL.md`, `.clawhub/origin.json`, `.clawhub/lock.json`, and `skills info --json`.
- CLI backend live Docker probes can be run as focused lanes, for example `pnpm test:docker:live-cli-backend:claude`, `pnpm test:docker:live-cli-backend:claude:resume`, or `pnpm test:docker:live-cli-backend:claude:mcp`. Gemini has matching `:resume` and `:mcp` aliases.

Some files were not shown because too many files have changed in this diff Show More