Compare commits

..

1108 Commits

Author SHA1 Message Date
Tak Hoffman
dca9b01c75 docs: front-load rescue bot quickstart 2026-04-21 12:52:55 -05:00
Tak Hoffman
6b12c89d57 docs: make rescue bot guide more prescriptive 2026-04-21 11:53:38 -05:00
Tak Hoffman
0a12c35bce docs: clarify rescue bot gateway setup 2026-04-21 11:49:17 -05:00
Vincent Koc
68fd946e6d test(zalo): use preferred tmp dir in outbound media test 2026-04-21 07:28:52 -07:00
Vincent Koc
ce0e191ae0 fix(zalo): use managed temp dir for outbound media 2026-04-21 06:57:03 -07:00
Pavan Kumar Gondhi
a65eb1b864 fix(zalo): add SSRF guard on outbound photo URLs [AI-assisted] (#69593)
* fix: address issue

* fix: address review feedback

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address review feedback

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address build failures

* fix: address PR review feedback

* fix: address review feedback

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address review feedback

* fix: address build feedback
2026-04-21 19:20:26 +05:30
Alex Knight
4407df6c03 perf(plugin-sdk): per-phase + per-jiti-call probes for bundled channel entries (#69537)
* perf(plugin-sdk): per-phase + per-jiti-call probes for bundled channel entries

Extends the existing OPENCLAW_PLUGIN_LOAD_PROFILE infrastructure (see
src/plugins/loader.ts `profilePluginLoaderSync` and src/plugins/source-loader.ts)
with two new probe sites inside src/plugin-sdk/channel-entry-contract.ts:

1. `bundled-register:<phase>` — wraps each phase of `defineBundledChannelEntry`'s
   register() callback (`setChannelRuntime`, `loadChannelPlugin`, `registerChannel`,
   `registerCliMetadata`, `registerFull`). Lets us pinpoint which phase of plugin
   registration is responsible for cold-start cost on a per-plugin basis.

2. `bundled-entry-module-load` — instruments `loadBundledEntryModuleSync` and
   reports `getJitiMs` (jiti loader factory) vs `jitiCallMs` (actual graph walk
   + transpile + ESM linking) separately. Lets us distinguish alias-map / loader
   setup overhead from import-graph traversal cost on a per-module basis.

Both probes are gated on OPENCLAW_PLUGIN_LOAD_PROFILE=1 and have zero overhead
when the env flag is unset (early return before any `performance.now()` call).
Log format matches the existing `[plugin-load-profile]` line shape so existing
log scrapers continue to work.

The helper is a file-local mirror of `profilePluginLoaderSync` rather than a
new SDK export — keeps the SDK boundary narrow per src/plugin-sdk/AGENTS.md
and avoids cross-importing host internals.

Used to validate PR #69317 (slack startup perf) — measurements showed slack
`setChannelRuntime` dropping from 13183ms to 67ms after barrel narrowing,
which would have been undiagnosable without these per-phase probes.

* perf(plugins): per-plugin register() probe in plugin loader

Adds a `phase=${registrationMode}:register` probe wrapping each call to
`runPluginRegisterSync(register, api)` in src/plugins/loader.ts. Emits the
established `[plugin-load-profile]` line shape via `profilePluginLoaderSync`,
gated on OPENCLAW_PLUGIN_LOAD_PROFILE=1.

Two call sites are wrapped:
- The main load path (registrationMode is dynamic: "snapshot", "validate",
  "full") at the post-snapshot register block. Emits e.g.
  `phase=full:register plugin=slack elapsedMs=14102.1 source=...`
- The cli-metadata-only path (registrationMode hardcoded to "cli-metadata")
  for fast `--metadata` boot flows.

Together with the existing `phase=full` (entire load) and `phase=source-loader`
probes plus the `bundled-register:*` and `bundled-entry-module-load` probes
added in the previous commit, this gives a full breakdown:

- `phase=full plugin=slack` — total cost from import through register return
- `phase=full:register plugin=slack` — just the register() callback (NEW)
- `phase=bundled-register:setChannelRuntime plugin=slack` — sub-phase
- `phase=bundled-register:loadChannelPlugin plugin=slack` — sub-phase
- `phase=bundled-entry-module-load plugin=(bundled-entry)` — per-module load

Lets you `sort -k4 -n -r` the log output to find the slowest plugin's
register() call across all bundled+third-party plugins, then drill in via
the sub-phase probes for bundled entries.

* perf(plugins): consolidate plugin-load-profile primitives in shared module

Extracts the previously duplicated `shouldProfilePluginLoader` /
`profilePluginLoaderSync` helpers into a new `src/plugins/plugin-load-profile.ts`
module. Removes 3 file-local copies of the same env-flag check and 2
near-duplicate `try { run() } finally { console.error(...) }` wrappers.

Files updated:
- NEW src/plugins/plugin-load-profile.ts — sole owner of:
    shouldProfilePluginLoader()
    profilePluginLoaderSync<T>({phase, pluginId?, source, run, extras?})
    formatPluginLoadProfileLine({phase, pluginId?, source, elapsedMs, extras?})
- src/plugins/loader.ts — drop file-local copies, import shared helper
  (existing 4 + new 2 call sites unchanged in shape)
- src/plugins/source-loader.ts — drop renamed local copy
  (`shouldProfilePluginSourceLoader`), use shared helper with
  `pluginId: "(direct)"` to preserve the existing `plugin=(direct)` field
- src/plugin-sdk/channel-entry-contract.ts — drop file-local copies and
  inline `profileStep` closure; use shared `profilePluginLoaderSync` directly
  at all 5 `bundled-register:*` call sites; dual-timing
  `bundled-entry-module-load` probe uses `formatPluginLoadProfileLine` with
  ordered `extras` for `getJitiMs`/`jitiCallMs`

Log line format is byte-for-byte identical to before (validated against
3 cases: standard, with pluginId, dual-timing). The `extras` API is
intentionally an ordered tuple list (not a record) so that scrapers see
deterministic field order between `elapsedMs=` and `source=`.

Net: +155/-87 lines across 4 files, removing ~60 lines of duplication
while exposing a stable, documented probe surface.

Verified:
- pnpm tsgo (core) — 0 errors
- pnpm lint on all 4 files — 0 warnings, 0 errors
- pnpm test src/plugins/loader.test.ts — 102/102
- pnpm test src/plugins/contracts/plugin-entry-guardrails.test.ts — 7/7
- pnpm test src/plugin-sdk/channel-entry-contract.test.ts — 4/4
- Standalone formatter smoke test — output matches existing format byte-for-byte

* refactor(plugins): rename profilePluginLoaderSync to withProfile and bind scope at register sites

* fix(plugin-sdk): zero jiti sub-step timings on Win32 nodeRequire fast-path
2026-04-21 22:06:13 +10:00
Tak Hoffman
06ff594a3e fix: preload slugged startup memory files (#69600)
* fix: load slugged startup memory files

* fix: harden slugged startup memory loading

* fix: prefer recent startup memory artifacts

* fix: tolerate startup memory stat races

* fix: include utc startup memory dates

* fix: prioritize recent startup memory dates

* fix: cap merged startup memory dates

* fix: preserve local startup memory window

* fix: order startup memory compatibility dates

* fix startup memory directory rescans
2026-04-21 07:03:44 -05:00
Pavan Kumar Gondhi
31160dc069 fix(agents): enforce subagent envelope inheritance on ACP child sessions [AI-assisted] (#69383)
* fix: address issue

* fix: address review feedback

* fix: finalize issue changes

* fix: address PR review feedback

* address build faiure

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address PR review feedback
2026-04-21 17:25:25 +05:30
Cássio Jones Dhein Silva
89b6d02481 fix(tui): arm streaming watchdog on every delta, not only visible ones (#69338)
When ingestDelta returns null (first empty/commentary delta or unchanged
content), the handler returned early, skipping setActivityStatus and
armStreamingWatchdog. If all subsequent deltas were also null (e.g.
due to phase filtering), the watchdog was never armed and the status bar
stayed stale as "idle" while a run was live.

Move setActivityStatus("streaming") and armStreamingWatchdog before
the null-displayText guard so they fire on every received delta event.

Fixes #34513, #40824

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 21:53:36 +10:00
Sanjay Santhanam
081da17090 fix(codex): exclude codex-app-server synthetic apiKey from secrets audit (#69581)
* fix(codex): exclude codex-app-server synthetic apiKey from secrets audit

The Codex extension uses the literal string "codex-app-server" as a
hardcoded placeholder apiKey in provider.ts, since the real
authentication is managed by the app-server transport itself.

The secrets audit currently reports this as a real plaintext leak
(PLAINTEXT_FOUND), producing a false positive for any user who has
configured the Codex harness.

Declare it as a plugin-owned non-secret marker in the Codex plugin
manifest, so it flows through the standard
`listKnownNonSecretApiKeyMarkers()` path alongside `ollama-local`,
`lmstudio-local`, `gcp-vertex-credentials`, and `minimax-oauth`.

Also extends the existing `model auth markers` unit tests to lock
in the behavior.

Fixes #69511

* ci: retrigger checks (no-op)
2026-04-21 21:38:26 +10:00
Mason Huang
850b6d2d46 docs(changelog): deduplicate #67800 entries in Unreleased (#69670)
* fix(changelog): remove duplicate entry for PR #67800

* docs(changelog): move #67800 note from Unreleased into 2026.4.20
2026-04-21 18:05:05 +08:00
Ted Li
4bacdc8824 fix(agents): honor explicit long Anthropic cache TTL on custom hosts (#67800)
Merged via squash.

Prepared head SHA: 0ffde15713
Co-authored-by: MonkeyLeeT <6754057+MonkeyLeeT@users.noreply.github.com>
Co-authored-by: hxy91819 <8814856+hxy91819@users.noreply.github.com>
Reviewed-by: @hxy91819
2026-04-21 17:45:27 +08:00
Ayaan Zaidi
f1df354222 fix: fix Telegram media file delivery (#69641) 2026-04-21 14:19:27 +05:30
Ayaan Zaidi
fcc86f043b fix(media): preserve outbound attachment filenames 2026-04-21 14:19:27 +05:30
Ayaan Zaidi
f350bb4dfc fix(media): parse lowercase media directives 2026-04-21 14:19:27 +05:30
Omar Shahine
14506aeca4 fix(bluebubbles): add opt-in coalesceSameSenderDms for split-send DMs (#69258)
Merged via squash.

Prepared head SHA: 8f1bd3cf53
Co-authored-by: omarshahine <10343873+omarshahine@users.noreply.github.com>
Co-authored-by: omarshahine <10343873+omarshahine@users.noreply.github.com>
Reviewed-by: @omarshahine
2026-04-21 01:43:17 -07:00
Peter Steinberger
f1805ab54d fix: centralize provider thinking profiles 2026-04-21 09:13:35 +01:00
Peter Steinberger
1cc2fc82ca docs: prepare 2026.4.20 changelog 2026-04-21 08:59:32 +01:00
Peter Steinberger
047acaa176 fix: stage ACP and Codex runtime deps 2026-04-21 08:47:24 +01:00
Ayaan Zaidi
6a4a60fe25 fix(gateway): drop stale service env on reinstall 2026-04-21 13:08:40 +05:30
Peter Steinberger
f14e91b39f test: add bundled channel dependency Docker smoke 2026-04-21 08:26:23 +01:00
Peter Steinberger
1d98853813 test: relax detached task recovery timing assertion 2026-04-21 08:22:35 +01:00
Peter Steinberger
2ad7bd0f55 fix: ignore placeholder shells in runtime detection (#69308) 2026-04-21 08:18:01 +01:00
Sk7n4k3d
7b414d8c0b shell: fall back to sh when SHELL is /usr/bin/false or nologin 2026-04-21 08:18:01 +01:00
Peter Steinberger
7b1871b99b fix(browser): clarify DevToolsActivePort attach failures 2026-04-21 08:11:41 +01:00
Peter Steinberger
9f054ee05b fix: sanitize mcp transport warning fields 2026-04-21 08:06:54 +01:00
Peter Steinberger
fccb2b8ace fix: launch Windows startup gateway directly 2026-04-21 08:03:34 +01:00
Peter Steinberger
c197b3fef4 fix(openai-codex): normalize legacy copilot transport 2026-04-21 08:03:31 +01:00
Peter Steinberger
85d86ebc4b fix: narrow MCP stdio env safety filter (#69540) 2026-04-21 08:03:29 +01:00
Devin Robison
62fa507189 fix(mcp): block dangerous stdio env overrides 2026-04-21 08:03:29 +01:00
Peter Steinberger
97534372f8 fix(openai-codex): normalize completions transport drift 2026-04-21 07:58:25 +01:00
Peter Steinberger
dc6ecd571a fix: skip workspace plugin runtime deps 2026-04-21 07:53:44 +01:00
Peter Steinberger
aacae4ce62 fix: use npm for bundled runtime dep repair 2026-04-21 07:53:44 +01:00
Peter Steinberger
fb2c405dbc fix: bound gateway usage cost cache (#68842) 2026-04-21 07:53:44 +01:00
Feelw00
8bf57e8bde fix(gateway): bound costUsageCache with MAX + FIFO eviction
Regression: `costUsageCache` in `src/gateway/server-methods/usage.ts` had no
delete/prune/evict path. The TTL check at L310 only gates stale reads — on a
miss after expiry, `set()` overwrites the same key but never removes stale
keys. `parseDateRange` derives cacheKey from `getTodayStartMs`, so cacheKey
rolls at every UTC 00:00, and additional axes (days / startDate / endDate /
utcOffset) multiply cardinality. The macOS menu polls `usage.cost` every ~45s
with no params, exercising `parseDateRange`'s default branch every day. Over
gateway uptime the map grows monotonically.

Three sibling caches in the same subsystem already implement MAX + FIFO
eviction (resolvedSessionKeyByRunId, TRANSCRIPT_SESSION_KEY_CACHE,
sessionTitleFieldsCache). This change mirrors their pattern:

- `COST_USAGE_CACHE_MAX = 256` (matches RUN_LOOKUP_CACHE_LIMIT and
  TRANSCRIPT_SESSION_KEY_CACHE_MAX).
- New `setCostUsageCache(cacheKey, entry)` helper checks size + evicts
  `keys().next().value` when adding a new key would exceed the cap.
- The three existing `costUsageCache.set(...)` call sites now route through
  the helper. TTL-on-read, in-flight dedup, and overwrite-on-same-key
  semantics are preserved.

Adds `src/gateway/server-methods/usage.cost-usage-cache.test.ts` which drives
growth through `__test.loadCostUsageSummaryCached` with 600 distinct
(startMs, endMs) pairs (mirrors day rollover + range switches). Pre-fix the
Map grows to 600; post-fix it plateaus, the last key is retained, and the
first key is evicted (FIFO).

AI-assisted (fully tested). 432 server-methods tests pass, pnpm check +
pnpm build clean.
2026-04-21 07:53:44 +01:00
Peter Steinberger
0d305839e5 fix(anthropic): scope api default normalization 2026-04-21 07:48:21 +01:00
Peter Steinberger
0532feb0d3 fix: skip redundant bundled runtime dep repairs 2026-04-21 07:37:48 +01:00
Peter Steinberger
494cd78889 fix: tolerate pnpm-backed runtime dependency installs 2026-04-21 07:37:48 +01:00
Peter Steinberger
05ba1335d9 fix: tolerate qa cli json startup logs 2026-04-21 07:37:48 +01:00
Ahmed Tokyo
c92490881b fix: map thinkingLevel to reasoning.effort for openai-responses-defaults family 2026-04-21 07:37:48 +01:00
Ayaan Zaidi
b9d2e0f86d fix(cron): gate delivery prompt on message tool availability 2026-04-21 12:01:06 +05:30
Ayaan Zaidi
19e451dc75 fix(cli): paginate cron show lookup 2026-04-21 12:01:06 +05:30
Ayaan Zaidi
5579fef673 fix(cron): align dry-run delivery previews with target policy 2026-04-21 12:01:06 +05:30
Ayaan Zaidi
ab3938df1e fix: cron chat delivery policy (#69587) 2026-04-21 12:01:06 +05:30
Ayaan Zaidi
0b25a73288 fix(cron): resolve delivery preview server-side 2026-04-21 12:01:06 +05:30
Ayaan Zaidi
4f0a978fc2 fix(cron): track implicit message sends 2026-04-21 12:01:06 +05:30
Ayaan Zaidi
9e160d5c0f fix(cron): make delivery previews dry-run safe 2026-04-21 12:01:06 +05:30
Ayaan Zaidi
4f2d24f463 fix(agents): honor explicit cron tool allowlists 2026-04-21 12:01:06 +05:30
Ayaan Zaidi
c18b6fc9da feat(cron): preview resolved delivery targets 2026-04-21 12:01:06 +05:30
Ayaan Zaidi
4c8299ca3d fix(cron): log delivery target trace 2026-04-21 12:01:06 +05:30
Ayaan Zaidi
d083702a7b fix(cron): require verified message delivery target 2026-04-21 12:01:06 +05:30
Ayaan Zaidi
657dcb416b fix(agents): forward forced message tool policy 2026-04-21 12:01:06 +05:30
Ayaan Zaidi
8d6ed34e4a docs(cron): clarify delivery modes 2026-04-21 12:01:06 +05:30
Ayaan Zaidi
4c1f187da0 fix(cron): keep message tool for chat delivery 2026-04-21 12:01:06 +05:30
Peter Steinberger
4a846dd129 fix(exec): honor yolo host exec semantics 2026-04-21 07:23:46 +01:00
Peter Steinberger
6ce17db11a fix: gate max thinking by model support 2026-04-21 07:02:43 +01:00
Peter Steinberger
f89740a62c docs: clarify beta changelog policy 2026-04-21 06:50:30 +01:00
Peter Steinberger
0bed456999 docs: expand beta release validation roster 2026-04-21 06:48:38 +01:00
Peter Steinberger
e4adb0b0e3 fix: hide adaptive think option for GPT models 2026-04-21 06:19:29 +01:00
Peter Steinberger
0da5e0e34e fix(openai): tighten gpt prompt contract 2026-04-21 06:14:54 +01:00
Peter Steinberger
f5be489266 test: add gpt-5.4 thinking visibility QA 2026-04-21 06:13:39 +01:00
Peter Steinberger
663501206f test: speed up channel contract CI 2026-04-21 06:12:55 +01:00
Peter Steinberger
048766fea5 docs: credit onboarding polish (#69553) (thanks @Patrick-Erichsen) 2026-04-21 06:08:43 +01:00
Patrick Erichsen
9fd0f7cd34 wizard: support searchable select, restore hint in search haystack 2026-04-21 06:08:43 +01:00
Patrick Erichsen
7752e3b30f onboard: clearer security disclaimer, loading spinners, api key placeholder 2026-04-21 06:08:43 +01:00
Pavan Kumar Gondhi
49db424c80 fix(qqbot): add SSRF guard to direct-upload URL paths in uploadC2CMedia and uploadGroupMedia [AI-assisted] (#69595)
* fix: address issue

* fix: address review feedback

* fix: address PR review feedback

* fix: address PR review feedback

* docs: add changelog entry for PR merge
2026-04-21 10:35:17 +05:30
Peter Steinberger
3e43306346 fix: handle webchat image-only turns (#69474) 2026-04-21 06:04:34 +01:00
Jaswir Raghoe
ca16413f3f fix(gateway): restore webchat pure-image turn handling (#69358)
eb10803691 tightened the reply-run empty-turn gate to only count
baseBodyFinal (strict user body) and to always append the '[User sent
media without caption]' placeholder to any prefix. That broke the Control
UI webchat path: images arrive via opts.images and do not stamp
sessionCtx.MediaPath (by design — see chat.directive-tags.test.ts
assertion that ctx.MediaPath stays undefined on dispatch). For pure-image
webchat turns the gate therefore returned 'I didn't receive any text in
your message', and when a caption was present the placeholder text leaked
into the Control UI user bubble on top of the inbound-context prefix.

Revert the three get-reply-run.ts hunks from eb10803691 back to the stable
2026.4.5 behavior: check baseBodyForPrompt.trim() (which includes the
inbound-context prefix) for the empty-turn gate, and fall back to the
plain '[User sent media without caption]' placeholder only when the whole
prompt body is empty.

Drop the media-only test the same commit added for metadata-only-prefix
bail-out; it encoded the exact behavior this reverts.

Fixes #69358.
Refs #69427.
2026-04-21 06:04:34 +01:00
Pavan Kumar Gondhi
5275d008ed fix(gateway): enforce allowRequestSessionKey gate on template-rendered mapping sessionKeys (#69381)
* fix: address issue

* fix: address review feedback

* fix: finalize issue changes

* fix: address PR review feedback

* fix: address review-pr skill feedback

* fix: address PR review feedback

* fix: address build failures

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address PR review feedback

* docs: add changelog entry for PR merge
2026-04-21 10:12:10 +05:30
Peter Steinberger
6c15561120 docs: add release tweet style guide 2026-04-21 05:39:33 +01:00
Peter Steinberger
8bb4dd7d08 fix: quiet bundled plugin runtime dep repairs 2026-04-21 05:36:09 +01:00
Peter Steinberger
6d409a6182 test: harden Parallels fresh install smoke 2026-04-21 05:34:25 +01:00
Peter Steinberger
b485ee7e36 docs: support release branch workflow 2026-04-21 05:33:21 +01:00
Peter Steinberger
1a3bde17a6 fix: support Lobster approvalId TaskFlow resumes (#69559) 2026-04-21 05:32:13 +01:00
kirkluokun
905da8bd6b fix(lobster): forward approvalId alongside resumeToken in tool envelope
@clawdbot/lobster/core returns both resumeToken and approvalId when a
workflow step needs approval, but the lobster plugin was dropping
approvalId in three places: normalizeEnvelope, the tool schema, and the
embedded-runner resume branch.

Agents forced to round-trip the ~155-byte base64url resumeToken across
tool calls are one stray truncation away from "Invalid token". The
8-hex approvalId is a disk-indexed alias (~/.lobster/state/approval_*
.json) — stable and escape-safe.

Changes are additive: token-based resume keeps working unchanged,
callers just gain an approvalId path.
2026-04-21 05:32:13 +01:00
Peter Steinberger
91dde183dc ci: isolate gateway watch regression harness 2026-04-21 05:27:57 +01:00
Sally O'Malley
62aff9aa56 fix: handle reasoning-only image responses (#69444)
Signed-off-by: sallyom <somalley@redhat.com>
2026-04-21 00:20:23 -04:00
Tak Hoffman
1303b03241 fix: add silent reply policy by conversation type (#68644)
Thanks @Takhoffman.
2026-04-21 05:17:55 +01:00
Peter Steinberger
5986431b02 fix: log pricing fetch timeout duration 2026-04-21 05:11:53 +01:00
Peter Steinberger
2641b052dc fix: align OpenAI reasoning effort handling 2026-04-21 04:58:31 +01:00
Peter Steinberger
e1d7e2e8a2 test: harden parallels package smokes 2026-04-21 04:32:12 +01:00
Peter Steinberger
6f5b7120b8 fix: trim windows dev update preflight 2026-04-21 04:32:12 +01:00
Peter Steinberger
817f861167 fix: isolate bundled plugin runtime deps 2026-04-21 04:32:12 +01:00
Peter Steinberger
201bf85ce9 test: expand codex image fallback coverage (#65061) (thanks @zhulijin1991) 2026-04-21 04:20:22 +01:00
zhulijin1991
92e864a521 fix(image): respect configured provider for bare image overrides 2026-04-21 04:20:22 +01:00
zhulijin1991
15258921ee fix(codex): avoid re-exposing image tool on vision turns 2026-04-21 04:20:22 +01:00
Peter Steinberger
22bff819ab fix: strengthen agent completion bias 2026-04-21 04:19:26 +01:00
Peter Steinberger
6e2cbe3faf test(qa): add long-running release audit scenario 2026-04-21 04:14:46 +01:00
Peter Steinberger
e032d44179 test: bound installer e2e agent turns 2026-04-21 04:11:01 +01:00
Peter Steinberger
d7d1270ced build: keep a2ui bundle stable 2026-04-21 04:11:01 +01:00
Peter Steinberger
32434b5f81 test: align install smoke timeout assertion 2026-04-21 04:05:39 +01:00
Peter Steinberger
8e20e6584d fix: keep session maintenance helpers acyclic 2026-04-21 04:05:39 +01:00
Peter Steinberger
1ccc1bac6d test: keep agent delete session fixtures fresh 2026-04-21 04:05:39 +01:00
Peter Steinberger
075e835858 test: keep gateway session fixtures fresh 2026-04-21 04:05:39 +01:00
Peter Steinberger
b06ff2abf2 test: cover session maintenance defaults (#69404) (thanks @bobrenze-bot) 2026-04-21 04:05:39 +01:00
Heather Wilde Renze
6a21962552 fix(sessions): enforce maintenance by default and prune on load to prevent gateway OOM
Co-authored-by: bobrenze-bot <bobrenze-ops@gmail.com>
2026-04-21 04:05:39 +01:00
Omar Shahine
b5f25de352 bluebubbles: forward per-group systemPrompt into GroupSystemPrompt (#69198)
Forward per-group systemPrompt config into inbound context GroupSystemPrompt so configured group-specific behavioral instructions (for example threaded-reply and tapback conventions) are injected on every turn. Supports "*" wildcard fallback matching the existing requireMention pattern.

Closes #60665.

Co-authored-by: Omar Shahine <omarshahine@users.noreply.github.com>
2026-04-20 20:01:03 -07:00
Peter Steinberger
d1f7f69cd4 fix: serialize run-node artifact writes 2026-04-21 03:53:23 +01:00
Peter Steinberger
11e6575c69 test: add QA coverage scenarios 2026-04-21 03:53:23 +01:00
Peter Steinberger
0c26623a96 fix: correct tiered model pricing costs 2026-04-21 03:48:25 +01:00
Shakker
04d41aeae1 docs: add setup tui hatch changelog 2026-04-21 03:47:38 +01:00
Shakker
aae4b1b29d Fix setup TUI hatch terminal handoff (#69524)
* fix: relaunch setup tui in a fresh process

* fix: harden setup tui handoff

* fix: preserve tui hatch exit flow

* Revert "fix: preserve tui hatch exit flow"

This reverts commit f4f119a5a3.

* fix: let setup tui resolve gateway auth

* fix: support packaged tui relaunch

* fix: pin setup tui gateway target

* fix: preserve setup tui auth source
2026-04-21 03:45:57 +01:00
Peter Steinberger
bed2472121 fix: stabilize docker live checks 2026-04-21 03:45:32 +01:00
Peter Steinberger
9efd2d10e7 fix: snapshot session cost estimates (#69403) (thanks @MrMiaigi) 2026-04-21 03:44:25 +01:00
Peter Steinberger
9b42cd8728 test: fix cost snapshot PR checks 2026-04-21 03:44:25 +01:00
Dexter (Miaigi)
47bb5ddece fix(cost): snapshot estimatedCostUsd instead of accumulating (#69347)
The bug: three persist sites accumulated cost instead of snapshotting
it like tokens. This caused cost to be inflated 1x-72x on multi-persist
sessions because the same cumulative usage was added repeatedly.

Root cause: persistSessionUsageUpdate, updateSessionStoreAfterAgentRun,
and the cron isolated-agent run path all used:
  estimatedCostUsd = existingCost + runCost

But runCost was already computed from cumulative run usage, so this
added the same cost repeatedly on redundant persists.

Fix: snapshot cost directly like tokens already do:
  estimatedCostUsd = runCost

Files affected:
- src/auto-reply/reply/session-usage.ts
- src/agents/command/session-store.ts
- src/cron/isolated-agent/run.ts

Tests added:
- session-store.test.ts: verify cost is snapshotted, not accumulated
- session.test.ts: updated existing test to verify snapshot behavior

Fixes #69347
2026-04-21 03:44:25 +01:00
Peter Steinberger
5bc9d9cc5c fix: clear auto model overrides on reset (#69419) (thanks @sk7n4k3d) 2026-04-21 03:36:16 +01:00
Sk7n4k3d
0eb6f5d8bc session: clear auto-sourced model/auth overrides on /new and /reset 2026-04-21 03:36:16 +01:00
Peter Steinberger
215d5fb320 fix: clear auto-failover model overrides (#69365) (thanks @Chevron7Locked) 2026-04-21 03:35:16 +01:00
Kevin O'Neill
dc0e966ed2 fix(model-selection): address Codex review P2 feedback on auto-heal override clearing
Three corrections to the auto-failover self-healing introduced in the prior commit:

1. Reset in-memory provider/model to configured primary after clearing auto override.
   get-reply-directives.ts preloads provider/model from the stored override before
   calling createModelSelectionState, so clearing only session state still ran the
   current turn on the fallback. Now provider/model are reset to defaultProvider/
   defaultModel so this turn retries the primary immediately, not on the next turn.

2. Remove resetModelOverride = true from the auto-heal path. That flag triggers a
   "Model override not allowed for this agent" system event in
   applyInlineDirectiveOverrides, which is incorrect: the override was valid and set
   by the fallback loop — it just expired once the primary recovered. Auto-heal is
   not an allowlist violation.

3. Add a test case that verifies the in-memory reset when the caller pre-loads the
   fallback provider/model (simulating the get-reply-directives.ts preload path).

Known limitation (noted in comment): channel model overrides (channels.modelByChannel)
are skipped on the recovery turn because hasSessionModelOverride was true when they
were evaluated at preload time. They resume on the following turn once session state
is clear. Fixing this cleanly requires changes to the get-reply-directives preload
flow and is out of scope for this PR.
2026-04-21 03:35:16 +01:00
Kevin O'Neill
f2abe28d40 fix(model-selection): clear auto-failover overrides so primary is retried on each turn
When runWithModelFallback falls back to a secondary provider it writes
providerOverride/modelOverride/modelOverrideSource:"auto" to the session.
On subsequent turns createModelSelectionState read this stored override and
passed the fallback provider directly to runWithModelFallback, so the
configured primary was never retried — the session was permanently pinned to
the fallback even after the primary recovered.

Fix: at model-selection ingress, when the direct session override has
modelOverrideSource "auto" (set by a previous automatic fallback, not a user
/model command), clear the override and retry the configured primary. If the
primary is still down runWithModelFallback will fall back and re-set the auto
override for that turn. Once the primary recovers the override stays clear.

User-selected overrides (modelOverrideSource "user" or legacy undefined+model)
are preserved unchanged.

Covered by four new unit tests in model-selection.test.ts:
- auto-failover override cleared and primary retried
- user-selected override preserved
- legacy override without source field preserved
- parent-session auto-override applied to child (not cleared by child logic)
2026-04-21 03:35:16 +01:00
Peter Steinberger
76d72d48f3 fix: normalize minimal ollama provider config (#69370) (thanks @PratikRai0101) 2026-04-21 03:28:17 +01:00
Pratik Rai
8edf705238 chore: satisfy strict linters and patch Windows CI corepack bug 2026-04-21 03:28:17 +01:00
Pratik Rai
2549dfe59b fix(ollama): inject default config fields during normalization to unblock implicit discovery 2026-04-21 03:28:17 +01:00
Peter Steinberger
5c85624eeb Revert "ci: use Blacksmith checkout cache"
This reverts commit 43734b1dbd.
2026-04-21 03:21:48 +01:00
Peter Steinberger
0b9a1e94b7 docs: thank openai codex endpoint contributor (#69336) 2026-04-21 03:21:01 +01:00
Peter Steinberger
cbdd6a4cbb fix: let active memory recall failures degrade (#69485) (thanks @Magicray1217) 2026-04-21 03:20:25 +01:00
Magicray1217
3f90d92667 fix(active-memory): gracefully degrade on timeout instead of failing entire reply. Fixes #66849 2026-04-21 03:20:25 +01:00
methazoo
8a2d7f2541 fix(openai-codex): use /backend-api/codex/ base URL
OpenAI removed the /backend-api/responses alias on chatgpt.com server-side.
The OpenAI SDK appends /responses to the configured baseUrl, so OpenClaw's
current baseUrl ("https://chatgpt.com/backend-api") now resolves to
/backend-api/responses and hits a Cloudflare HTML 403 block page. The
provider's 403+HTML error classifier then surfaces this as an auth-scope
failure, triggering fruitless OAuth re-login loops for every GPT-5.4
sub-agent call.

- Point OPENAI_CODEX_BASE_URL at https://chatgpt.com/backend-api/codex
  (both the catalog constant and the sibling local constant in the provider).
- Extend isOpenAICodexBaseUrl to accept the new /codex segment while keeping
  the legacy path recognized so pre-existing user configs and persisted
  model metadata still round-trip through the normalizer correctly.
- Add positive-case test coverage for the new base URL; update existing
  normalization tests whose expected canonical output now includes /codex.

Verified with live curl using the exact OAuth access token stored by
OpenClaw: the /codex/responses path returns HTTP 200 with streaming SSE,
while the old /responses alias returns HTTP 403 HTML regardless of auth
headers. Scoped tests (base-url, openai-codex-provider, transport-policy,
openai-provider, index) pass; pnpm tsgo and pnpm build are clean.
2026-04-21 03:19:58 +01:00
Peter Steinberger
8150c363b5 fix: stabilize memory dreaming QA 2026-04-21 03:12:42 +01:00
Peter Steinberger
a9bef83a0c refactor: delegate bluebubbles conversation helpers 2026-04-21 03:12:42 +01:00
Peter Steinberger
bd0c9024a2 docs: document Kimi cost live smoke 2026-04-21 03:10:56 +01:00
Peter Steinberger
18269f0b88 fix: classify loopback shared-secret pairing (#69431) (thanks @SARAMALI15792) 2026-04-21 03:10:34 +01:00
SARAMALI15792
fb1a5a2c26 test(gateway): assert cli_container_local precedence over loopback fallback (#69397) 2026-04-21 03:10:34 +01:00
SARAMALI15792
8ef356d5c3 fix(gateway): classify loopback shared-secret clients as local for pairing (#69397) 2026-04-21 03:10:34 +01:00
Peter Steinberger
43734b1dbd ci: use Blacksmith checkout cache 2026-04-21 03:09:13 +01:00
Sliverp
b938e6398b feat: add tiered model pricing support (#67605)
Adds tiered model pricing support for cost tracking, keeps configured pricing ahead of cached catalog values, and includes latest Moonshot Kimi K2.6/K2.5 cost estimates.\n\nThanks @sliverp.
2026-04-21 03:02:57 +01:00
Peter Steinberger
8d747d20b8 test: split contract vitest shards 2026-04-21 03:01:08 +01:00
Peter Steinberger
525e66e513 fix(openai): use tagged GPT-5 prompt contract 2026-04-21 02:45:17 +01:00
Peter Steinberger
c910ddac38 test: add Kimi and Qianfan extension coverage 2026-04-21 02:41:26 +01:00
Peter Steinberger
82b8a4aab6 docs(openai): clarify GPT-5 prompt defaults 2026-04-21 02:36:16 +01:00
Peter Steinberger
ab03d4e037 fix(openai): default GPT-5 prompt overlay 2026-04-21 02:36:16 +01:00
Andrii Furmanets
b6a8759b29 fix(web-search): restore SecretRef runtime compatibility for bundled providers (#68424)
Adds missing compatibility runtime path metadata for bundled SecretRef-capable web-search providers and keeps the manifest registry covered by a regression test.\n\nThanks @afurm!
2026-04-21 02:34:24 +01:00
Peter Steinberger
f04185cc70 test: stabilize live media and gateway probes 2026-04-21 02:10:19 +01:00
Peter Steinberger
5ab26a8774 ci: extend checkout fetch timeout 2026-04-21 02:05:26 +01:00
aniaan
c8e5150fd4 feat(moonshot): default to Kimi K2.6 with K2.6-only thinking.keep support (#68816)
Merged via squash.

Prepared head SHA: ed54e02842
Co-authored-by: aniaan <40813941+aniaan@users.noreply.github.com>
Co-authored-by: odysseus0 <8635094+odysseus0@users.noreply.github.com>
Reviewed-by: @odysseus0
2026-04-20 18:04:49 -07:00
Peter Steinberger
a112903802 test: use synthetic auto reply fixtures 2026-04-21 01:49:30 +01:00
poisk
32e8bca02c fix(telegram): honor removeAckAfterReply for status reactions (#68067)
Thanks @poiskgit.
2026-04-21 01:47:20 +01:00
Peter Steinberger
60a1f01a3e test: use synthetic agent infra fixtures 2026-04-21 01:46:33 +01:00
Peter Steinberger
442da01db4 test: use synthetic program status fixtures 2026-04-21 01:44:03 +01:00
Peter Steinberger
969ca8511d test: use synthetic cli provider fixtures 2026-04-21 01:42:29 +01:00
Peter Steinberger
66665eea6d test: use synthetic status session fixtures 2026-04-21 01:40:29 +01:00
Peter Steinberger
6bb6cfc68e test: use synthetic plugin channel fixtures 2026-04-21 01:32:27 +01:00
Peter Steinberger
97e528ed54 test: use synthetic agent session fixtures 2026-04-21 01:24:34 +01:00
Peter Steinberger
9f2f89320e test: use synthetic infra channel fixtures 2026-04-21 01:21:05 +01:00
Peter Steinberger
14ceec27fa test: use synthetic config cron channel fixtures 2026-04-21 01:19:35 +01:00
Peter Steinberger
f50202ee95 test: use synthetic auto-reply channel fixtures 2026-04-21 01:18:05 +01:00
Peter Steinberger
f3b56165f5 docs(telegram): clarify polling stall tuning 2026-04-21 01:15:28 +01:00
Peter Steinberger
e8898bb6c1 test: use synthetic agent channel fixtures 2026-04-21 01:15:11 +01:00
Peter Steinberger
3f274006cd refactor: share oauth callback flow 2026-04-21 01:07:09 +01:00
Peter Steinberger
f85c0b7dc5 refactor: reuse shared local file access 2026-04-21 01:07:09 +01:00
Peter Steinberger
7b1f7b179f refactor: share thread binding lifecycle 2026-04-21 01:07:09 +01:00
Peter Steinberger
4ea8063203 refactor: reuse operator approval gateway lifecycle 2026-04-21 01:07:09 +01:00
Peter Steinberger
6c67339798 docs: note Codex approval default fix (#68721) (thanks @Lucenx9) 2026-04-21 01:06:36 +01:00
Lucenx9
758e83015b docs(codex): clarify approval override example 2026-04-21 01:06:36 +01:00
Lucenx9
d04f7e7ce7 fix(codex): default app-server approvals to on-request 2026-04-21 01:06:36 +01:00
Amine Harch el korane
8c05043eca fix(telegram): tune polling stall threshold
Raise the Telegram polling watchdog default from 90s to 120s and add bounded channels.telegram.pollingStallThresholdMs overrides, including per-account config.\n\nThanks @Vitalcheffe.
2026-04-21 01:03:04 +01:00
Peter Steinberger
660e4257a7 refactor: share codex auth bridge 2026-04-21 00:54:08 +01:00
Peter Steinberger
0647481c7c refactor: share ssrf policy merging 2026-04-21 00:54:08 +01:00
Peter Steinberger
7e28caa637 refactor: share fast mode normalization 2026-04-21 00:54:08 +01:00
Peter Steinberger
44ca47b2eb refactor: share allow-from store file reads 2026-04-21 00:54:08 +01:00
Peter Steinberger
bcd232467f ci: remove channel contract heartbeat 2026-04-21 00:53:50 +01:00
Peter Steinberger
3caf9faef5 test: use synthetic metadata channel labels 2026-04-21 00:52:52 +01:00
Peter Steinberger
71154bf3bf test: use synthetic approval pairing fixtures 2026-04-21 00:51:37 +01:00
Peter Steinberger
05835dd2d4 test: use synthetic heartbeat wake fixtures 2026-04-21 00:50:32 +01:00
Peter Steinberger
25428c4631 fix: keep Codex projector events isolated (#69072) (thanks @ayeshakhalid192007-dev) 2026-04-21 00:49:54 +01:00
ayeshakhalid192007-dev
f2f27775fb fix(codex/app-server): release session lane when projector throws on turn/completed 2026-04-21 00:49:54 +01:00
ayeshakhalid192007-dev
54a2a20447 test(codex): wait for initialize write before reading harness in models.test.ts 2026-04-21 00:49:54 +01:00
Peter Steinberger
874306a2ac test: use synthetic task delivery fixtures 2026-04-21 00:49:06 +01:00
Peter Steinberger
aecd709dfe test: use synthetic task channel fixtures 2026-04-21 00:47:12 +01:00
Peter Steinberger
a8b81fa8e5 fix: guard qqbot channel API fetch 2026-04-21 00:43:50 +01:00
Peter Steinberger
039d1010fe test: fix landing gate helpers 2026-04-21 00:43:50 +01:00
Peter Steinberger
46fdc7d610 docs: update changelog for #68310 2026-04-21 00:43:50 +01:00
Watchtower
dcb525de50 fix(pi-embedded-runner): gate silent-error retry on replay safety
Per @steipete review on #68310: the silent-error retry must not fire when the
failed attempt already recorded potential side effects (messaging tool sent,
cron add, or a mutating tool call that wasn't round-tripped as replay-safe).
Otherwise resubmission can duplicate those actions.

Adds `!attempt.replayMetadata.hadPotentialSideEffects` to the retry condition,
mirroring the gate used by resolveEmptyResponseRetryInstruction and the
planning-only / reasoning-only retry resolvers in run/incomplete-turn.ts.

Adds a new negative regression test:
  "does not retry when the failed attempt recorded side effects"
which reproduces the reviewer's repro — stopReason=error + output=0 + empty
content, but replayMetadata={hadPotentialSideEffects: true, replaySafe: false}.
Expected: no retry, surfaces incomplete-turn error. Confirmed locally.
2026-04-21 00:43:50 +01:00
Watchtower
5fb302ebf1 fix(pi-embedded-runner): retry silent stopReason=error turns (non-frontier models)
ollama/glm-5.1:cloud (and occasionally other models) can end a turn with
stopReason="error", usage.output=0, and empty content[] after a successful
tool-call sequence. The existing empty-response retry path in
src/agents/pi-embedded-runner/run/incomplete-turn.ts is gated on
isStrictAgenticSupportedProviderModel (gpt-5 family only), so non-frontier
models fall through to "incomplete turn detected" with payloads=0 and no
recovery. The user sees no reply and has to nudge.

Add a narrow, model-agnostic resubmission inside the attempt loop, placed
before the incompleteTurnText surface-to-user return:

  - stopReason === "error"
  - usage.output === 0
  - content.length === 0   (excludes reasoning-only error turns)
  - bounded by MAX_EMPTY_ERROR_RETRIES = 3

No instruction injection, no model gating; same prompt, same session
transcript (tool results already captured), just let the loop try again.

New test file run.empty-error-retry.test.ts covers:
  1. Retries for ollama/glm-5.1:cloud → succeeds on 2nd attempt.
  2. Caps at 3 retries → 4 total attempts → surfaces incomplete-turn error.
  3. Does NOT retry when output > 0 (preserve produced text).
  4. Does NOT retry when stopReason=stop + output=0 (NO_REPLY path).
  5. Retries for anthropic/claude-opus-4-7 too — model-agnostic.

Relates to #68281.
2026-04-21 00:43:50 +01:00
Peter Steinberger
982b1c9464 test(ci): reduce channel contract import cost 2026-04-21 00:40:07 +01:00
Peter Steinberger
92191d37e6 test: split chat view coverage 2026-04-21 00:35:58 +01:00
Peter Steinberger
503af7afa6 refactor: dedupe install scan skill spec 2026-04-21 00:32:42 +01:00
Peter Steinberger
1a834a0ff6 test: reuse runtime sidecar uniqueness helper 2026-04-21 00:32:42 +01:00
Peter Steinberger
883f66eef3 test: share provider catalog fixtures 2026-04-21 00:32:42 +01:00
Peter Steinberger
3b1ef4354f test: share streaming error response helper 2026-04-21 00:32:42 +01:00
Dale Yarborough
7b5527a74e fix(gateway): prevent 1006 errors from race condition in WebSocket upgrade (#43392)
Merged via squash.

Prepared head SHA: 0bca6d3512
Co-authored-by: dalefrieswthat <176454532+dalefrieswthat@users.noreply.github.com>
Co-authored-by: grp06 <1573959+grp06@users.noreply.github.com>
Reviewed-by: @grp06
2026-04-20 16:29:14 -07:00
Peter Steinberger
67719b3c28 test: share debug proxy reset helper 2026-04-21 00:24:18 +01:00
Peter Steinberger
da3f47ddd0 test: share generation live env helper 2026-04-21 00:24:18 +01:00
Peter Steinberger
a95b61560a test: dedupe reconnect drain fixtures 2026-04-21 00:24:17 +01:00
Peter Steinberger
897a7b794f refactor: dedupe tlon helpers 2026-04-21 00:24:17 +01:00
scoootscooob
f700ad32a8 providers: default Moonshot to Kimi 2.6 (#69477)
Merged via squash.

Prepared head SHA: 4d778d09d1
Co-authored-by: scoootscooob <167050519+scoootscooob@users.noreply.github.com>
Co-authored-by: scoootscooob <167050519+scoootscooob@users.noreply.github.com>
Reviewed-by: @scoootscooob
2026-04-20 16:15:29 -07:00
Peter Steinberger
74178b37be test: split chat status indicator coverage 2026-04-21 00:08:11 +01:00
Peter Steinberger
f2a46ec46f refactor: dedupe perplexity request headers 2026-04-21 00:06:19 +01:00
Peter Steinberger
594337698f refactor: dedupe qqbot helpers 2026-04-21 00:06:19 +01:00
Peter Steinberger
8e681123d8 refactor: dedupe synology chat tests 2026-04-21 00:06:19 +01:00
Peter Steinberger
28d6aa5514 refactor: reuse text runtime in google chat 2026-04-21 00:06:19 +01:00
Peter Steinberger
77a6187a70 fix(telegram): bound offset confirmation timeout (#50368) (thanks @boticlaw) 2026-04-21 00:04:15 +01:00
Daniel
45ffb6cc25 fix(telegram): add client-side timeout to #confirmPersistedOffset getUpdates 2026-04-21 00:04:15 +01:00
Peter Steinberger
a732b916f4 test: use synthetic media channel fixtures 2026-04-20 23:59:39 +01:00
Garry Tan
c8086b731a tasks: add detached task recovery hook before markLost (#69313)
Merged via squash.

Prepared head SHA: 24322af4f7
Co-authored-by: garrytan <19957+garrytan@users.noreply.github.com>
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Reviewed-by: @mbelinky
2026-04-21 00:58:20 +02:00
Peter Steinberger
871aa9d0b9 test: use synthetic ui channel fixtures 2026-04-20 23:54:59 +01:00
Peter Steinberger
73f36b0c80 test: use synthetic outbound dispatch fixtures 2026-04-20 23:49:39 +01:00
Peter Steinberger
59d18a13b7 refactor: reuse text runtime in nextcloud talk 2026-04-20 23:42:11 +01:00
Peter Steinberger
caf4766493 refactor: reuse media base64 helper in qqbot 2026-04-20 23:42:11 +01:00
Peter Steinberger
b8c02c64fb refactor: reuse shared string coercion in ui 2026-04-20 23:42:11 +01:00
Peter Steinberger
7ca649413a refactor: share env secret ref allowlist check 2026-04-20 23:42:11 +01:00
Peter Steinberger
3fd64772d6 test: use synthetic message media fixtures 2026-04-20 23:41:56 +01:00
chiyouYCH
2055e75f9f fix(memory-core): prevent dreaming-narrative session leaks (#66358) (#67023)
Merged via squash.

Prepared head SHA: 51f72b200c
Co-authored-by: chiyouYCH <26790612+chiyouYCH@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-04-20 15:41:11 -07:00
Peter Steinberger
a06f4d0808 test: use synthetic outbound message fixtures 2026-04-20 23:38:56 +01:00
Peter Steinberger
d0b69a2064 test: use synthetic message channel fixtures 2026-04-20 23:37:28 +01:00
Peter Steinberger
04cdc33731 test: fix unit coverage scope 2026-04-20 23:36:33 +01:00
Peter Steinberger
a216b4ebc3 test: merge system run path binding cases 2026-04-20 23:34:59 +01:00
Peter Steinberger
0094f76314 refactor: share plugin config issue formatting 2026-04-20 23:34:19 +01:00
Peter Steinberger
6464cf4756 refactor: share plugin package version lookup 2026-04-20 23:34:19 +01:00
Peter Steinberger
4fb2e2309e refactor: share timeout abort helper with matrix 2026-04-20 23:34:19 +01:00
Peter Steinberger
8b7418b127 refactor: share channel doctor alias normalization 2026-04-20 23:34:19 +01:00
Peter Steinberger
e2abd4bc62 test(ci): fix msteams and heartbeat red lanes 2026-04-20 23:33:49 +01:00
Peter Steinberger
1151d69bb8 test: use synthetic outbound routing fixtures 2026-04-20 23:33:25 +01:00
Peter Steinberger
68954f9c6c test: extract chat item builder coverage 2026-04-20 23:33:21 +01:00
Peter Steinberger
31d545260e test: merge acp manager retry cases 2026-04-20 23:33:21 +01:00
Peter Steinberger
f6c9912e37 test: use synthetic outbound binding fixtures 2026-04-20 23:29:43 +01:00
Peter Steinberger
7e8b58cb25 test: use synthetic outbound utility fixtures 2026-04-20 23:27:18 +01:00
Peter Steinberger
b07c40a5a8 test: merge system run denial matrices 2026-04-20 23:26:37 +01:00
Peter Steinberger
11eae6b2d8 test: use synthetic message action fixtures 2026-04-20 23:25:08 +01:00
Peter Steinberger
c1be9ac0a7 test: move chat tool disclosure coverage 2026-04-20 23:22:26 +01:00
Peter Steinberger
c561e4c11b test: use synthetic outbound core fixtures 2026-04-20 23:20:51 +01:00
Peter Steinberger
f1a544ef6d perf: avoid sort-for-single selection 2026-04-20 23:20:31 +01:00
Peter Steinberger
2d010306e4 test: split grouped chat rendering coverage 2026-04-20 23:17:21 +01:00
Peter Steinberger
3eb48ec3e7 refactor(telegram): split polling liveness tracking 2026-04-20 23:16:55 +01:00
Peter Steinberger
431e33b567 test: share channel directory id assertions 2026-04-20 23:15:58 +01:00
Peter Steinberger
da5a6b68bd refactor: share ssrf base url policy 2026-04-20 23:15:58 +01:00
Peter Steinberger
85450b3da9 test: share msteams message handler mocks 2026-04-20 23:15:58 +01:00
Peter Steinberger
3f8ac729f2 test: share msteams runtime setup 2026-04-20 23:15:58 +01:00
Peter Steinberger
72571f0d38 test: decouple outbound target tests from bundled plugins 2026-04-20 23:14:50 +01:00
Peter Steinberger
e0c01bf956 perf: trim hot path allocations 2026-04-20 23:13:20 +01:00
Peter Steinberger
9f9235b692 test(channels): shard registry-backed contracts 2026-04-20 23:10:46 +01:00
ly85206559
35cb59e3b5 fix(agents): reapply compaction settings after resource loader reload (#65602) (#67146)
Merged via squash.

Prepared head SHA: 4978f7b8b5
Co-authored-by: ly85206559 <12526624+ly85206559@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-04-20 15:10:24 -07:00
Peter Steinberger
d7c7905a52 refactor: share provider polling helper 2026-04-20 23:04:10 +01:00
Peter Steinberger
eb94d3af94 test: share provider stream capture helper 2026-04-20 23:04:10 +01:00
Peter Steinberger
614d0348a5 test: share msteams sso handler setup 2026-04-20 23:04:10 +01:00
Peter Steinberger
8f4920e2eb refactor: share line sdk types 2026-04-20 23:04:10 +01:00
Peter Steinberger
60fea81cf1 fix(telegram): harden polling transport liveness (#69476)
* fix(telegram): release undici dispatchers via TelegramTransport.close()

TelegramTransport now exposes an explicit close() that destroys every
owned undici dispatcher (default Agent plus lazily-created IPv4 and
IP-pinned fallback Agents) and the TCP sockets they hold. Dispatcher
constructors are also given bounded keep-alive defaults
(keepAliveTimeout, keepAliveMaxTimeout, connections, pipelining) as a
defence-in-depth layer so the pool cannot grow unbounded even if a
caller forgets to call close().

Without this, every transport that went through a fallback retry left
its fallback Agents anchored forever in a closure; long-running polling
sessions accumulated hundreds of ESTABLISHED keep-alive sockets to
api.telegram.org, saturating the per-IP quota on upstream forward
proxies and making the currently-active outbound node time out while
every other node still tested healthy.

Mock dispatchers in fetch.test.ts gain destroy() spies so the close()
chain is assertable. Call sites that built caller-owned transports from
globalThis.fetch (delivery.resolve-media, test helpers) return an async
no-op close(), matching the new required surface.

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

* fix(telegram): dispose polling transport on shutdown and dirty rebuild

Every recoverable network error and stall-watchdog trip sets
TelegramPollingTransportState.#transportDirty so the next polling
cycle rebuilds the transport inside acquireForNextCycle(). Previously
the rebuild simply overwrote the field, leaving the old transport's
keep-alive sockets anchored in the now-unreferenced dispatcher — the
polling loop has no natural GC point for these resources, and Node's
object GC never touches OS-level sockets.

acquireForNextCycle() now closes the previous transport (fire-and-
forget so the polling cycle is not blocked by a slow destroy) before
swapping in the rebuilt one. dispose() is a new method that the owning
TelegramPollingSession calls from the finally block of runUntilAbort(),
so a single transport is always tied to a single polling session
lifetime. After dispose(), acquireForNextCycle() returns undefined to
prevent zombie rebuilds.

Under high sustained polling traffic over long-lived sessions, this is
what stops the per-gateway connection count to api.telegram.org from
growing indefinitely and saturating upstream proxy quotas.

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

* docs(changelog): note Telegram undici dispatcher lifecycle fix

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

* fix(telegram): disable HTTP/2 for all Telegram polling dispatchers

Undici 8 enables HTTP/2 ALPN by default, but Telegram's long-polling
connections stall on Windows due to IPv6 + H2 multiplexing issues. The
core fetch-guard already sets allowH2:false for guarded paths, but the
Telegram extension creates its own Agent/ProxyAgent/EnvHttpProxyAgent
instances directly from undici without this flag.

Apply allowH2:false to all dispatcher constructors in the Telegram
transport layer, matching the approach used in src/infra/net/undici-runtime.ts.

Fixes #66885

* fix: avoid false telegram polling stall restarts

* fix(telegram): publish polling health liveness

---------

Co-authored-by: Ethan Chen <ethanbit@qq.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Magicray1217 <magicray1217@users.noreply.github.com>
Co-authored-by: aoao <aoao@openclaw>
2026-04-20 23:03:57 +01:00
Peter Steinberger
250c756fb4 test: share directive reply mock payloads 2026-04-20 22:51:16 +01:00
Peter Steinberger
ca2c9fef8c test: share gateway live client helpers 2026-04-20 22:51:16 +01:00
Peter Steinberger
c55e1f7566 test: share gateway broadcaster fixtures 2026-04-20 22:51:16 +01:00
Peter Steinberger
40eae3cbb7 refactor: share ui select option helper 2026-04-20 22:51:16 +01:00
Peter Steinberger
412d6cf21b test(ui): tighten app tool stream event helper type 2026-04-20 22:50:45 +01:00
Peter Steinberger
b7e5d9a96e test: decouple outbound tests from bundled plugins 2026-04-20 22:44:38 +01:00
Peter Steinberger
27c52f8062 ci: keep channel contract shards alive 2026-04-20 22:42:57 +01:00
Peter Steinberger
2003ab736a test: share app render settings fixture 2026-04-20 22:39:51 +01:00
Peter Steinberger
171077037a test: share tool stream event helpers 2026-04-20 22:39:07 +01:00
Peter Steinberger
b33ce7a371 refactor: share skills dialog opener 2026-04-20 22:37:50 +01:00
Peter Steinberger
e0621bd7b9 test: share nodes device render helper 2026-04-20 22:37:11 +01:00
Peter Steinberger
9dcbf911a0 refactor: share ui approval event handling 2026-04-20 22:36:23 +01:00
Peter Steinberger
1f951f36fd test: remove unused agent runtime support 2026-04-20 22:36:22 +01:00
Peter Steinberger
3e6758f55a test: share provider usage runtime mocks 2026-04-20 22:36:22 +01:00
Peter Steinberger
c63fb08f81 test: share approval native runtime stubs 2026-04-20 22:36:22 +01:00
Peter Steinberger
b248899878 perf(channels): narrow slow bundled channel entry imports 2026-04-20 22:34:11 +01:00
Peter Steinberger
88d97c55c7 test: share secrets runtime file fixture 2026-04-20 22:28:49 +01:00
Peter Steinberger
9cba6672d6 refactor: share inactive web provider warnings 2026-04-20 22:28:49 +01:00
Peter Steinberger
19525e1dd0 test: share media auth snapshot setup 2026-04-20 22:28:49 +01:00
Peter Steinberger
cf4354ad83 test: share plugin secret collector setup 2026-04-20 22:28:49 +01:00
Peter Steinberger
99b933f160 perf(gateway): skip cold startup sidecars until needed 2026-04-20 22:24:37 +01:00
Agustin Rivera
6d3ce088da fix(gateway): require read scope for chat websocket broadcasts (#69373)
* fix(gateway): guard chat-class websocket broadcasts

* fix(gateway): harden broadcast event scope guards

* fix(gateway): keep websocket seq per recipient

* fix(gateway): let nodes receive voicewake broadcasts

* fix(gateway): preserve seq gaps for dropped broadcasts

* fix(gateway): drop USER.md worklog from PR

* fix(gateway): add scope guard docstring for pairing exclusion

* fix(gateway): allow plugin.* broadcast events for write/admin scopes

- Plugin-defined gateway broadcast events (plugin.* namespace) are now
  delivered to operator.write and operator.admin scoped clients
- This preserves the ability for plugins to broadcast custom events
  through context.broadcast() without requiring explicit enumeration
- Explicit plugin.* entries in EVENT_SCOPE_GUARDS take precedence
  (e.g., plugin.approval.* uses APPROVALS_SCOPE)

* docs(changelog): note chat broadcast read-scope gating (#69373)

---------

Co-authored-by: Devin Robison <drobison@nvidia.com>
2026-04-20 15:24:34 -06:00
Peter Steinberger
382201acf0 test: share gateway password inactive assertion 2026-04-20 22:21:34 +01:00
Peter Steinberger
3349cc5ea0 refactor: share approval native adapter types 2026-04-20 22:21:34 +01:00
Peter Steinberger
df3374d11d test: share provider allowlist fallback setup 2026-04-20 22:21:34 +01:00
Peter Steinberger
f197ca503a test: share gateway pairing authz setup 2026-04-20 22:21:34 +01:00
B.K.
4b4631cd48 fix(bootstrap): close silent 10% content gap in trim ratios (openclaw#69114)
Verified:
- pnpm test src/agents/pi-embedded-helpers.buildbootstrapcontextfiles.test.ts
- pnpm test src/agents/subagent-registry.steer-restart.test.ts

Co-authored-by: B.K. <263413630+BKF-Gitty@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-04-20 16:20:10 -05:00
Peter Steinberger
de404de321 test: share secrets exec resolver fixtures 2026-04-20 22:17:34 +01:00
Peter Steinberger
8d1e734213 test: share cron store rename spy helper 2026-04-20 22:17:34 +01:00
Peter Steinberger
8f4ec8e6ce test: share msteams revoke fallback helper 2026-04-20 22:17:34 +01:00
Peter Steinberger
45f1d9cb0f refactor: share feishu security audit contract 2026-04-20 22:17:33 +01:00
Peter Steinberger
98ba5fd952 fix(extensions): type web search onboarding scopes 2026-04-20 22:14:50 +01:00
Peter Steinberger
d16634be57 test(extensions): keep generation helper out of discovery 2026-04-20 22:09:16 +01:00
Peter Steinberger
0324114293 refactor: share exa web search provider base 2026-04-20 22:05:39 +01:00
Peter Steinberger
e96a4e8fc3 refactor: share perplexity web search provider base 2026-04-20 22:05:39 +01:00
Peter Steinberger
1680c86b6c refactor: share duckduckgo web search provider base 2026-04-20 22:05:39 +01:00
Peter Steinberger
692733ead4 refactor: share anthropic vertex provider helpers 2026-04-20 22:05:39 +01:00
Peter Steinberger
7abf2e0574 test(bluebubbles): dedupe runtime type import 2026-04-20 22:00:02 +01:00
Peter Steinberger
8f6cf2afdd test(telegram): move ingest schema coverage 2026-04-20 21:59:41 +01:00
Peter Steinberger
75c8c4c08c test(bluebubbles): import runtime type 2026-04-20 21:58:34 +01:00
Peter Steinberger
8134fe737c test(extensions): move legacy schema assertions 2026-04-20 21:58:34 +01:00
Peter Steinberger
29a5ab9632 refactor: share feishu api error formatting 2026-04-20 21:58:25 +01:00
Peter Steinberger
66fb12d18a test: share generation live env helper 2026-04-20 21:58:25 +01:00
Peter Steinberger
972d01965c test: share feishu card assertions 2026-04-20 21:58:25 +01:00
Peter Steinberger
cb869c823e test: share discord native command fixtures 2026-04-20 21:58:25 +01:00
Peter Steinberger
22f2de0c4f test(extensions): fix shared test helper contracts 2026-04-20 21:56:17 +01:00
Peter Steinberger
3a7a1f156d test(extensions): move remaining channel schema tests 2026-04-20 21:54:49 +01:00
Peter Steinberger
30eb467ec8 test: share msteams attachment fixtures 2026-04-20 21:52:13 +01:00
Peter Steinberger
eb5f33a5c6 test: share lancedb temp fixtures 2026-04-20 21:52:13 +01:00
Peter Steinberger
5272a94a19 test: share bluebubbles media fixtures 2026-04-20 21:52:13 +01:00
Peter Steinberger
51da1f70fa test: share msteams message handler fixtures 2026-04-20 21:52:13 +01:00
Peter Steinberger
49b2ec1e2e test(extensions): move config regression coverage 2026-04-20 21:51:34 +01:00
Peter Steinberger
5927eb73ec test(xai): accept nullable stream wrapper 2026-04-20 21:50:54 +01:00
Peter Steinberger
2f4cf2d67d test(extensions): move channel config schema coverage 2026-04-20 21:47:13 +01:00
Peter Steinberger
88cd163a8d test: share xai stream payload fixtures 2026-04-20 21:46:35 +01:00
Peter Steinberger
2c532eafa7 test: split skills download write fixture 2026-04-20 21:46:35 +01:00
Peter Steinberger
d8745d928d test: share browser facade fixtures 2026-04-20 21:46:35 +01:00
Peter Steinberger
ba331014be test: share plugin sdk facade fixtures 2026-04-20 21:46:35 +01:00
Peter Steinberger
9b8e549263 test(comfy): narrow shared request body helper 2026-04-20 21:44:46 +01:00
Peter Steinberger
43d5255998 test: remove command extension mocks 2026-04-20 21:43:32 +01:00
Peter Steinberger
1f816b1561 test: share plugin install archive fixtures 2026-04-20 21:40:16 +01:00
Peter Steinberger
134a56f3e4 test: share openai codex oauth fixtures 2026-04-20 21:40:16 +01:00
Peter Steinberger
56529d7850 refactor: share ollama provider builder 2026-04-20 21:40:16 +01:00
Peter Steinberger
1bd1cac23f test: share comfy provider fixtures 2026-04-20 21:40:16 +01:00
Peter Steinberger
40db9734c4 ci: start windows checks earlier 2026-04-20 21:39:47 +01:00
Peter Steinberger
2b6acf9c92 test: drop matrix contract runtime mock 2026-04-20 21:39:25 +01:00
Peter Steinberger
e4a21b35f5 test(feishu): normalize dispatcher deliver promise 2026-04-20 21:33:55 +01:00
Peter Steinberger
fa7da15be1 test: share google oauth fetch fixture 2026-04-20 21:33:44 +01:00
Peter Steinberger
3a8aed4c77 test: share anthropic stream wrapper fixtures 2026-04-20 21:33:44 +01:00
Peter Steinberger
d7ff1ceb29 test: share minimax image fixtures 2026-04-20 21:33:44 +01:00
Peter Steinberger
47163f6bb7 test: share ollama payload wrapper fixture 2026-04-20 21:33:44 +01:00
Peter Steinberger
dd9792662f test: mock gateway web channel seam 2026-04-20 21:30:54 +01:00
Peter Steinberger
f3e6eeb643 perf(gateway): fast path startup secrets 2026-04-20 21:30:06 +01:00
Peter Steinberger
5a289f5cad test: share openai plugin fixtures 2026-04-20 21:28:25 +01:00
Peter Steinberger
027f4b4eda test: share openai codex cli auth fixture 2026-04-20 21:28:25 +01:00
Peter Steinberger
f5a0222af2 test: share feishu dispatcher fixture 2026-04-20 21:28:25 +01:00
Peter Steinberger
03b10e97d3 test: share feishu docx batch fixture 2026-04-20 21:28:25 +01:00
Peter Steinberger
1a4917c3d3 test: mock web channel runtime boundary 2026-04-20 21:26:09 +01:00
Peter Steinberger
5fa11582ae test: share feishu reaction fixtures 2026-04-20 21:24:19 +01:00
Peter Steinberger
2247c8ea91 test: share lmstudio model load fixtures 2026-04-20 21:22:46 +01:00
Peter Steinberger
d4f602bdff test: share lmstudio stream preload helpers 2026-04-20 21:21:26 +01:00
Peter Steinberger
226f0427bc test: share nostr admin scope assertions 2026-04-20 21:20:48 +01:00
Peter Steinberger
17b46d5d56 test: share ollama stream event harness 2026-04-20 21:17:52 +01:00
Peter Steinberger
c12500cf50 test: share qa channel harness 2026-04-20 21:16:24 +01:00
Peter Steinberger
512dc4f2b1 test: share memory session search setup 2026-04-20 21:14:46 +01:00
Peter Steinberger
d8b3de39b0 test: share memory backend config helpers 2026-04-20 21:13:11 +01:00
Peter Steinberger
58c92e81b1 test: merge pairing allowlist read coverage 2026-04-20 21:09:30 +01:00
Peter Steinberger
eb6a0f3529 test: trim runtime approval matrix duplicates 2026-04-20 21:08:16 +01:00
Peter Steinberger
01074e376c test: trim chat action render case 2026-04-20 21:05:00 +01:00
Peter Steinberger
c28a3d9768 perf(test): render chat indicators directly 2026-04-20 21:04:07 +01:00
Peter Steinberger
dc4e90bbd2 fix(qa-lab): restore transport helper contracts 2026-04-20 21:03:34 +01:00
Peter Steinberger
90bc577a12 refactor: share matrix qa event matcher 2026-04-20 21:03:13 +01:00
Peter Steinberger
fffb7d3d7a perf(test): avoid proxy runtime dynamic import 2026-04-20 21:02:13 +01:00
Peter Steinberger
3df9a60b0b perf(test): trim hotspot coverage duplication 2026-04-20 21:01:06 +01:00
Peter Steinberger
fbba29319f refactor: share qa credential helpers 2026-04-20 21:00:42 +01:00
Peter Steinberger
0882b85d5a refactor: share qa runtime helpers 2026-04-20 20:58:28 +01:00
Peter Steinberger
26fdff9e03 test: trim chat view render cases 2026-04-20 20:57:09 +01:00
Peter Steinberger
6fbfb8b7a3 test: share character eval fixtures 2026-04-20 20:56:30 +01:00
Peter Steinberger
958ca2ebec test(extensions): move registry channel contracts 2026-04-20 20:55:39 +01:00
Peter Steinberger
9c9ca5f431 test(extensions): move channel contracts to owners 2026-04-20 20:55:39 +01:00
Peter Steinberger
0f1ce47033 test(extensions): move provider contracts to owners 2026-04-20 20:55:39 +01:00
Peter Steinberger
f587887122 test: share qa temp dir harness 2026-04-20 20:54:45 +01:00
Peter Steinberger
f5305afcfb test: speed changed lanes and channel contracts 2026-04-20 20:53:38 +01:00
Peter Steinberger
d8cf947f6b perf(gateway): streamline startup sidecars 2026-04-20 20:52:42 +01:00
Peter Steinberger
7896a44365 test: trim duplicate tool card renders 2026-04-20 20:52:33 +01:00
Peter Steinberger
aa0957c4dd test: share messaging plugin fixtures 2026-04-20 20:52:31 +01:00
Peter Steinberger
553cc80027 perf(test): flush mcp notifications directly 2026-04-20 20:51:13 +01:00
Tortes
3d19f018ab fix(plugins): prefer higher-precedence manifests for duplicate plugin ids
Keep only the highest-precedence manifest when distinct discovered plugins share an id, while preserving the newer installed-global precedence behavior on main. Lower-precedence duplicates now warn against the ignored manifest source instead of loading as disabled plugin entries.

Thanks @Tortes.
2026-04-20 20:49:05 +01:00
Peter Steinberger
2d55e0a00b perf(test): avoid app chat slash reload 2026-04-20 20:48:57 +01:00
Peter Steinberger
8aaea14209 refactor: share matrix runtime helpers 2026-04-20 20:48:14 +01:00
Peter Steinberger
5945d4145a fix(test): keep browser vitest mock out of runtime scan 2026-04-20 20:45:42 +01:00
Peter Steinberger
1bd92975c2 test: share matrix runtime fixtures 2026-04-20 20:43:39 +01:00
Edward Abrams
8595e6c872 fix(plugins): preserve memory capability across snapshot plugin loads
Preserve the active memory capability when non-activating plugin snapshot loads run, and add a regression test.\n\nThanks @zeroaltitude.
2026-04-20 20:43:08 +01:00
Peter Steinberger
a6aa028626 perf(test): trim hotspot integration paths 2026-04-20 20:41:08 +01:00
Peter Steinberger
e3edd408aa test: share matrix maintenance fixtures 2026-04-20 20:40:20 +01:00
Peter Steinberger
84d8cb0826 test: share browser security mock 2026-04-20 20:36:23 +01:00
Peter Steinberger
44082acef5 perf(test): reuse node host runtime fixtures 2026-04-20 20:34:55 +01:00
Peter Steinberger
d033662145 test: share browser cdp fixtures 2026-04-20 20:33:22 +01:00
Peter Steinberger
8a09b40cb2 perf(test): trim test teardown waits 2026-04-20 20:30:16 +01:00
Peter Steinberger
28f7745a5e test: share browser route fixtures 2026-04-20 20:30:06 +01:00
Peter Steinberger
978e379079 test: stabilize gateway reload test gates 2026-04-20 20:28:48 +01:00
Peter Steinberger
0b948b51ae test: isolate provider auth alias mocks 2026-04-20 20:28:48 +01:00
Peter Steinberger
0355bc2b0d test: suppress session lock watchdog noise 2026-04-20 20:28:48 +01:00
Peter Steinberger
5f94c2592d test: stabilize directory id sorting 2026-04-20 20:28:48 +01:00
Peter Steinberger
4bbd1dc0d5 test: silence doctor manifest repair notes 2026-04-20 20:28:48 +01:00
Peter Steinberger
6e58da9750 build: stabilize a2ui bundle inputs 2026-04-20 20:28:48 +01:00
Peter Steinberger
18021818ce test: avoid prototype patching in harnesses 2026-04-20 20:28:48 +01:00
Peter Steinberger
975b989de6 test: reduce module reload churn 2026-04-20 20:28:47 +01:00
Peter Steinberger
911cfe2adc refactor: use structured clone for local copies 2026-04-20 20:28:47 +01:00
Peter Steinberger
9fa204003f perf: cache daemon gateway probe import 2026-04-20 20:28:47 +01:00
Peter Steinberger
497a126645 test: narrow matrix cross-signing mock type 2026-04-20 20:28:00 +01:00
Peter Steinberger
25e3a6078d test: share matrix message edit fixture 2026-04-20 20:27:22 +01:00
Peter Steinberger
577dc17d0c test: share matrix sync lifecycle harness 2026-04-20 20:25:38 +01:00
Peter Steinberger
135578b4e9 test: share matrix crypto reset assertions 2026-04-20 20:23:29 +01:00
JC
ebb53d8dab docs(plugins): add Prometheus Avatar community plugin (#52752)
Add Prometheus Avatar to the community plugins docs.\n\nThanks @jc-myths.
2026-04-20 20:22:37 +01:00
Gökdeniz Kaymak
c9d3c3022f docs(plugins): add Apify community plugin (#45263)
Add Apify to the community plugins docs.\n\nThanks @protoss70.
2026-04-20 20:22:31 +01:00
dependabot[bot]
c14594cd93 build(deps): bump debian sandbox image digest (#39403)
Bump debian sandbox image digest.\n\nThanks @dependabot.
2026-04-20 20:22:13 +01:00
dependabot[bot]
fb74a7f0a4 build(deps): bump actions/checkout from 4 to 6 (#61768)
Bump actions/checkout from 4 to 6.\n\nThanks @dependabot.
2026-04-20 20:22:08 +01:00
Peter Steinberger
50b9526951 test: share matrix harness fixtures 2026-04-20 20:22:00 +01:00
Agustin Rivera
fe30b31a97 fix(gateway): tighten gateway config mutation guard (#69377)
* fix(gateway): tighten gateway config mutation guard

Co-authored-by: zsx <git@zsxsoft.com>

* fix(gateway): cover unkeyed guard entries

Co-authored-by: zsx <git@zsxsoft.com>

* fix(gateway): preserve array entry order in guard

Co-authored-by: zsx <git@zsxsoft.com>

* fix(gateway): close remaining review gaps

* fix(gateway): stabilize dangerous flag ids

* fix(gateway): log comment resolution

* fix(gateway): block id removal stripping protected overrides

* fix(gateway): drop review worklog

* docs(changelog): note gateway tool config mutation guard expansion (#69377)

---------

Co-authored-by: zsx <git@zsxsoft.com>
Co-authored-by: Devin Robison <drobison@nvidia.com>
2026-04-20 13:21:20 -06:00
Gustavo Madeira Santana
9641f9ebbb Reject array-shaped cron state sidecars 2026-04-20 15:20:20 -04:00
Peter Steinberger
83bb7e8aab test: share matrix qa summary fixtures 2026-04-20 20:19:57 +01:00
Peter Steinberger
33254ca696 test: share matrix restart replay helpers 2026-04-20 20:18:21 +01:00
Peter Steinberger
704feda9da ci: split channel contract shards further 2026-04-20 20:17:57 +01:00
Peter Steinberger
24d50acc70 docs: clarify dependency parser advisory triage 2026-04-20 20:13:37 +01:00
Peter Steinberger
29a1c4f46c test: narrow auto-reply skill secret fixture 2026-04-20 20:12:34 +01:00
Peter Steinberger
aa52d1be42 test: share auto-reply command fixtures 2026-04-20 20:07:37 +01:00
Peter Steinberger
bccd429f70 test: type version fast path commit resolver mock 2026-04-20 20:07:11 +01:00
Peter Steinberger
41cce9ea79 perf(test): reuse run-plan fixture root 2026-04-20 20:06:49 +01:00
Peter Steinberger
59657913fd perf(test): prune duplicate memory host tests 2026-04-20 20:05:37 +01:00
Peter Steinberger
88de927a0c perf(test): dedupe memory host mirror tests 2026-04-20 20:02:19 +01:00
Peter Steinberger
a2f158e5ed test: share subagent command fixtures 2026-04-20 19:57:37 +01:00
Peter Steinberger
8e519aa826 perf(test): slim entry and chat tests 2026-04-20 19:55:44 +01:00
Peter Steinberger
43fa394b83 test: share get-reply edge fixtures 2026-04-20 19:55:26 +01:00
Sebastian B Otaegui
f48d040bf5 feat: send compaction start and completion notices (#67830)
Merged via squash.

Prepared head SHA: abedf6cf11
Co-authored-by: feniix <91633+feniix@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-04-20 11:55:17 -07:00
Peter Steinberger
1603577dfd test: share get-reply hook fixtures 2026-04-20 19:53:41 +01:00
Peter Steinberger
a74ba90196 test: share get-reply fixtures 2026-04-20 19:51:25 +01:00
Agustin Rivera
5a12f30441 Limit paired-device pairing actions to the caller device (#69375)
* fix(pairing): restrict paired-device pairing actions

* fix(pairing): close device authz review gaps

* docs(changelog): note device-pair scoping for non-admin paired devices (#69375)

---------

Co-authored-by: Devin Robison <drobison@nvidia.com>
2026-04-20 12:50:39 -06:00
Peter Steinberger
82e6501f89 test: share auto-reply session fixtures 2026-04-20 19:48:04 +01:00
Peter Steinberger
cf7b906216 perf: defer unconfigured gateway hooks 2026-04-20 19:47:35 +01:00
Peter Steinberger
ee54a8d298 test: share gateway shared auth ws helpers 2026-04-20 19:44:50 +01:00
Agustin Rivera
018494fa3e fix(dotenv): reserve workspace OPENCLAW env namespace (#69376) 2026-04-20 12:43:30 -06:00
Peter Steinberger
e1818116bc test: share gateway runtime scope fixture 2026-04-20 19:42:51 +01:00
Peter Steinberger
87eda35bcb test: share gateway reload write fixtures 2026-04-20 19:41:26 +01:00
Peter Steinberger
c99a13f72c test: share channel config dm resolver fixture 2026-04-20 19:40:04 +01:00
Peter Steinberger
c4f628085d build: refresh a2ui bundle 2026-04-20 19:38:59 +01:00
Peter Steinberger
905d2d8062 test: share qa runtime fixtures 2026-04-20 19:38:34 +01:00
Peter Steinberger
b3a0da7c5e test(extensions): split outbound payload contracts 2026-04-20 19:37:20 +01:00
Kris Wu
0a761a9eac fix(agents): rename auto_compaction_start/end to compaction_start/end [AI] (#67713)
Merged via squash.

Prepared head SHA: 03e0c69038
Co-authored-by: mpz4life <32388289+mpz4life@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-04-20 11:35:40 -07:00
Peter Steinberger
456489974d test: share jiti loader cache fixture 2026-04-20 19:35:31 +01:00
Peter Steinberger
020a49de41 test: share plugin setup registry fixtures 2026-04-20 19:34:15 +01:00
Peter Steinberger
dd409eec80 perf(test): mock audit plugin policy deps 2026-04-20 19:33:43 +01:00
Peter Steinberger
f48f0957f5 test: share channel setup fixtures 2026-04-20 19:32:19 +01:00
Peter Steinberger
dab1be48fc perf(test): merge chat and system run cases 2026-04-20 19:32:05 +01:00
Peter Steinberger
bdc5a96db6 test: keep cron executor delivery default typed 2026-04-20 19:30:34 +01:00
Peter Steinberger
1f4bb2df82 test: share bundled channel guard fixtures 2026-04-20 19:29:36 +01:00
Peter Steinberger
a292cbf46f docs: clarify optional Docker sandboxing 2026-04-20 19:27:45 +01:00
Peter Steinberger
434e3d81f3 test: share session conversation fallback fixtures 2026-04-20 19:27:07 +01:00
Dewaldt Huysamen
263a190fc9 Context engine/plugins: accept third-party engines whose info.id differs from registered slot id (#66678)
Merged via squash.

Prepared head SHA: 988229cd8f
Co-authored-by: GodsBoy <5792287+GodsBoy@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-04-20 11:26:38 -07:00
Peter Steinberger
40c9d0affc test: share cron message tool fixtures 2026-04-20 19:25:22 +01:00
Peter Steinberger
a3827a93a9 test: share doctor bundled plugin fixture 2026-04-20 19:24:04 +01:00
Feelw00
4be6ff9d5f feat(cron): split jobs.json into config and runtime state files (#63105)
Merged via squash.

Prepared head SHA: 470bb2561f
Co-authored-by: Feelw00 <45638585+Feelw00@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-04-20 14:23:18 -04:00
Peter Steinberger
33e63d914b test: share cron delivery job fixture 2026-04-20 19:22:41 +01:00
Peter Steinberger
16985aba4e test: type skill scanner matrix cases 2026-04-20 19:21:24 +01:00
Peter Steinberger
bcf17447f0 test: share execFile builtin mock 2026-04-20 19:20:46 +01:00
Peter Steinberger
901f2f38fc test: share mcp bridge gateway setup 2026-04-20 19:19:35 +01:00
Peter Steinberger
f8bb35ead0 test: share media server harness 2026-04-20 19:18:27 +01:00
Peter Steinberger
5934a8eacc test: share task executor cancellation fixtures 2026-04-20 19:16:31 +01:00
Peter Steinberger
7aebac697e ci: split remaining slow test shards 2026-04-20 19:15:45 +01:00
Peter Steinberger
9a71595d97 test: share tui session action setup 2026-04-20 19:14:01 +01:00
Peter Steinberger
ed526a2121 perf(test): merge skill scanner matrix cases 2026-04-20 19:11:45 +01:00
Peter Steinberger
85c1ff6ea4 perf(test): merge system run plan matrix tests 2026-04-20 19:09:51 +01:00
Peter Steinberger
02a6e78531 test: share spawnSync builtin mock 2026-04-20 19:08:24 +01:00
Peter Steinberger
305d04b758 perf(test): move temp path guard to check 2026-04-20 19:07:29 +01:00
Peter Steinberger
8ea7866356 test: share video provider options fixture 2026-04-20 19:04:47 +01:00
Peter Steinberger
ed1716cd9d test: share gateway dispatch mock exports 2026-04-20 19:02:47 +01:00
Peter Steinberger
34f60de970 perf(test): reuse version fixture root 2026-04-20 19:02:32 +01:00
Peter Steinberger
785ecf7715 perf(test): mock system run logger 2026-04-20 19:01:37 +01:00
Peter Steinberger
d9311a7935 perf(test): mock plugin activation manifest registry 2026-04-20 19:00:11 +01:00
Peter Steinberger
43a34e23b3 test: share lazy channel contract surface 2026-04-20 18:59:45 +01:00
Peter Steinberger
26c213031d perf(test): isolate gateway audit tests 2026-04-20 18:58:10 +01:00
Peter Steinberger
f43e006529 perf(test): mock plugin trust audit deps 2026-04-20 18:51:05 +01:00
Peter Steinberger
81722f0b26 test: share gateway auth status oauth fixture 2026-04-20 18:50:07 +01:00
Peter Steinberger
fafdd23568 test: share gateway web start context 2026-04-20 18:48:45 +01:00
Peter Steinberger
0c75b9ce00 ci: speed up fast security checks 2026-04-20 18:47:02 +01:00
Peter Steinberger
537f4689f9 test: share gateway deleted-agent guard mocks 2026-04-20 18:46:20 +01:00
Peter Steinberger
aa36c077fc test: share delivery queue reconnect fixtures 2026-04-20 18:42:43 +01:00
Peter Steinberger
9430113fe5 test: share outbound media action assertions 2026-04-20 18:40:04 +01:00
Peter Steinberger
4a7e3d9058 test: share outbound action mock fixture 2026-04-20 18:37:30 +01:00
Peter Steinberger
456bc8df65 test: share launchd integration helpers 2026-04-20 18:35:25 +01:00
Peter Steinberger
0fb9a3beac test: share schtasks startup fallback helpers 2026-04-20 18:34:05 +01:00
Peter Steinberger
29a48ab129 test: share systemd stage fixture 2026-04-20 18:32:11 +01:00
Peter Steinberger
cde7ae8809 test: share launchd test helpers 2026-04-20 18:30:53 +01:00
Peter Steinberger
800572e9c6 test: share schtasks install fixtures 2026-04-20 18:29:18 +01:00
Peter Steinberger
af134f1dd9 test: share doctor device pairing setup 2026-04-20 18:28:06 +01:00
Peter Steinberger
90bbd6b453 perf(test): preimport plugin activation boundary 2026-04-20 18:26:59 +01:00
Peter Steinberger
dbfc3d7104 perf(test): split plugin trust audit seam 2026-04-20 18:25:25 +01:00
Peter Steinberger
d206bf6362 test: share control ui trusted proxy fixtures 2026-04-20 18:23:50 +01:00
Peter Steinberger
1a006aa49e test: share gateway close deps fixture 2026-04-20 18:22:25 +01:00
Peter Steinberger
8b05743df2 ci(windows): normalize node path for bash 2026-04-20 18:22:19 +01:00
Pavan Kumar Gondhi
2f06696579 fix(security): block MINIMAX_API_HOST workspace env injection and remove env-driven URL routing [AI-assisted] (#67300)
* fix: address issue

* fix: address review feedback

* fix: finalize issue changes

* fix: address PR review feedback

* address review feedback

* docs: add changelog entry for PR merge
2026-04-20 22:51:03 +05:30
Peter Steinberger
99a896797f test: share device pair approval fixtures 2026-04-20 18:20:43 +01:00
Peter Steinberger
1471f25b45 test(kimi): preserve async stream arguments 2026-04-20 18:20:00 +01:00
Peter Steinberger
1fc7dd2fc1 test: share elevenlabs tts request fixture 2026-04-20 18:19:25 +01:00
Peter Steinberger
0d708eaacf test: share fal video queue fixtures 2026-04-20 18:18:21 +01:00
Peter Steinberger
803b6488d5 test: share fireworks runtime model fixture 2026-04-20 18:16:27 +01:00
Peter Steinberger
231ed8570c fix(bluebubbles): use runtime fetch wrapper 2026-04-20 18:15:43 +01:00
Peter Steinberger
6bc9b34824 perf(test): narrow plugin activation boundary 2026-04-20 18:15:41 +01:00
Peter Steinberger
370dfc9279 test: share kimi stream fixtures 2026-04-20 18:15:12 +01:00
Peter Steinberger
27b37f18ba test: share ollama tags fetch fixture 2026-04-20 18:13:50 +01:00
Peter Steinberger
ddc355b04a test: share openai image response fixture 2026-04-20 18:12:43 +01:00
Peter Steinberger
e8a8c264d2 docs: add test performance guidance 2026-04-20 18:11:39 +01:00
Peter Steinberger
4ea6e426cd test: share openai codex model fixtures 2026-04-20 18:11:35 +01:00
Peter Steinberger
e5f2b25f25 test: share openai speech fetch fixture 2026-04-20 18:09:29 +01:00
Peter Steinberger
71b08988fb perf(test): narrow security audit imports 2026-04-20 18:09:13 +01:00
Peter Steinberger
4edc64037c test(extensions): stabilize ci test assertions 2026-04-20 18:09:01 +01:00
Peter Steinberger
84065b9a68 test: share zai glm template fixture 2026-04-20 18:08:20 +01:00
Peter Steinberger
b97f993b0c test: share qa docker healthy deps 2026-04-20 18:07:04 +01:00
Peter Steinberger
f76b426e2b perf: reduce jiti loader alias work 2026-04-20 18:06:45 +01:00
Peter Steinberger
254417a344 test: stabilize BlueBubbles catchup parallelism 2026-04-20 18:06:45 +01:00
Peter Steinberger
17bac9e22d test: isolate BlueBubbles shard under parallelism 2026-04-20 18:06:45 +01:00
Peter Steinberger
5c7667c15c test: align TTS facade mock after rebase 2026-04-20 18:06:45 +01:00
Peter Steinberger
e753fc9cc7 test: fix ACP and TTS local failures 2026-04-20 18:06:45 +01:00
Peter Steinberger
642a3567b1 perf(test): mock security code safety scans 2026-04-20 18:06:32 +01:00
Peter Steinberger
a7978a271d test: share daemon probe pairing fixture 2026-04-20 18:05:30 +01:00
Omar Shahine
e89b41fce7 fix(bluebubbles): configurable sendTimeoutMs, bump send default to 30s (#69193)
Merged via squash.

Prepared head SHA: 358204f963
Co-authored-by: omarshahine <10343873+omarshahine@users.noreply.github.com>
Co-authored-by: omarshahine <10343873+omarshahine@users.noreply.github.com>
Reviewed-by: @omarshahine
2026-04-20 10:04:52 -07:00
Peter Steinberger
ba40142f71 test: share transcript assistant fixture 2026-04-20 18:03:08 +01:00
Peter Steinberger
b10c434788 test: share acpx runtime fixture 2026-04-20 18:01:13 +01:00
Peter Steinberger
9d168dd2f3 test: cover changed runner routing 2026-04-20 18:00:09 +01:00
Peter Steinberger
96f7e322ba test: route changed runner edits narrowly 2026-04-20 18:00:09 +01:00
Peter Steinberger
ecfb3abbed test: share slack approval origin fixture 2026-04-20 17:59:57 +01:00
Peter Steinberger
ca2d89bc4d test(extensions): move channel contracts out of core 2026-04-20 17:59:51 +01:00
Peter Steinberger
1f139c198a test: share slack security audit fixture 2026-04-20 17:58:24 +01:00
Peter Steinberger
164f0feddf perf(test): narrow feishu reply lifecycle 2026-04-20 17:57:36 +01:00
Peter Steinberger
78cf0e95ad test: share telegram thread binding fixture 2026-04-20 17:51:49 +01:00
Peter Steinberger
873ef6cf45 test: share telegram inbound body fixture 2026-04-20 17:50:36 +01:00
Peter Steinberger
e7befec3ff test: share telegram command menu fixture 2026-04-20 17:49:04 +01:00
Peter Steinberger
47d42606ac fix: repair bundled plugin runtime deps on startup 2026-04-20 17:47:55 +01:00
Peter Steinberger
4e059035a9 test: share twitch finalize fixture 2026-04-20 17:47:01 +01:00
Peter Steinberger
23221a3b12 test: share whatsapp qr socket fixture 2026-04-20 17:45:34 +01:00
Peter Steinberger
87083edf0a test: share postpublish slack fixture 2026-04-20 17:43:57 +01:00
Peter Steinberger
f8fdd854ed test: share clawhub release tooling fixture 2026-04-20 17:42:42 +01:00
Peter Steinberger
97e79bb5f6 test: balance extension shard scheduling 2026-04-20 17:41:38 +01:00
Peter Steinberger
14eb1923b4 test: share postinstall davey fixture 2026-04-20 17:38:46 +01:00
Peter Steinberger
f304af6b74 test(core): guard security audit boundaries 2026-04-20 17:38:20 +01:00
Peter Steinberger
a73bbe4bdd test(extensions): move channel security coverage 2026-04-20 17:38:20 +01:00
Peter Steinberger
db2678528d test: remove duplicate test project routing case 2026-04-20 17:36:26 +01:00
Peter Steinberger
b591b3e79a refactor: share check script helpers 2026-04-20 17:34:56 +01:00
Peter Steinberger
e93860f5f2 perf(test): narrow telegram config schema tests 2026-04-20 17:33:57 +01:00
Peter Steinberger
bf18161ea8 perf(test): skip text-only media policy setup 2026-04-20 17:31:41 +01:00
Peter Steinberger
b225d31179 ci: split remaining slow CI lanes 2026-04-20 17:29:11 +01:00
Peter Steinberger
b3963e847e perf(test): narrow gateway agent create event test 2026-04-20 17:25:09 +01:00
Peter Steinberger
cb2fc70741 test: share chat model select state fixtures 2026-04-20 17:23:29 +01:00
Peter Steinberger
3ed4b69b38 test: share agent tool call id fixtures 2026-04-20 17:21:42 +01:00
Peter Steinberger
acb4c5c2f3 perf(test): cache extension boundary prep freshness 2026-04-20 17:20:33 +01:00
Peter Steinberger
1f9bc4d057 test: share synology chat fixtures 2026-04-20 17:18:33 +01:00
Peter Steinberger
9b5324ff7e refactor: share qqbot speech provider schema 2026-04-20 17:16:27 +01:00
Peter Steinberger
c0490aa418 test: share telegram dm access fixture 2026-04-20 17:15:07 +01:00
Peter Steinberger
6a4d633e42 perf(test): keep session init thread parsing hot path lazy 2026-04-20 17:14:42 +01:00
Peter Steinberger
9e125184ed test: share signal archive extraction assertion 2026-04-20 17:13:13 +01:00
Peter Steinberger
6ed67fc873 test: share speech tts payload fixture 2026-04-20 17:11:33 +01:00
Peter Steinberger
3ae3a5e77d test: share twitch account fixture 2026-04-20 17:09:09 +01:00
Peter Steinberger
68fbe9fab1 test: share telegram api base mock 2026-04-20 17:06:57 +01:00
Peter Steinberger
78ae7bbd90 test: share voice call notify fixtures 2026-04-20 17:05:16 +01:00
Peter Steinberger
bc98fd96f1 test: share whatsapp test helper mocks 2026-04-20 17:03:02 +01:00
Peter Steinberger
f42fc9e6c2 test: share codex provider fixtures 2026-04-20 16:59:18 +01:00
Peter Steinberger
f0ef3070fa refactor: share codex app-server client factory 2026-04-20 16:58:16 +01:00
Peter Steinberger
f3bc22d577 ci: allow empty extension channel lint 2026-04-20 16:58:03 +01:00
Peter Steinberger
d2e2d971b6 test: share codex app-server setup helpers 2026-04-20 16:54:01 +01:00
Peter Steinberger
eb4a9f2a2a ci: reduce high-core runner fanout 2026-04-20 16:52:13 +01:00
Peter Steinberger
1f24ecbf24 test: share codex projector fixtures 2026-04-20 16:51:34 +01:00
Peter Steinberger
f11a8ea1ee perf(test): stub acp channel binding plugins 2026-04-20 16:50:40 +01:00
Peter Steinberger
96a6e1bf55 fix: allow LM Studio local auth marker 2026-04-20 16:50:01 +01:00
Peter Steinberger
0603ceba23 test: split heavy extension test shards 2026-04-20 16:50:01 +01:00
Peter Steinberger
68b7666d7c test: share codex app-server client fixtures 2026-04-20 16:46:32 +01:00
Peter Steinberger
9fe066b37a perf(test): avoid provider runtime in transport alias tests 2026-04-20 16:44:34 +01:00
Peter Steinberger
d3c9b9d30f test: share codex run-attempt fixtures 2026-04-20 16:41:11 +01:00
Peter Steinberger
78f9f3093e test: share codex app-server test helpers 2026-04-20 16:39:15 +01:00
Peter Steinberger
c597db3fb8 ci: target high-core Blacksmith lanes 2026-04-20 16:38:07 +01:00
Peter Steinberger
d76cc4eb93 test: share vydra provider fixtures 2026-04-20 16:34:48 +01:00
Peter Steinberger
76052a4e01 refactor: share browser runtime helpers 2026-04-20 16:32:28 +01:00
Peter Steinberger
21fbe416d4 ci: fix Windows node path capture 2026-04-20 16:29:48 +01:00
Peter Steinberger
0010b246c0 test: share browser tab route fixtures 2026-04-20 16:29:20 +01:00
Peter Steinberger
74967abd51 refactor: share browser path helpers 2026-04-20 16:27:11 +01:00
Peter Steinberger
c292d58d91 refactor: share tavily tool helpers 2026-04-20 16:24:29 +01:00
Peter Steinberger
2b65a5f0ac ci: use faster Blacksmith runners 2026-04-20 16:23:16 +01:00
Peter Steinberger
c4358fb567 perf(test): shorten security audit hotspot tests 2026-04-20 16:22:36 +01:00
Peter Steinberger
705cc97331 test: narrow native command fixture type 2026-04-20 16:22:36 +01:00
Peter Steinberger
e75ae8b3db test: share matrix event fixtures 2026-04-20 16:22:01 +01:00
Peter Steinberger
938a78f9bf test: share whatsapp monitor fixtures 2026-04-20 16:19:58 +01:00
Peter Steinberger
d02b10c3fb test: share whatsapp inbound reply fixture 2026-04-20 16:16:50 +01:00
Peter Steinberger
9b21540a4e perf(test): defer matrix approval reaction imports 2026-04-20 16:16:18 +01:00
Peter Steinberger
98a5f737d7 perf(test): streamline qa gateway child tests 2026-04-20 16:14:15 +01:00
Peter Steinberger
df05668f8b test: share auto reply trigger fixtures 2026-04-20 16:13:46 +01:00
Peter Steinberger
c705720d87 test: share skill command workspace fixtures 2026-04-20 16:10:35 +01:00
Peter Steinberger
c067c16360 perf(test): shorten browser chrome timeout probes 2026-04-20 16:09:37 +01:00
Peter Steinberger
3381e4c375 test: share devices cli fixtures 2026-04-20 16:08:25 +01:00
Peter Steinberger
decdb92f34 test: enforce extension dependency ownership 2026-04-20 16:07:14 +01:00
Peter Steinberger
1148f245c8 build(deps): declare extension runtime dependencies 2026-04-20 16:07:14 +01:00
Peter Steinberger
6abbe837b5 perf(test): shorten codex app-server timeout tests 2026-04-20 16:07:05 +01:00
Peter Steinberger
38cfdad16b test: share canvas host test helpers 2026-04-20 16:05:55 +01:00
Peter Steinberger
360953cb49 test: share config observe recovery helpers 2026-04-20 16:04:22 +01:00
Peter Steinberger
24644e3c27 ci: remove sticky disk cache plumbing 2026-04-20 16:03:55 +01:00
Peter Steinberger
f930b23dad test: share agents delete setup 2026-04-20 16:01:49 +01:00
Peter Steinberger
09171eee8d perf(test): shorten qa channel readiness polling 2026-04-20 16:00:59 +01:00
Peter Steinberger
0b239d163a test: share build cache fixture 2026-04-20 15:59:29 +01:00
Peter Steinberger
7d6b15eb67 refactor: share ollama discovery logic 2026-04-20 15:57:53 +01:00
Peter Steinberger
4dcadecab0 ci: remove Blacksmith pnpm sticky disk action 2026-04-20 15:56:56 +01:00
Peter Steinberger
e50b8e3e99 perf(test): mock qa lab capture store 2026-04-20 15:55:08 +01:00
Peter Steinberger
53df18943f refactor: reuse gateway close handler params 2026-04-20 15:52:56 +01:00
Peter Steinberger
372ca5e81e perf(test): skip manual lane settle delay in unit tests 2026-04-20 15:51:28 +01:00
Peter Steinberger
b4e3bbc57b refactor: reuse anthropic service tier wrapper 2026-04-20 15:50:15 +01:00
Peter Steinberger
c8a39b657e docs: streamline agent guidance 2026-04-20 15:48:41 +01:00
Peter Steinberger
788b47536c feat: add changed-lane local gate 2026-04-20 15:48:20 +01:00
Peter Steinberger
5bac634abf refactor: share memory wiki query scoring 2026-04-20 15:48:16 +01:00
Peter Steinberger
d2a271d5c8 perf(test): tighten codex model and fs bridge tests 2026-04-20 15:48:12 +01:00
Peter Steinberger
a72f102259 refactor: share msteams token request flow 2026-04-20 15:45:41 +01:00
Peter Steinberger
629b5b034a refactor: share openai realtime close capture 2026-04-20 15:40:12 +01:00
Peter Steinberger
b5a16e263d perf(test): cache sandbox bind policy paths 2026-04-20 15:39:38 +01:00
Peter Steinberger
fc56cd135f refactor: reuse telegram command keyboard helper 2026-04-20 15:38:10 +01:00
Peter Steinberger
61fa215acd refactor: share stream message wrapper 2026-04-20 15:36:20 +01:00
Peter Steinberger
8d4e3f5c3c refactor: reuse runtime logger helper 2026-04-20 15:34:39 +01:00
Peter Steinberger
f6f7d2f85e refactor: share qa channel protocol types 2026-04-20 15:32:31 +01:00
Peter Steinberger
eddfffebe8 refactor: share facade resolution helpers 2026-04-20 15:29:16 +01:00
Peter Steinberger
f163432674 fix(discord): avoid native opus install path (#69339)
* fix(discord): avoid native opus install path

* test(tts): mock lazy facade values
2026-04-20 15:25:07 +01:00
Peter Steinberger
3a99b8b9e1 perf(test): preload browser server harness 2026-04-20 15:22:42 +01:00
Peter Steinberger
f73d8e8d9e refactor: share configured account id helper 2026-04-20 15:21:20 +01:00
Peter Steinberger
0a9edac632 refactor: share parsed chat allowlist matcher 2026-04-20 15:18:44 +01:00
Peter Steinberger
44030ac4fd perf(test): cache qa scenario catalog 2026-04-20 15:17:07 +01:00
Peter Steinberger
795a8042a1 perf(test): tighten chrome internal timeouts 2026-04-20 15:12:20 +01:00
Peter Steinberger
3ecb713b00 perf: speed local checks and warm builds 2026-04-20 15:08:41 +01:00
Peter Steinberger
4e907f78ca refactor: reuse channel config policy helper 2026-04-20 15:06:24 +01:00
Peter Steinberger
beff874340 perf(test): trim active memory and qa lab hotspots 2026-04-20 15:04:38 +01:00
Peter Steinberger
f6360da116 fix(deps): remove extension-owned deps from root install (#69335)
* fix(deps): remove extension runtime deps from root install

* fix(deps): keep bundled plugin deps local

* test(plugins): assert matrix deps stay plugin-local
2026-04-20 15:03:09 +01:00
Peter Steinberger
8642137252 refactor: share model allowlist entry helper 2026-04-20 15:02:51 +01:00
Peter Steinberger
8a660099f2 docs: add changelog for PR 69316 2026-04-20 15:00:50 +01:00
Peter Steinberger
3664119029 perf: reuse plugin loader config cache 2026-04-20 15:00:50 +01:00
Peter Steinberger
099d4b50b6 docs: clarify alias map memoization rationale 2026-04-20 15:00:50 +01:00
Peter Steinberger
53176153a2 test: cover alias map cache context 2026-04-20 15:00:50 +01:00
Alex Knight
2b64f4bf4b perf: memoize buildPluginLoaderAliasMap to enable jiti sentinel reuse
buildPluginLoaderAliasMap() creates a new alias object via spread on every
call. jiti's normalizeAliases() uses a reference-identity sentinel
(`if (e[pt]) return e`) to skip its O(N²) normalization work — but fresh
object refs defeat the sentinel, causing the full cycle to repeat on
every call.

This change caches alias maps by their inputs (modulePath, argv1,
moduleUrl, pluginSdkResolution) so identical parameters return the same
object reference. Subsequent jiti calls hit the sentinel fast-path
instead of re-running normalization.

Includes 5 new tests covering:
  - reference identity for identical inputs
  - cache isolation (different modulePath, pluginSdkResolution, argv1
    each produce distinct objects)
  - content equivalence between cached and freshly-computed results

Refs #68983, #63948
2026-04-20 15:00:50 +01:00
Peter Steinberger
f27c164e7f refactor: share lazy facade value binder 2026-04-20 14:57:50 +01:00
Peter Steinberger
85c1c59c5f refactor: share message content block visitor 2026-04-20 14:53:42 +01:00
Peter Steinberger
17c77f1307 perf(test): skip tts provider lookup without directives 2026-04-20 14:52:27 +01:00
Peter Steinberger
4da0a99a9e refactor: share speech provider helpers 2026-04-20 14:50:58 +01:00
Peter Steinberger
9d17871ff0 refactor: share computed status adapter base 2026-04-20 14:46:20 +01:00
Peter Steinberger
4f37a5d590 test: remove duplicated env lookup helper 2026-04-20 14:43:03 +01:00
Peter Steinberger
8a4332864b fix(plugins): stop eager bundled plugin dep install (#69334)
* fix(plugins): stop eager bundled plugin dep install

* test(auto-reply): mock direct auth profile store imports
2026-04-20 14:41:18 +01:00
Peter Steinberger
f006678f3c refactor: share balanced json extraction 2026-04-20 14:40:21 +01:00
Peter Steinberger
655e0be3d7 refactor: share scoped gateway http auth 2026-04-20 14:37:05 +01:00
Peter Steinberger
e8ad3573c0 refactor: share media generation failure recording 2026-04-20 14:34:01 +01:00
Peter Steinberger
e3dd80f9d4 refactor: share cron list page types 2026-04-20 14:31:56 +01:00
Peter Steinberger
eaea16f166 refactor: share google turn ordering sanitizer 2026-04-20 14:29:16 +01:00
Peter Steinberger
b722273acb refactor: share inbound media detection 2026-04-20 14:27:26 +01:00
Peter Steinberger
80ab02d8be perf(test): narrow status message runtime 2026-04-20 14:27:22 +01:00
Peter Steinberger
8645e8655e refactor: share pairing connect detail assembly 2026-04-20 14:24:51 +01:00
Peter Steinberger
a9dcd52a7e refactor: share message action discovery params 2026-04-20 14:22:54 +01:00
Peter Steinberger
60ec7ca0f1 refactor: share gateway send inflight handling 2026-04-20 14:20:13 +01:00
Peter Steinberger
8dc756747b docs: update GitHub Copilot default model 2026-04-20 14:19:26 +01:00
Peter Steinberger
1ea02d231d refactor: reuse plugin contract snapshot type 2026-04-20 14:17:39 +01:00
Peter Steinberger
73f4bfadc1 style: fix ios app lint warnings 2026-04-20 14:17:25 +01:00
Peter Steinberger
a290e91b12 style: fix macos app lint warnings 2026-04-20 14:17:25 +01:00
Peter Steinberger
0c444ff5ba fix: classify no-delivery cron runs correctly (#69285) 2026-04-20 14:15:53 +01:00
Peter Steinberger
510fe8b95d perf(test): speed up reply trigger hotspots 2026-04-20 14:14:55 +01:00
Peter Steinberger
b7703616f0 refactor: share task audit sorting 2026-04-20 14:13:51 +01:00
Peter Steinberger
b79df1796c refactor: share session plugin line filtering 2026-04-20 14:11:55 +01:00
Peter Steinberger
100e587243 refactor: share provider auth choice selection 2026-04-20 14:10:06 +01:00
Peter Steinberger
283b72f2de refactor: share setup registry scaffolding 2026-04-20 14:08:29 +01:00
Peter Steinberger
76c4714ce7 refactor: share proxy capture event base 2026-04-20 14:06:19 +01:00
Peter Steinberger
60818959b0 refactor: share bundled plugin compat decisions 2026-04-20 14:04:16 +01:00
Peter Steinberger
ebcd475d24 test: update oxlint check wiring assertion 2026-04-20 14:02:51 +01:00
Peter Steinberger
848348f423 refactor: share active plugin runtime lookup 2026-04-20 14:01:54 +01:00
Peter Steinberger
99123dc5fd refactor: share native approval runtime types 2026-04-20 13:58:19 +01:00
Peter Steinberger
46ae3d314a perf: parallelize local check gate 2026-04-20 13:55:55 +01:00
Peter Steinberger
a1bd02fdfd refactor: share human list formatting 2026-04-20 13:54:24 +01:00
Peter Steinberger
c6a0452d13 refactor: share approval session lookup 2026-04-20 13:52:20 +01:00
Peter Steinberger
ef9b1a0001 refactor: share channel account inspection 2026-04-20 13:50:12 +01:00
Peter Steinberger
26a0172568 refactor: reuse detached task create params 2026-04-20 13:46:50 +01:00
Peter Steinberger
ff414f5870 refactor: share channel manifest metadata mapping 2026-04-20 13:44:41 +01:00
Peter Steinberger
91f1f881bb refactor: share channel media limit lookup 2026-04-20 13:41:39 +01:00
Peter Steinberger
b4a3c00efb refactor: reuse session status text params 2026-04-20 13:38:58 +01:00
Peter Steinberger
9607776ed7 refactor: share cli root option scanning 2026-04-20 13:36:39 +01:00
Peter Steinberger
0f1a938a3e refactor: share shell wrapper traversal 2026-04-20 13:33:46 +01:00
Peter Steinberger
abe2296daf refactor: share plugin activation decisions 2026-04-20 13:31:00 +01:00
Aditya Advani
b38988ca96 feat(mattermost): keep draft previews on one visible sink per turn (#47838)
Merged via squash.

Prepared head SHA: e4e3205176
Co-authored-by: ninjaa <1315093+ninjaa@users.noreply.github.com>
Co-authored-by: mukhtharcm <56378562+mukhtharcm@users.noreply.github.com>
Reviewed-by: @mukhtharcm
2026-04-20 17:57:12 +05:30
Peter Steinberger
91d31197be ci: run architecture check before release 2026-04-20 13:24:49 +01:00
Peter Steinberger
039d22cda8 refactor: share media provider capability check 2026-04-20 13:23:12 +01:00
Peter Steinberger
d2b67fbb68 refactor: share route binding normalization 2026-04-20 13:21:12 +01:00
Peter Steinberger
1e4f3f2123 refactor(test): remove legacy extension test seams 2026-04-20 13:18:49 +01:00
Peter Steinberger
869950564f build: update dependencies 2026-04-20 13:18:32 +01:00
Peter Steinberger
ffb1628727 fix: recover invalid gateway configs 2026-04-20 13:18:07 +01:00
Peter Steinberger
dafc31502a refactor: share provider and outbound helpers 2026-04-20 13:18:02 +01:00
Peter Steinberger
897c50e1a4 perf: speed up type check gate 2026-04-20 13:17:43 +01:00
Peter Steinberger
8116e638f3 chore: release 2026.4.20 2026-04-20 13:16:40 +01:00
Peter Steinberger
976306641d fix(matrix): resolve live allowlist updates 2026-04-20 13:10:02 +01:00
Peter Steinberger
9429b0976a fix(bluebubbles): refresh client cache on network policy changes 2026-04-20 13:10:02 +01:00
Peter Steinberger
20c88ef5db perf(test): narrow web tool imports 2026-04-20 12:51:14 +01:00
Peter Steinberger
6c711a64cb style: apply formatter fixes 2026-04-20 12:27:15 +01:00
Peter Steinberger
6686533d19 perf(test): tighten skill and session fixtures 2026-04-20 12:27:07 +01:00
Peter Steinberger
d0c756e8ab perf(test): slim subagent lifecycle imports 2026-04-20 12:26:57 +01:00
Peter Steinberger
69c78fbef0 perf(test): dedupe model config fixtures 2026-04-20 12:26:47 +01:00
Peter Steinberger
2c814d33e6 perf(test): slim bash tool imports 2026-04-20 12:26:39 +01:00
Peter Steinberger
b4f12bb4c3 perf(test): slim OpenAI and Pi runner imports 2026-04-20 12:26:30 +01:00
Gustavo Madeira Santana
c700bfc35d perf(test): slim matrix media fixtures 2026-04-20 05:51:04 -04:00
Gustavo Madeira Santana
50458789ad test(qa): cover cron duplicate delivery scenarios 2026-04-20 05:51:04 -04:00
Gustavo Madeira Santana
ea37a833dc test(matrix): add stale sync replay dedupe scenario 2026-04-20 05:51:04 -04:00
Ayaan Zaidi
94e2bf258d fix(ui): restore pairing connect error formatting 2026-04-20 14:15:20 +05:30
github-actions[bot]
042c117342 chore(ui): refresh pl control ui locale 2026-04-20 08:11:52 +00:00
github-actions[bot]
92a4d72709 chore(ui): refresh id control ui locale 2026-04-20 08:11:42 +00:00
github-actions[bot]
648f60c188 chore(ui): refresh uk control ui locale 2026-04-20 08:11:39 +00:00
github-actions[bot]
a48b655006 chore(ui): refresh tr control ui locale 2026-04-20 08:11:20 +00:00
github-actions[bot]
b9d108453f chore(ui): refresh fr control ui locale 2026-04-20 08:10:26 +00:00
github-actions[bot]
1df6d0467c chore(ui): refresh ko control ui locale 2026-04-20 08:10:16 +00:00
github-actions[bot]
60827fa096 chore(ui): refresh ja-JP control ui locale 2026-04-20 08:10:12 +00:00
github-actions[bot]
cdce715ba4 chore(ui): refresh es control ui locale 2026-04-20 08:09:59 +00:00
github-actions[bot]
7b5f09ab9d chore(ui): refresh pt-BR control ui locale 2026-04-20 08:09:05 +00:00
github-actions[bot]
f88ffa7f79 chore(ui): refresh de control ui locale 2026-04-20 08:08:48 +00:00
github-actions[bot]
3fb2c9a916 chore(ui): refresh zh-CN control ui locale 2026-04-20 08:08:46 +00:00
github-actions[bot]
1f25db1514 chore(ui): refresh zh-TW control ui locale 2026-04-20 08:08:42 +00:00
Ayaan Zaidi
aff96ea963 fix: avoid preview-only pairing approval hint (#69226) 2026-04-20 13:36:41 +05:30
Ayaan Zaidi
24c4e458f9 fix: surface pending scope upgrade feedback (#69226) 2026-04-20 13:36:41 +05:30
Ayaan Zaidi
444ece721c fix(ui): localize pairing upgrade hint copy 2026-04-20 13:36:41 +05:30
Ayaan Zaidi
221e550eb9 fix(agents): preserve pairing guidance for node invoke upgrades 2026-04-20 13:36:41 +05:30
Ayaan Zaidi
c9be0ece71 test(cli): align probe status expectation after rebase 2026-04-20 13:36:41 +05:30
Ayaan Zaidi
84f535c315 fix(cli): preserve local pairing fallback for upgrades 2026-04-20 13:36:41 +05:30
Ayaan Zaidi
66c1190bcc fix(control-ui): show scope upgrade pending state 2026-04-20 13:36:41 +05:30
Ayaan Zaidi
f070a92e19 fix(gateway): surface pending pairing upgrade details 2026-04-20 13:36:41 +05:30
Ayaan Zaidi
d63671fce0 docs(pairing): explain approval upgrades 2026-04-20 13:08:04 +05:30
Ayaan Zaidi
41a01cdae5 fix(control-ui): explain pairing access upgrades 2026-04-20 13:08:04 +05:30
Ayaan Zaidi
67d2026e22 feat(cli): show pairing access upgrades 2026-04-20 13:08:04 +05:30
Ayaan Zaidi
9de39accdb fix(shared): model pairing approval state from effective access 2026-04-20 13:08:04 +05:30
Ayaan Zaidi
4e01916a7e fix(gateway): report pairing upgrade details 2026-04-20 13:08:04 +05:30
Ayaan Zaidi
a89c1baddc fix: improve pairing-required recovery guidance (#69227) 2026-04-20 12:33:03 +05:30
Ayaan Zaidi
66e1c3982d fix(status): sanitize pairing recovery details 2026-04-20 12:33:03 +05:30
Ayaan Zaidi
98a0b22e8e fix(status): show pairing recovery details 2026-04-20 12:33:03 +05:30
Ayaan Zaidi
4bc5eab390 fix(gateway): enrich pairing connect errors 2026-04-20 12:33:03 +05:30
Chunyue Wang
2ad17098fe fix(slack): tolerate unresolved channel SecretRef on outbound send path (#68954)
Merged via squash.

Prepared head SHA: f13c639c26
Co-authored-by: openperf <80630709+openperf@users.noreply.github.com>
Co-authored-by: openperf <80630709+openperf@users.noreply.github.com>
Reviewed-by: @openperf
2026-04-20 14:59:09 +08:00
rubensfox20
54d7728e74 UI: localize Overview and login gate labels (#61054)
Merged via squash.

Prepared head SHA: 807c79cc5f
Co-authored-by: rubensfox20 <111531429+rubensfox20@users.noreply.github.com>
Co-authored-by: sallyom <11166065+sallyom@users.noreply.github.com>
Reviewed-by: @sallyom
2026-04-20 02:45:31 -04:00
Ayaan Zaidi
4d27f3b04c fix: split gateway probe capability from reachability (#69215) 2026-04-20 11:59:27 +05:30
Ayaan Zaidi
4800d4e1d7 fix(gateway): trim ssh command prefixes 2026-04-20 11:59:27 +05:30
Ayaan Zaidi
2c53354901 fix(gateway): tighten probe capability reporting 2026-04-20 11:59:27 +05:30
Ayaan Zaidi
212f6ddf8f test(status): restore check fixture for gateway auth 2026-04-20 11:59:27 +05:30
Ayaan Zaidi
a80874a4c1 docs(gateway): clarify probe capability wording 2026-04-20 11:59:27 +05:30
Ayaan Zaidi
485c258aaf fix(gateway): split probe capability from reachability 2026-04-20 11:59:27 +05:30
Ayaan Zaidi
a4130ae8ed fix: quote doctor pairing repair commands (#69210) 2026-04-20 11:36:25 +05:30
Ayaan Zaidi
b36d688f78 fix: add doctor pairing drift changelog (#69210) 2026-04-20 11:36:25 +05:30
Ayaan Zaidi
a1b4ef9b2f fix(doctor): harden pairing health notes 2026-04-20 11:36:25 +05:30
Ayaan Zaidi
f19e3ab298 refactor(doctor): distill pending pairing diagnosis 2026-04-20 11:36:25 +05:30
Ayaan Zaidi
f96eca4ab2 fix(doctor): tighten pairing state type guards 2026-04-20 11:36:25 +05:30
Ayaan Zaidi
c68a582e6e docs(doctor): document device pairing drift checks 2026-04-20 11:36:25 +05:30
Ayaan Zaidi
451b37ece1 fix(doctor): report device pairing auth drift 2026-04-20 11:36:25 +05:30
Ayaan Zaidi
b414c8b863 fix: default GitHub Copilot onboarding to Claude Opus 4.6 (#69207) 2026-04-20 11:03:50 +05:30
Ayaan Zaidi
be47599cad test(github-copilot): cover opus default model 2026-04-20 11:03:50 +05:30
Ayaan Zaidi
66ae458cce fix(github-copilot): default onboarding to opus 4.6 2026-04-20 11:03:50 +05:30
Ayaan Zaidi
0ce5e358d4 fix: require numeric Telegram allowFrom setup ids (#69191) 2026-04-20 10:03:25 +05:30
Ayaan Zaidi
3c354c0907 docs(telegram): clarify allowFrom setup ids 2026-04-20 10:03:25 +05:30
Ayaan Zaidi
df3aa90a20 fix(telegram): require numeric allowFrom ids in setup 2026-04-20 10:03:25 +05:30
竹田賢史
1d5b58ac18 feat(plugins): pass attachment metadata to before_model_resolve hook (#67322)
Merged via squash.

Prepared head SHA: 8af0ba9703
Co-authored-by: estack-takeda-yorichika <47170408+estack-takeda-yorichika@users.noreply.github.com>
Co-authored-by: sallyom <11166065+sallyom@users.noreply.github.com>
Reviewed-by: @sallyom
2026-04-20 00:14:50 -04:00
Roy Martin
9fc0d2a6bf fix(bluebubbles): prefer iMessage over SMS when both chats exist (#61781)
Merged via squash.

Prepared head SHA: 664fecff2f
Co-authored-by: rmartin <119151+rmartin@users.noreply.github.com>
Co-authored-by: omarshahine <10343873+omarshahine@users.noreply.github.com>
Reviewed-by: @omarshahine
2026-04-19 21:12:41 -07:00
zqchris
77b424b15e BlueBubbles/reactions: fall back unsupported reactions to love (#64693)
* bluebubbles: fall back unsupported reactions to love

iMessage tapback only supports love/like/dislike/laugh/emphasize/question.
Previously, `normalizeBlueBubblesReactionInput` threw when the input did
not map to one of those (e.g. a non-standard unicode emoji like 👀 used
to mean "seen, working on it"), which aborted the whole reaction request
and left the user with no feedback.

This splits the normalizer into a strict and lenient variant:

- `normalizeBlueBubblesReactionInputStrict` throws on unsupported input
  and is used by validator-style callers (e.g. `resolveBlueBubblesAckReaction`
  in monitor-processing.ts) that rely on the throw to detect misconfigured
  ack reactions and skip them cleanly. This preserves the previous silent-skip
  + warn-once behavior for ack reactions configured with an unsupported
  emoji.
- `normalizeBlueBubblesReactionInput` stays lenient and falls back to
  `love` (or `-love` when removing) on unsupported input, so agent-driven
  `sendBlueBubblesReaction` still produces a visible tapback instead of
  failing the whole reaction request. Contract errors (empty input)
  continue to bubble up.

`love` is chosen over `like` as the neutral default: `❤️` reads as a
general acknowledgment across chat norms, while `👍` carries an
agreement connotation that does not match the "seen, working on it"
semantic.

* CHANGELOG: note BlueBubbles reaction fallback

---------

Co-authored-by: Omar Shahine <10343873+omarshahine@users.noreply.github.com>
2026-04-19 20:52:36 -07:00
Omar Shahine
97492cf602 test(agents,gateway): fix two main-baseline test breakages from #68726 and #65986 (#69173)
* test(agents): expect timing fields in killed-run outcome

Aligns the steer-restart killed-run test with the timing fields added to
subagent run outcomes in #68726. The production code now returns
startedAt/endedAt/elapsedMs alongside status and error on the error
outcome, but this test's toEqual still asserted only status+error, so it
has been failing on main since #68726 landed. Uses the same expect.any(Number)
matcher already in use a few lines below for the ended hook payload.

* test(gateway): register ops agent in sessions.create task-start test

The "sessions.create can start the first agent turn from an initial task"
test triggers the auto chat.send path by passing `task:`. After #65986
added a deleted-agent guard to chat.send, an unregistered `ops` agent
triggers the reject path and the auto-started run never happens, so
runStarted comes back false.

Register `ops` via testState.agentsConfig (matching the pattern already
used by other ops-agent tests in this file) so the guard lets chat.send
through and the first turn starts as expected.

---------

Co-authored-by: Omar Shahine <10343873+omarshahine@users.noreply.github.com>
2026-04-19 20:45:19 -07:00
Shakker
fd90c30c23 docs: add gateway startup retry changelog 2026-04-20 04:06:57 +01:00
Shakker
4e6dfc015e chore: dedupe gateway chat client imports 2026-04-20 04:06:57 +01:00
Shakker
4ebeb18fde test: restore gateway chat timer cleanup 2026-04-20 04:06:57 +01:00
Shakker
3730d6d17a fix: retry tui chat history during startup 2026-04-20 04:06:57 +01:00
Ayaan Zaidi
52cca21ea8 fix: require explicit recipient for mode none (#69163) 2026-04-20 08:25:16 +05:30
Ayaan Zaidi
f657a25422 docs(changelog): note mode none recipient fix 2026-04-20 08:25:16 +05:30
Ayaan Zaidi
b64e1d8b91 fix(cron): require explicit recipient for mode none 2026-04-20 08:25:16 +05:30
Ayaan Zaidi
03de50e70b test(cron): cover mode none implicit recipient leak 2026-04-20 08:25:16 +05:30
Ayaan Zaidi
7d9a9d83ff fix: preserve isolated message targets (#69153)
* test(cron): cover delivery target context for mode none

* fix(cron): preserve target context for delivery mode none

* test(cron): cover isolated message target forwarding

* fix(cron): forward isolated message targets into embedded runs

* fix(cron): ignore implicit last-target context for mode none

* fix(cron): keep mode none channel explicit only

* test(cron): fix isolated target test typing

* fix: preserve isolated message targets (#69153)

* fix: preserve isolated message targets (#69153)
2026-04-20 08:05:32 +05:30
Josh Avant
d5b326523f qa-lab: make live lanes CI-ready for v1 E2E automation (#69122)
* qa-lab: harden CI defaults and failure semantics for live lanes

* qa-lab: add unit tests for suite progress logging defaults

* qa-lab: cover malformed multipass summary edge cases

* qa-lab: share suite summary failure counting helper

* qa-lab: test allow-failures parse wiring and sanitize progress ids

* fix: note qa CI live-lane defaults in changelog (#69122) (thanks @joshavant)
2026-04-19 21:13:27 -05:00
Gustavo Madeira Santana
6159b17cdf Tests: isolate sessions spawn registry seam 2026-04-19 19:26:56 -04:00
Gustavo Madeira Santana
f06493f0ea fix: preserve deleted main session targets 2026-04-19 19:24:58 -04:00
BitToby
d41c9860d7 fix: invalidate orphaned sessions on agent deletion (#65986)
Merged via squash.

Prepared head SHA: bc7c167dd9
Co-authored-by: bittoby <218712309+bittoby@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-04-19 18:47:52 -04:00
B.K.
4277078bc5 fix(subagent): include role, session key, and timing in error payloads (#68726)
Merged via squash.

Prepared head SHA: 55c756142f
Co-authored-by: BKF-Gitty <263413630+BKF-Gitty@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-04-19 18:31:31 -04:00
Gustavo Madeira Santana
f9a1875127 qa-matrix: cover Matrix allowlist hot reload
Add a Matrix QA scenario that removes an observer from the running account group allowlist and verifies the existing gateway stops replying without relying on a channel restart.

The scenario disables generic config reload and defers restart during the probe so it specifically covers the Matrix handler per-message live allowlist read.
2026-04-19 18:10:51 -04:00
Gustavo Madeira Santana
f309656325 fix(matrix): align mention-stripped command body 2026-04-19 17:34:37 -04:00
Bulut M.
f039d80306 refactor(terminal): optimize log sanitization (#67205)
* refactor(terminal): optimize sanitizeForLog with dynamic regex

* perf(terminal): optimize log sanitization

---------

Co-authored-by: Altay <altay@uinaf.dev>
2026-04-20 00:21:32 +03:00
Gustavo Madeira Santana
4d4f3eb404 chore(docs): remove stale ref 2026-04-19 17:13:02 -04:00
Mr.NightQ
733c0c2fda fix(matrix): strip mention prefix before slash command matching (#68570)
Merged via squash.

Prepared head SHA: d2c1ed5832
Co-authored-by: nightq <3429433+nightq@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-04-19 16:50:06 -04:00
Gustavo Madeira Santana
efc19f0ddb Add Matrix QA coverage for MXID-prefixed commands
Add a qa-matrix contract scenario that sends a Matrix self MXID-prefixed
control command from an observer and expects no SUT reply. This captures the
regression fixed by the Matrix command precheck change.
2026-04-19 16:46:49 -04:00
Omar Shahine
8fbf0972e7 bluebubbles: always set method explicitly on text sends, force Private API on macOS 26 (#69070)
Merged via squash.

Prepared head SHA: e3af5c5d83
Co-authored-by: omarshahine <198016546+xqing3@users.noreply.github.com>
Co-authored-by: omarshahine <10343873+omarshahine@users.noreply.github.com>
Reviewed-by: @omarshahine
2026-04-19 13:42:56 -07:00
澄潭
f38a498985 fix(matrix): hot-reload dm.allowFrom and groupAllowFrom on each inbound message (#68546)
Merged via squash.

Prepared head SHA: ab369851c8
Co-authored-by: johnlanni <6763318+johnlanni@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-04-19 15:55:18 -04:00
Gustavo Madeira Santana
55f094ea33 Skills: require manual test performance invocation 2026-04-19 15:48:18 -04:00
Gustavo Madeira Santana
d64948f5c2 Skills: add OpenClaw test performance workflow 2026-04-19 15:38:38 -04:00
Masato Hoshino
517801282a fix(matrix): pin event-helpers import to canonical matrix-js-sdk subpath (refs #50477) (#68498)
Merged via squash.

Prepared head SHA: 32e08e4d8e
Co-authored-by: masatohoshino <246810661+masatohoshino@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-04-19 15:35:34 -04:00
Ayaan Zaidi
c206702add docs(changelog): note cron delivery validation follow-ups 2026-04-19 23:18:27 +05:30
Ayaan Zaidi
9b38606d5c fix(gateway): preserve cron delivery validation behavior 2026-04-19 23:18:27 +05:30
Ayaan Zaidi
c0563aa532 test(gateway): cover cron delivery validation follow-ups 2026-04-19 23:18:27 +05:30
Marcus Castro
aa76cf43f0 fix(whatsapp): stabilize auth state and reconcile local runtime after CLI login (#67815)
* WhatsApp: harden auth persistence and backup recovery

* WhatsApp: model unstable auth state across runtime and setup

* WhatsApp: recover login and monitor startup from unstable auth

* Channels: surface auth stabilizing in status and health

* Gateway protocol: add channels.start surface

* Gateway: reconcile local channel runtime after CLI login

* Channels UI: reflect recovered login start state

* Changelog: note WhatsApp auth stabilization

* Gateway: fix lint in call test
2026-04-19 14:20:46 -03:00
Ayaan Zaidi
1d4e4314dd fix: preserve deferred cron heartbeat target (#69021)
* test(cron): cover deferred heartbeat target preservation

* fix(cron): preserve deferred heartbeat target override

* test(cron): update timer expectation for deferred heartbeat target

* fix(cron): preserve agent heartbeat config for targeted wakes

* test(cron): use wake request type in scheduler helper

* fix(cron): forward heartbeat overrides through gateway wake adapter

* fix(cron): preserve coalesced wake heartbeat overrides

* fix: preserve deferred cron heartbeat target (#69021)
2026-04-19 22:48:46 +05:30
Ayaan Zaidi
64089fd15e fix: reject invalid cron announce delivery config (#69015)
* test(gateway): cover invalid cron announce delivery config

* fix(gateway): reject invalid cron announce delivery config

* style(gateway): use toSorted for configured channels

* fix: reject invalid cron announce delivery config (#69015)

* fix: preserve default-agent cron delivery validation (#69015)
2026-04-19 22:05:24 +05:30
Ayaan Zaidi
99fb9ab444 fix: key recurring delivery dedupe to execution (#69000) 2026-04-19 21:16:57 +05:30
Ayaan Zaidi
34334f0e68 test(cron): cover recurring delivery dedupe 2026-04-19 21:16:57 +05:30
Ayaan Zaidi
21cfc21e4f fix(cron): key delivery dedupe by execution 2026-04-19 21:16:57 +05:30
Ayaan Zaidi
f44a7423c2 refactor(cron): share execution id helper 2026-04-19 21:16:57 +05:30
Omar Shahine
055c17b088 bluebubbles: consolidate HTTP traffic through typed BlueBubblesClient (#68234)
Merged via squash.

Prepared head SHA: ee72657bc8
Co-authored-by: omarshahine <10343873+omarshahine@users.noreply.github.com>
Co-authored-by: omarshahine <10343873+omarshahine@users.noreply.github.com>
Reviewed-by: @omarshahine
2026-04-19 08:43:32 -07:00
Bob
84cd786911 fix: tolerate partial discord channel metadata (#68953)
Merged via squash.

Prepared head SHA: 2026540d3e
Co-authored-by: dutifulbob <261991368+dutifulbob@users.noreply.github.com>
Co-authored-by: osolmaz <2453968+osolmaz@users.noreply.github.com>
Reviewed-by: @osolmaz
2026-04-19 17:00:30 +02:00
Mariano
bd3ad3436e tasks: add detached runtime plugin registration contract (#68915)
* tasks: register detached runtime plugins

* tasks: harden detached runtime ownership

* tasks: extract detached runtime contract types

* changelog: note detached runtime contract

* changelog: attribute detached runtime contract
2026-04-19 13:13:11 +02:00
termtek
c67a9c5259 docs(changelog): add thanks for kimi fix 2026-04-19 19:09:29 +08:00
termtek
28ee477930 docs(changelog): note kimi thinking default fix 2026-04-19 19:07:42 +08:00
Frank Yang
4ca5f51430 fix: default kimi thinking to off (#68907)
Co-authored-by: termtek <termtek@ubuntu.tail2b72cd.ts.net>
2026-04-19 18:50:54 +08:00
Mariano
8cb73844c8 browser: route existing-session user profile through browser nodes (#68891)
* browser: route user profile through browser nodes

* browser: align existing-session node docs

* browser: preserve host fallback on node discovery errors

* browser: preserve configured node pin errors

* browser: widen config mock in node pin test
2026-04-19 12:21:23 +02:00
Ayaan Zaidi
d83215084f test(tasks): align detached runtime mock return types 2026-04-19 15:21:03 +05:30
Viz
4cfc8cd5be fix(browser): discover CDP websocket from bare ws:// URL before attach (#68715)
* fix(browser): discover CDP websocket from bare ws:// URL before attach

When browser.cdpUrl is set to a bare ws://host:port (no /devtools/ path), ensureBrowserAvailable would call isChromeReachable -> canOpenWebSocket against the URL verbatim. Chrome only accepts WebSocket upgrades at the specific path returned by /json/version, so the handshake failed immediately with HTTP 400. With attachOnly: true, that surfaced as:

  Browser attachOnly is enabled and profile "openclaw" is not running.

even though the CDP endpoint was reachable and the profile was healthy. Reproduced by the new tests in chrome.test.ts and cdp.test.ts (#68027).

Fix: introduce isDirectCdpWebSocketEndpoint(url) — true only when a ws/wss URL has a /devtools/<kind>/<id> handshake path. Route any other ws/wss cdpUrl (including the bare ws://host:port shape) through HTTP /json/version discovery by normalising the scheme via the existing normalizeCdpHttpBaseForJsonEndpoints helper. Apply this in isChromeReachable, getChromeWebSocketUrl, and createTargetViaCdp. Direct WS endpoints with a /devtools/ path are still opened without an extra discovery round-trip.

Fixes #68027

* test(browser): add seeded fuzz coverage for CDP URL helpers

Adds property-based / seeded-fuzz tests for the URL helpers the
attachOnly CDP fix depends on (#68027):

  - isWebSocketUrl
  - isDirectCdpWebSocketEndpoint
  - normalizeCdpHttpBaseForJsonEndpoints
  - parseBrowserHttpUrl
  - redactCdpUrl
  - appendCdpPath
  - getHeadersWithAuth

Follows the existing repo convention (see
src/gateway/http-common.fuzz.test.ts): no fast-check dep, small
mulberry32 PRNG + hand-rolled generators, deterministic per-describe
seeds so failures are reproducible.

Lifts cdp.helpers.ts coverage from 77.77% -> 89.54% statements,
67.9% -> 80.24% branches, 78% -> 90% lines. Remaining uncovered
lines are inside the WS sender internals (createCdpSender,
withCdpSocket, fetchCdpChecked rate-limit branch), which require
integration-style mocks and are unrelated to the attachOnly fix.

* test(browser): drive cdp.helpers/cdp/chrome to 100% coverage

Lifts the three files touched by the #68027 attachOnly fix to 100% statements/branches/functions/lines across the extensions test suite. Adds cdp.helpers.internal.test.ts, cdp.internal.test.ts, and chrome.internal.test.ts covering error paths, branch matrices, CDP session helpers, Chrome spawn/launch/stop flows, and canRunCdpHealthCommand. Defensively unreachable guards are annotated with c8 ignore + inline justifications.

* fix(browser): restore WS fallback for non-/devtools ws:// CDP URLs

When /json/version discovery is unavailable (or returns no
webSocketDebuggerUrl), fall back to treating the original bare ws/wss
URL as a direct WebSocket endpoint. This preserves the #68027 fix for
Chrome's debug port while restoring compatibility with Browserless/
Browserbase-style providers that expose a direct WebSocket root without
a /json/version endpoint.

Priority order for bare ws/wss cdpUrl inputs:
  1. /devtools/<kind>/<id> URL \u2192 direct handshake, no discovery (unchanged)
  2. bare ws/wss root \u2192 try HTTP discovery first; if discovery returns a
     webSocketDebuggerUrl use it; otherwise fall back to the original URL
     as a direct WS endpoint
  3. HTTP/HTTPS URL \u2192 HTTP discovery only, no fallback (unchanged)

Affected call sites: isChromeReachable, getChromeWebSocketUrl,
createTargetViaCdp.

Also renames a misleading test ('still enforces SSRF policy for direct
WebSocket URLs') to accurately describe what it tests: SSRF enforcement
on the navigation target URL, not on the CDP endpoint.

New tests added for all three fallback paths. Coverage remains 100% on
all three touched files (238 tests).

* fix: browser attachOnly bare ws CDP follow-ups (#68715) (thanks @visionik)
2026-04-19 05:43:39 -04:00
ZC
25e51bba52 fix: parse PowerShell cron tools allow-list (#68858) (thanks @chen-zhang-cs-code)
* fix(cron): parse PowerShell tools allow list

* fix(cron): clarify tools allow-list help

* fix: parse PowerShell cron tools allow-list (#68858) (thanks @chen-zhang-cs-code)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-04-19 15:11:14 +05:30
Peter Steinberger
53495f5136 test: complete workspace setup in update smokes 2026-04-19 10:28:10 +01:00
Mariano
0787266637 tasks: extract detached task lifecycle runtime (#68886)
* tasks: extract detached task lifecycle runtime

* tests: relax gateway seam expectation

---------

Co-authored-by: Mariano Belinky <mariano@mb-server-643.local>
2026-04-19 10:56:31 +02:00
Peter Steinberger
2ecea9395b test: make OpenWebUI smoke deterministic 2026-04-19 09:22:20 +01:00
Peter Steinberger
ec193a2b82 test: tolerate empty fireworks live responses 2026-04-19 09:09:53 +01:00
Peter Steinberger
8c4ecf42df fix: stabilize release smoke reruns 2026-04-19 09:05:33 +01:00
Subash Natarajan
6682b12563 fix: strip orphaned OpenAI reasoning blocks before responses API call (#55787)
Merged via squash.

Prepared head SHA: 263b952d88
Co-authored-by: suboss87 <11032439+suboss87@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-04-19 00:57:03 -07:00
Peter Steinberger
beb2fded6d test: stabilize standalone Parallels smoke lanes 2026-04-19 08:43:15 +01:00
Rubén Cuevas
a1f277e30e fix(ui): stop unsupported wiki RPC probes during startup (#67905)
* UI: gate wiki method probes by advertised methods

* test(ui): cover legacy wiki method fallback
2026-04-19 17:06:30 +10:00
Rubén Cuevas
6d427f8c2a docs: clarify source control-ui dev/build flow (#68814) 2026-04-19 16:48:32 +10:00
cuitianhao
39cb6ecbb9 fix: keep cron last delivery sentinel runtime-only (#68829) (thanks @tianhaocui)
* fix(cron): stop persisting "last" as literal delivery channel value

The UI controller writes the sentinel value "last" into jobs.json when
the delivery channel field is empty. This overwrites user-configured
channels (e.g. "telegram") because the form populates with "last" as
the default fallback, and saving the form materializes it as a literal
persisted value.

"last" is a runtime-only sentinel meaning "use whatever channel was
last used in the session" and should never be written to jobs.json.
When the channel field is empty, write `undefined` instead so the
runtime delivery plan resolver applies the "last" fallback at
execution time without polluting the persisted state.

Fixes #68760

* fix(cron): keep last delivery sentinel runtime-only

* fix: keep cron last delivery sentinel runtime-only (#68829) (thanks @tianhaocui)

* fix: preserve clear-to-last cron updates (#68829) (thanks @tianhaocui)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-04-19 12:09:16 +05:30
Peter Steinberger
dc3df91e95 chore: release 2026.4.19-beta.2 2026-04-19 06:53:19 +01:00
Peter Steinberger
bb6ba38a10 fix: keep qa lab compat shim out of release inventory 2026-04-19 06:24:34 +01:00
Peter Steinberger
9a93ea9d7a refactor: share channel status account formatting 2026-04-19 05:33:19 +01:00
Peter Steinberger
3e081c5d21 refactor: share task executor param types 2026-04-19 05:28:03 +01:00
Peter Steinberger
c72f539ced refactor: share status memory resolver types 2026-04-19 05:25:13 +01:00
Peter Steinberger
83801c49f7 refactor: share config observe recovery helpers 2026-04-19 05:22:28 +01:00
Peter Steinberger
812f96cf24 refactor: share gateway reload params 2026-04-19 05:19:19 +01:00
Peter Steinberger
2f84c47b8b refactor: share task flow create params 2026-04-19 05:16:38 +01:00
Peter Steinberger
d385b96451 refactor: share status scan bootstrap params 2026-04-19 05:14:22 +01:00
Peter Steinberger
d4e1a790ab refactor: share media dimension parsing 2026-04-19 05:11:58 +01:00
Peter Steinberger
34abb441f6 refactor: share provider plugin id filtering 2026-04-19 05:09:57 +01:00
Peter Steinberger
984ecd98ca refactor: share legacy config migration pipeline 2026-04-19 05:06:58 +01:00
Peter Steinberger
528f296cfc refactor: share acp identity construction 2026-04-19 05:04:19 +01:00
Peter Steinberger
45381135df refactor: share conversation target normalization 2026-04-19 05:02:03 +01:00
Peter Steinberger
2a14f76964 refactor: share plugin validation diagnostics 2026-04-19 04:59:40 +01:00
Peter Steinberger
ca3e5ffd89 refactor: reduce subagent requester wrapper duplication 2026-04-19 04:52:51 +01:00
Peter Steinberger
9686e518bc test: share media generation reset helpers 2026-04-19 04:48:52 +01:00
Peter Steinberger
5ca33f7cb4 refactor: share model resolve fallback lookup 2026-04-19 04:46:11 +01:00
Peter Steinberger
dfe2e81829 refactor: share provider replay hook params 2026-04-19 04:40:22 +01:00
Peter Steinberger
3eed321081 refactor: share model allowlist parsing 2026-04-19 04:34:51 +01:00
Peter Steinberger
2a35ea4f07 test: share pi embedded helper setup 2026-04-19 04:31:01 +01:00
Peter Steinberger
efda761724 refactor: share cron flat recovery 2026-04-19 04:28:03 +01:00
Peter Steinberger
c63d6bf508 refactor: reuse codex search config types 2026-04-19 04:25:12 +01:00
Peter Steinberger
bcbb3de760 test: reuse run attempt fixture 2026-04-19 04:22:05 +01:00
Peter Steinberger
590474a9a4 test: share compact session fixture 2026-04-19 04:19:35 +01:00
Peter Steinberger
10e14bd5be test: reuse sanitize assistant fixture 2026-04-19 04:16:58 +01:00
Peter Steinberger
bfea6bebc9 test: share subagent cleanup lookup 2026-04-19 04:14:55 +01:00
Peter Steinberger
ab4eb5aa94 test: share anthropic cache payload fixture 2026-04-19 04:12:37 +01:00
Peter Steinberger
f5c49758fc test: share gateway exec allowlist fixture 2026-04-19 04:10:19 +01:00
Peter Steinberger
394c7a2357 test: share exec approval disabled fixture 2026-04-19 04:07:54 +01:00
Peter Steinberger
91ad6c2739 test: share mcp cache tool turn helper 2026-04-19 04:05:28 +01:00
Peter Steinberger
04697eca88 refactor: share channel action params 2026-04-19 04:03:16 +01:00
Peter Steinberger
1908967cfa test: share auth profile env cleanup 2026-04-19 04:00:36 +01:00
Peter Steinberger
f54cf74ef6 test: share BTW sanitized user assertion 2026-04-19 03:58:08 +01:00
Peter Steinberger
44166f7cfe test: share live model switch params 2026-04-19 03:55:35 +01:00
Peter Steinberger
6a87d6e814 test: share model fallback probe assertions 2026-04-19 03:52:57 +01:00
Peter Steinberger
0f871664c5 test: share bootstrap heartbeat fixture 2026-04-19 03:49:35 +01:00
Peter Steinberger
0a5515297e test: share skill auth config fixtures 2026-04-19 03:47:24 +01:00
Peter Steinberger
97a3089cec test: share unsafe skill scan fixture 2026-04-19 03:44:29 +01:00
Peter Steinberger
555f74cf67 test: share escaped bundled skill fixture 2026-04-19 03:42:19 +01:00
Peter Steinberger
9e93aa0c32 test: share ClawHub skill update assertion 2026-04-19 03:40:13 +01:00
Peter Steinberger
bf5b6cba70 test: share usage accumulator fixtures 2026-04-19 03:37:36 +01:00
stain lu
24b915ed41 fix: surface preserved stale session totals (#67695) (thanks @stainlu)
* fix(agents): preserve session totalTokens when provider omits usage data

Fixes #67667

When a provider (e.g. MiniMax via Anthropic endpoint) does not return
usage data in its API response, hasNonzeroUsage() is false and the
entire totalTokens update block in persistSessionAfterRun is skipped.
This resets totalTokens to undefined, causing /status to show 0%
context usage even after compaction has calculated real token counts.

The fix preserves the previous totalTokens value when the current run
has no usage data, marking it as stale (totalTokensFresh: false) so
display layers know it is from a prior run. This is strictly better
than null — the user sees the last known context usage instead of 0%.

* ci: retrigger after flaky gateway shutdown test

* test(agents): port totalTokens regression test to withTempSessionStore helper post-rebase

* fix(status): surface preserved stale session totals

* fix: surface preserved stale session totals (#67695) (thanks @stainlu)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-04-19 08:06:36 +05:30
Peter Steinberger
8233ca6401 test: share sandbox docker create fixture 2026-04-19 03:35:20 +01:00
Peter Steinberger
bf2fbf071b test: share vertex ADC auth fixture 2026-04-19 03:32:49 +01:00
Peter Steinberger
199f4d78d9 test: share anthropic payload fixtures 2026-04-19 03:29:43 +01:00
stain lu
4da808da50 fix: scope nested agent lanes per target session (#67785) (thanks @stainlu)
* fix(agents): scope nested lane per target session to stop cross-agent blocking

* docs(agents): note per-session nested-lane lifecycle parity with session:* lanes

* refactor(agents): distill nested lane helpers

* fix: scope nested agent lanes per target session (#67785) (thanks @stainlu)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-04-19 07:58:55 +05:30
Peter Steinberger
67bd9edd8b test: share cache trace memory fixture 2026-04-19 03:27:05 +01:00
Peter Steinberger
6de5f92835 test: share command delivery media fixture 2026-04-19 03:24:43 +01:00
Peter Steinberger
83a0f1fd52 test: share subagent cleanup decision fixture 2026-04-19 03:22:10 +01:00
Peter Steinberger
314654bd0f test: share auth profile env fixture 2026-04-19 03:19:57 +01:00
Peter Steinberger
22d99ee9df test: share models config env fixture 2026-04-19 03:17:36 +01:00
Peter Steinberger
8f92c0607c test: share transcript replay defaults fixture 2026-04-19 03:15:23 +01:00
Ayaan Zaidi
74f0dc87de fix: always send openai stream usage flag (#68746) (thanks @kagura-agent) 2026-04-19 07:44:48 +05:30
Ayaan Zaidi
43f6ffd0ae test: distill openai stream usage regression coverage 2026-04-19 07:44:48 +05:30
kagura-agent
c560793482 fix: always send stream_options.include_usage when streaming openai-completions
Backends like llama-cpp and LM Studio require stream_options: { include_usage: true }
in the request payload to report token usage in streaming responses.
buildOpenAICompletionsParams() previously gated this behind supportsUsageInStreaming
compat detection, which excluded non-standard and custom endpoints. The OpenAI SDK
sends this unconditionally, so we now do the same.

Fixes #68707
2026-04-19 07:44:48 +05:30
Peter Steinberger
1212412ff1 test: share context window model fixture 2026-04-19 03:12:59 +01:00
Peter Steinberger
a56aa6ccbe test: share model compat streaming fixture 2026-04-19 03:10:47 +01:00
Peter Steinberger
59032f63b1 test: share compact skill prompt fixture 2026-04-19 03:08:24 +01:00
Peter Steinberger
72f4b4186b test: share requester route binding fixture 2026-04-19 03:06:12 +01:00
Peter Steinberger
aa8331c836 test: share channel summary fixtures 2026-04-19 03:03:46 +01:00
Peter Steinberger
4862d34925 fix: package plugin SDK alias wrappers 2026-04-19 03:01:25 +01:00
Peter Steinberger
e39af9545f test: share sessions list details helper 2026-04-19 03:00:06 +01:00
Peter Steinberger
e28984c74a test: share media completion fixture 2026-04-19 02:58:01 +01:00
Peter Steinberger
3d3d585165 test: share idle timeout stream fixture 2026-04-19 02:54:27 +01:00
Peter Steinberger
5200ffb90c test: share update npm root runner 2026-04-19 02:52:33 +01:00
Peter Steinberger
0969336ef6 test: share install package fixtures 2026-04-19 02:50:25 +01:00
Peter Steinberger
2d6f44b6ce test: share fetch capture fixtures 2026-04-19 02:48:23 +01:00
Peter Steinberger
ff5904f5f4 test: share subagent action fixtures 2026-04-19 02:46:08 +01:00
Peter Steinberger
faae8e08b3 test: share qmd multi-agent config fixture 2026-04-19 02:43:52 +01:00
Peter Steinberger
f8f98c116e test: share doctor config mutation fixtures 2026-04-19 02:41:13 +01:00
Peter Steinberger
b7d362ddbb test: share doctor stale plugin fixture 2026-04-19 02:39:18 +01:00
Peter Steinberger
a4ac25972b test: share agent command runtime fixture 2026-04-19 02:35:30 +01:00
Peter Steinberger
b73103ab85 test: share cleanup command harness 2026-04-19 02:33:15 +01:00
Peter Steinberger
14435c8bdf test: share dotenv env key fixtures 2026-04-19 02:30:56 +01:00
Peter Steinberger
9380128193 test: share backup temp home helper 2026-04-19 02:29:01 +01:00
Peter Steinberger
5bbfa40255 test: share channel add config assertion 2026-04-19 02:26:50 +01:00
Peter Steinberger
496ccc3f73 test: share daemon scheduled repair helper 2026-04-19 02:25:04 +01:00
Peter Steinberger
383fa94c92 test: share onboarding discovery beacon fixture 2026-04-19 02:23:19 +01:00
Peter Steinberger
d1485ada9c test: share channel token account fixture 2026-04-19 02:21:06 +01:00
Peter Steinberger
1917c09d1c test: share sessions config fixture 2026-04-19 02:19:18 +01:00
Peter Steinberger
6798cbbd52 test: share status memory fixture 2026-04-19 02:17:13 +01:00
Peter Steinberger
10d7c4d50e test: share approval channel event fixture 2026-04-19 02:15:18 +01:00
Peter Steinberger
37bed56c1d test: share exec skill prelude fixtures 2026-04-19 02:12:28 +01:00
Peter Steinberger
caf8d75dfb test: stabilize plugin docker bundle command smoke 2026-04-19 02:09:43 +01:00
Peter Steinberger
ac8f0c9c0d chore: prepare 2026.4.19-beta.1 release 2026-04-19 02:09:43 +01:00
Peter Steinberger
9a1761d80c test: share approval session target fixture 2026-04-19 02:09:17 +01:00
Peter Steinberger
89a5eadd4e test: share exec approval policy fallback fixture 2026-04-19 02:06:54 +01:00
Peter Steinberger
77876bd05c test: share ghost reminder heartbeat fixtures 2026-04-19 02:04:24 +01:00
Peter Steinberger
805481c176 perf: narrow bonjour and sqlite runtime type surfaces 2026-04-19 02:03:19 +01:00
Peter Steinberger
9d8e923ddb test: share heartbeat override fixture 2026-04-19 02:00:01 +01:00
Peter Steinberger
af711f9e9f perf: speed up subagent and skill tests 2026-04-19 01:57:34 +01:00
Peter Steinberger
346aa0ed47 perf: narrow HTML parser type surface 2026-04-19 01:31:29 +01:00
Peter Steinberger
6f076dcde7 test: share core channel delivery harness 2026-04-19 01:27:04 +01:00
Peter Steinberger
1f71137d1e perf: narrow PDF extractor type surface 2026-04-19 01:26:13 +01:00
Peter Steinberger
046d983d26 test: share orphan key migration fixtures 2026-04-19 01:24:35 +01:00
Peter Steinberger
550b946696 test: share telegram forum delivery harness 2026-04-19 01:21:35 +01:00
Peter Steinberger
13e707fb7f test: share plugin approval forwarding fixtures 2026-04-19 01:18:39 +01:00
Peter Steinberger
1bef457cb6 test: speed up agent hotspot tests 2026-04-19 01:17:14 +01:00
Peter Steinberger
f40bd56793 test: isolate agent hotspot scans 2026-04-19 01:16:55 +01:00
Peter Steinberger
473225c471 test: share watch node harness setup 2026-04-19 01:16:10 +01:00
Peter Steinberger
f62766b996 test: share heartbeat telegram fixtures 2026-04-19 01:12:58 +01:00
Peter Steinberger
22a9dade9c test: share plugin install setup fixtures 2026-04-19 01:10:33 +01:00
Peter Steinberger
3fb87b127c test: share exec policy rollback snapshots 2026-04-19 01:07:39 +01:00
Peter Steinberger
e1fe71872c test: share unresolved cron next-run fixtures 2026-04-19 01:05:15 +01:00
Peter Steinberger
c96a0b1112 test: share cron isolated job fixtures 2026-04-19 01:02:26 +01:00
Peter Steinberger
0dda02515f test: share channel removal prompt fixtures 2026-04-19 00:59:42 +01:00
Peter Steinberger
a86c43e1fd refactor: share trusted catalog fallback listing 2026-04-19 00:57:27 +01:00
Peter Steinberger
60d83b1d32 test: share status health fixture 2026-04-19 00:54:48 +01:00
Peter Steinberger
7ac3c2ca88 test: share logs cli write capture helpers 2026-04-19 00:52:13 +01:00
Peter Steinberger
1ce9c355ab test: share lifecycle token drift fixtures 2026-04-19 00:50:12 +01:00
Peter Steinberger
7b7d69a31e test: share restart health stopped-free fixture 2026-04-19 00:48:17 +01:00
Peter Steinberger
ac0515ce7e test: share update global package fixtures 2026-04-19 00:46:00 +01:00
Peter Steinberger
6e18f0e59e test: share web media document loader 2026-04-19 00:43:32 +01:00
Vincent Koc
ba58bc3787 test(auto-reply): trim reply test module churn 2026-04-18 16:42:25 -07:00
Peter Steinberger
58a3527e17 test: share json file symlink helper 2026-04-19 00:40:26 +01:00
Peter Steinberger
2172bf1cdd test: share jsonl socket accept helper 2026-04-19 00:37:58 +01:00
Peter Steinberger
a5ea6d3cf4 test: share media fetch response mock 2026-04-19 00:35:43 +01:00
Peter Steinberger
f1d04006e0 test: share config partial write helper 2026-04-19 00:33:47 +01:00
Peter Steinberger
861e23b02c test: share config surface loader fixture 2026-04-19 00:31:48 +01:00
Peter Steinberger
6ffcf4523d test: share bundled plugin hotfix types 2026-04-19 00:29:26 +01:00
Peter Steinberger
cf3c1994dc test: share outbound media send harness 2026-04-19 00:27:29 +01:00
Peter Steinberger
e88a9e5ee4 test: share plugin root alias diagnostics harness 2026-04-19 00:25:12 +01:00
Peter Steinberger
158ebbb2ed test: share gateway config onboard harness 2026-04-19 00:22:42 +01:00
Peter Steinberger
aaad2468c8 refactor: share config delivery context schema 2026-04-19 00:20:28 +01:00
Peter Steinberger
1652707c6e test: share config audit record setup 2026-04-19 00:16:46 +01:00
Peter Steinberger
f8f9f13e0d test: share legacy config schema assertions 2026-04-19 00:13:50 +01:00
Peter Steinberger
91bb931b0f test: share acp prompt harness 2026-04-19 00:10:25 +01:00
Peter Steinberger
e7343dbfa8 refactor: share plugin auto-enable gate checks 2026-04-19 00:07:03 +01:00
Peter Steinberger
b7446a0c65 refactor: share redacted entry restore logic 2026-04-19 00:04:20 +01:00
Peter Steinberger
2070142c49 refactor: share agent binding formatter 2026-04-19 00:01:49 +01:00
Peter Steinberger
570fb5594c refactor: share outbound legacy send keys 2026-04-18 23:59:33 +01:00
Peter Steinberger
17fcbcefbc refactor: share plugin config trust helpers 2026-04-18 23:55:05 +01:00
Peter Steinberger
57326feb8d test: share oauth mock setup 2026-04-18 23:48:26 +01:00
Peter Steinberger
58da2f5897 fix(browser): improve CDP startup diagnostics 2026-04-18 23:44:27 +01:00
Peter Steinberger
0e9d63a417 test: share bundle mcp setup 2026-04-18 23:42:15 +01:00
Peter Steinberger
cbe124689d test: share embedded runner e2e mocks 2026-04-18 23:42:15 +01:00
Peter Steinberger
a0919685be perf: narrow local llama type surface 2026-04-18 23:38:04 +01:00
Peter Steinberger
ecfd6cfa73 test: drop redundant apply patch absolute write 2026-04-18 23:34:49 +01:00
Peter Steinberger
fc0c707b98 test: merge thread binding spawn coverage 2026-04-18 23:34:49 +01:00
Peter Steinberger
15a7869bbc test: merge live model switch assertions 2026-04-18 23:34:49 +01:00
Peter Steinberger
b97d50f2fb test: merge attempt param forwarding cases 2026-04-18 23:34:49 +01:00
Peter Steinberger
5aaec6a389 test: drop duplicate compaction reconciliation route 2026-04-18 23:34:49 +01:00
Peter Steinberger
cef82adf19 test: speed up bash tool wait loops 2026-04-18 23:34:49 +01:00
Peter Steinberger
cea60d603e test: drop duplicate zip extraction coverage 2026-04-18 23:34:49 +01:00
Peter Steinberger
3455c857a0 test: shorten exec approval followup cases 2026-04-18 23:34:49 +01:00
Peter Steinberger
0001551143 test: trim workspace skill loading cases 2026-04-18 23:34:48 +01:00
Peter Steinberger
3ea27c63e2 test: mock bundle MCP materialization boundary 2026-04-18 23:34:48 +01:00
Peter Steinberger
d9b05e601e test: clean subagent depth registry state 2026-04-18 23:34:48 +01:00
Peter Steinberger
4a5a43fb98 test: trim agent test setup overhead 2026-04-18 23:34:48 +01:00
Peter Steinberger
212c4af50d perf: skip disabled bundle MCP scans 2026-04-18 23:34:48 +01:00
Peter Steinberger
de9f726add test: mock subagent control runtime boundaries 2026-04-18 23:34:48 +01:00
Peter Steinberger
daabd058fc test: reduce agent hotspot overhead 2026-04-18 23:34:48 +01:00
Peter Steinberger
7bc3019691 test: share oauth workspace helpers 2026-04-18 23:32:28 +01:00
Peter Steinberger
73728127b6 refactor(browser): share SSRF hostname allowlist helper 2026-04-18 23:28:37 +01:00
Peter Steinberger
6fb74d4985 perf: simplify tsgo test lanes 2026-04-18 23:16:47 +01:00
Cyrus Forbes
9a94194329 fix: avoid cumulative codex usage as context (#64669) (thanks @cyrusaf) 2026-04-18 23:09:05 +01:00
Peter Steinberger
4e2541e5fb refactor: share stream iterator wrappers 2026-04-18 22:57:45 +01:00
Peter Steinberger
f76883d46c test: harden exec approval temp cleanup 2026-04-18 22:55:56 +01:00
Peter Steinberger
1fd049e307 fix: scope remote CDP host allowlist (#68207) 2026-04-18 22:54:54 +01:00
HansY
e90c89cf8b fix(browser): auto-allowlist configured CDP hostnames in SSRF policy 2026-04-18 22:54:54 +01:00
Peter Steinberger
a4a34edd21 test: reuse codex refresh helpers 2026-04-18 22:52:32 +01:00
Peter Steinberger
f48c91ac2f test: share oauth fuzz utilities 2026-04-18 22:49:54 +01:00
Peter Steinberger
8bfa06e992 refactor: enforce plugin-owned channel boundaries 2026-04-18 22:48:27 +01:00
Peter Steinberger
e89e214516 test: share oauth test helpers 2026-04-18 22:46:49 +01:00
Peter Steinberger
310d2db312 refactor: share model selection helpers 2026-04-18 22:41:40 +01:00
Peter Steinberger
3b2db583cd refactor: share subagent registry query helpers 2026-04-18 22:33:52 +01:00
Peter Steinberger
7481478303 test: share token estimate mock 2026-04-18 22:29:56 +01:00
Peter Steinberger
f0f4fa6978 test: reuse model override normalizer 2026-04-18 22:26:58 +01:00
Peter Steinberger
da22866030 test: dedupe embedded fallback fixtures 2026-04-18 22:24:03 +01:00
Peter Steinberger
808be2cae7 chore: disable makefile configure on open 2026-04-18 22:19:32 +01:00
Peter Steinberger
7b2a723891 test: dedupe exec host boundary mocks 2026-04-18 22:18:46 +01:00
Peter Steinberger
40d2e5aa45 test: trim slow agent waits 2026-04-18 22:18:24 +01:00
Peter Steinberger
d2c1b743c0 test: share claude cli error fixture 2026-04-18 22:15:01 +01:00
Peter Steinberger
966a3ea27c test: dedupe btw transcript fixtures 2026-04-18 22:12:19 +01:00
Peter Steinberger
b4543caf55 test: dedupe anthropic transport fixtures 2026-04-18 22:08:21 +01:00
Peter Steinberger
e069169765 perf: decouple plugin facades from extension types 2026-04-18 22:06:45 +01:00
Peter Steinberger
127bafa0b9 test: dedupe vertex stream payload fixture 2026-04-18 22:05:51 +01:00
Peter Steinberger
23ff2a9cf7 test: dedupe live model switch fixtures 2026-04-18 22:03:30 +01:00
Peter Steinberger
db0d212835 test: dedupe skills prompt fixture setup 2026-04-18 22:00:27 +01:00
Peter Steinberger
f00ef03d91 test: dedupe bash gateway inline eval setup 2026-04-18 21:57:56 +01:00
Peter Steinberger
607c855621 test: dedupe tool result hook fixtures 2026-04-18 21:54:58 +01:00
Peter Steinberger
2bca977ced test: dedupe auth health fixtures 2026-04-18 21:52:45 +01:00
Peter Steinberger
688adf732d test: dedupe avatar fixture setup 2026-04-18 21:50:27 +01:00
Peter Steinberger
1af8bd90c3 fix: satisfy google transport fetch boundary 2026-04-18 21:48:44 +01:00
Peter Steinberger
26f1f28ffe test: dedupe skills fixture setup 2026-04-18 21:47:15 +01:00
Peter Steinberger
f60c3bf6e0 test: fix oauth rebase conflict 2026-04-18 21:44:27 +01:00
Peter Steinberger
5530cec127 test: isolate skills plugin discovery 2026-04-18 21:44:27 +01:00
Peter Steinberger
46d6f500f3 test: reduce oauth concurrency fixture fanout 2026-04-18 21:44:27 +01:00
Peter Steinberger
c6784493fc test: split oauth effective credential policy 2026-04-18 21:44:27 +01:00
Peter Steinberger
4db3c5145f test: trim oauth adoption branch coverage 2026-04-18 21:44:27 +01:00
Peter Steinberger
cc8f4e98a6 test: split oauth mirror policy coverage 2026-04-18 21:44:27 +01:00
Peter Steinberger
d5f8f62ab2 test: reuse skill workspace fixture root 2026-04-18 21:44:27 +01:00
Peter Steinberger
eed0a93c59 test: narrow auth usage store mock 2026-04-18 21:44:27 +01:00
Peter Steinberger
57b55883c5 refactor: share live provider owner matching 2026-04-18 21:43:34 +01:00
Peter Steinberger
85826c83e4 refactor(google): move Gemini transport into plugin 2026-04-18 21:41:54 +01:00
Peter Steinberger
3a20606c04 test: dedupe subagent thread binding setup 2026-04-18 21:40:02 +01:00
Peter Steinberger
2dabf1932f test: dedupe update plan gating assertions 2026-04-18 21:37:38 +01:00
Peter Steinberger
2e1ddedc58 refactor: share chat content text coercion 2026-04-18 21:35:05 +01:00
Peter Steinberger
dc30298b29 test: dedupe context guard setup 2026-04-18 21:31:39 +01:00
Peter Steinberger
8879ed153d refactor: share embedded stream event wrapper 2026-04-18 21:27:55 +01:00
Peter Steinberger
5d6ee4f73e test: accept current codex models summaries (#68284) (thanks @vincentkoc) 2026-04-18 21:27:27 +01:00
Peter Steinberger
e8b401d0c8 chore: refresh plugin sdk api baseline (#68284) (thanks @vincentkoc) 2026-04-18 21:27:27 +01:00
Peter Steinberger
2fc429dfbf fix: keep codex oauth bridge extension-owned (#68284) (thanks @vincentkoc) 2026-04-18 21:27:27 +01:00
Vincent Koc
f1cc8f0cfc fix(codex): reuse bound auth profile for app-server startup 2026-04-18 21:27:27 +01:00
Vincent Koc
b2ca265f11 test(openai): align codex import profile expectation 2026-04-18 21:27:27 +01:00
Vincent Koc
4a4f52b097 fix(auth): restore codex oauth error and resume handling 2026-04-18 21:27:27 +01:00
Vincent Koc
a018257487 fix(auth): harden codex oauth bridge security 2026-04-18 21:27:27 +01:00
Vincent Koc
f6921fd733 refactor(auth): break oauth helper import cycle 2026-04-18 21:27:27 +01:00
Vincent Koc
20debfab90 test(auth): align codex bootstrap expectations 2026-04-18 21:27:27 +01:00
Vincent Koc
78288e37ed fix(auth): close codex review gaps 2026-04-18 21:27:27 +01:00
Vincent Koc
859eb06662 refactor(auth): route codex runtimes through canonical oauth 2026-04-18 21:27:27 +01:00
Vincent Koc
f98e98ab66 fix(auth): keep oauth fallback recovery consistent 2026-04-18 21:27:27 +01:00
Vincent Koc
d97d5c04f0 fix(auth): harden oauth bootstrap identity checks 2026-04-18 21:27:27 +01:00
Vincent Koc
6f450c2d1f refactor(auth): reuse shared oauth policy helpers 2026-04-18 21:27:27 +01:00
Vincent Koc
5f2e77a6e1 refactor(auth): centralize oauth lifecycle manager 2026-04-18 21:27:27 +01:00
Vincent Koc
554507b413 fix(auth): align codex cli bootstrap policy 2026-04-18 21:27:27 +01:00
Peter Steinberger
de2a9459e5 test: remove unused cli runner fixtures 2026-04-18 21:25:25 +01:00
Peter Steinberger
ea1e933b29 refactor: share sessions spawn attachment checks 2026-04-18 21:24:53 +01:00
Peter Steinberger
848f154f3e refactor: share tool call transcript helpers 2026-04-18 21:22:23 +01:00
Peter Steinberger
f298f86a7f test: remove unused agent test helpers 2026-04-18 21:21:17 +01:00
Peter Steinberger
8f648078bd refactor: dedupe embedded runner helpers 2026-04-18 21:17:42 +01:00
Peter Steinberger
ed463f6de0 chore: remove unused flow and daemon helpers 2026-04-18 21:16:19 +01:00
Peter Steinberger
3a3ab31d2b test: dedupe plugin contract helper assertions 2026-04-18 21:12:54 +01:00
Peter Steinberger
1d7d268a63 refactor: share duplicate script helpers 2026-04-18 21:12:54 +01:00
Peter Steinberger
1687c672a7 refactor: dedupe media understanding provider helpers 2026-04-18 21:12:54 +01:00
Peter Steinberger
045010bb78 chore: trim unused wrapper exports 2026-04-18 21:11:00 +01:00
Gustavo Madeira Santana
6794ff411a Docs: trim redundant outbound guardrail 2026-04-18 16:10:45 -04:00
Gustavo Madeira Santana
35e31ed351 Docs: capture test performance guardrails 2026-04-18 16:09:27 -04:00
Peter Steinberger
2d59395883 refactor: move provider endpoint metadata into manifests 2026-04-18 21:06:50 +01:00
Peter Steinberger
67ebc433f9 fix(agents): remove root Anthropic SDK dependency 2026-04-18 21:03:02 +01:00
Peter Steinberger
93a6c93865 test: reuse oauth lock timeout setup 2026-04-18 20:57:17 +01:00
Peter Steinberger
b3a97df754 refactor: cache reply and visibility runtimes 2026-04-18 20:54:30 +01:00
Peter Steinberger
8ba5865383 chore: remove unused helpers 2026-04-18 20:53:35 +01:00
Peter Steinberger
60baaf6e04 test: avoid web fetch barrel in ssrf tests 2026-04-18 20:50:17 +01:00
Peter Steinberger
b928f360a1 test: reduce auth and subagent control hotspots 2026-04-18 20:47:47 +01:00
Peter Steinberger
a2b093cf6a chore: remove unused agent exports 2026-04-18 20:46:33 +01:00
Peter Steinberger
0195da6b0e refactor: cache optional runtime imports 2026-04-18 20:45:26 +01:00
Peter Steinberger
6d40de45c7 fix: keep history-backed chat images visible 2026-04-18 20:44:05 +01:00
Alec Hrdina
98316cfbbd fix(ui): skip blocked local transcript image paths 2026-04-18 20:44:05 +01:00
Alec Hrdina
3cb142ff2e fix(ui): fall back for generic transcript image MIME 2026-04-18 20:44:05 +01:00
Alec Hrdina
501a68a69b fix(ui): ignore non-image transcript media paths 2026-04-18 20:44:05 +01:00
Alec Hrdina
b5038fd9a1 fix(ui): keep history-backed user image messages visible 2026-04-18 20:44:05 +01:00
Peter Steinberger
cfd796a515 docs: fix clawtributors README layout 2026-04-18 20:41:21 +01:00
Peter Steinberger
7d728afa12 test(matrix): harden thread binding stop flush test 2026-04-18 20:37:13 +01:00
Peter Steinberger
712644f0d9 fix(queue): preserve pending items during drains 2026-04-18 20:37:13 +01:00
Peter Steinberger
511a6c0ad0 chore(deps): prune root dependency declarations 2026-04-18 20:37:13 +01:00
Peter Steinberger
155162a8cd chore(lint): enable additional cleanup rules 2026-04-18 20:37:13 +01:00
Peter Steinberger
4fa961d4f1 refactor(lint): enable map spread rule 2026-04-18 20:37:12 +01:00
Peter Steinberger
0c245c35c5 test: trim auth and skill install setup 2026-04-18 20:37:04 +01:00
Peter Steinberger
cd783b9946 chore: remove unused exports 2026-04-18 20:35:20 +01:00
Peter Steinberger
afebeb5e9a fix: align active-memory timeout schema (#68480) (thanks @Bartok9) 2026-04-18 20:31:41 +01:00
Bartok
866d1eef0a fix(active-memory): raise timeoutMs ceiling from 60s to 120s
The normalizePluginConfig clamp hard-coded a 60_000 ms ceiling for
config.timeoutMs, silently reducing any configured value above 60
seconds down to 60 000 ms at runtime. This made it impossible for
operators to set longer recall budgets even though the docs
(docs/pi.md) showed 120_000 as a valid example.

Raise the ceiling to 120_000 ms so values between 60 001 and 120 000
are honored. Values above 120 000 are still clamped to prevent
unbounded blocking.

Adds two regression tests:
  - 90 000 ms is passed through unchanged
  - 200 000 ms is clamped to 120 000 ms

Fixes #68410.
2026-04-18 20:31:41 +01:00
Peter Steinberger
ab1e091e39 test: reduce agents test hotspots 2026-04-18 20:31:02 +01:00
Peter Steinberger
d1fb2d25ea refactor: cache reply understanding imports 2026-04-18 20:29:03 +01:00
Peter Steinberger
2b7b5774b6 chore: remove dead code 2026-04-18 20:27:42 +01:00
Peter Steinberger
73e497f9be refactor: cache hot channel imports 2026-04-18 20:19:53 +01:00
Peter Steinberger
85912849cc refactor: move extension markers into manifests 2026-04-18 20:16:44 +01:00
Peter Steinberger
a5d6330f87 refactor: cache remaining runtime imports 2026-04-18 20:08:04 +01:00
Peter Steinberger
58759bb565 test: genericize synthetic auth coverage 2026-04-18 19:59:36 +01:00
Peter Steinberger
f168a62068 test: speed up auth profile session override 2026-04-18 19:57:45 +01:00
Peter Steinberger
796f272f7d refactor: move synthetic auth refs to manifests 2026-04-18 19:53:54 +01:00
Peter Steinberger
ebfab7bf84 docs: update changelog for Telegram callback fix (#68588) (thanks @Lucenx9) 2026-04-18 19:52:31 +01:00
Lucenx9
90b8f3fba2 fix(telegram): tighten permanent edit error match 2026-04-18 19:52:31 +01:00
Lucenx9
d8b18f1d96 fix(telegram): avoid wedging callback updates on permanent edit errors 2026-04-18 19:52:31 +01:00
Peter Steinberger
a07b9fc840 test: trim runtime import surfaces 2026-04-18 19:51:22 +01:00
Peter Steinberger
9e27d04dc3 test: narrow agent control mocks 2026-04-18 19:51:22 +01:00
Peter Steinberger
fe0055a1d1 test: dedupe pi tools schema coverage 2026-04-18 19:51:22 +01:00
Peter Steinberger
6ccac3d208 test: optimize skills workspace fixtures 2026-04-18 19:51:22 +01:00
Peter Steinberger
5e7b5cf285 perf: snapshot pi project settings 2026-04-18 19:51:22 +01:00
Peter Steinberger
ec86d0f64a fix: keep google thinking helpers within SDK boundary 2026-04-18 19:46:00 +01:00
Peter Steinberger
5dbfaa15fa refactor: keep ollama compat in extension 2026-04-18 19:42:10 +01:00
Peter Steinberger
d3eeadba94 refactor: drop private channel sdk facades 2026-04-18 19:37:15 +01:00
Peter Steinberger
858a3f72fa fix(agents): keep google compat facades in core 2026-04-18 19:35:18 +01:00
Gustavo Madeira Santana
f6d336935d Agents: keep requester origin inference light 2026-04-18 14:32:53 -04:00
Peter Steinberger
1cc9bc58a2 fix(agents): preserve ollama compat fallbacks 2026-04-18 19:27:43 +01:00
Peter Steinberger
1f1ff0567a refactor(lint): reduce map spread patterns 2026-04-18 19:27:43 +01:00
Peter Steinberger
cc919db83b chore(lint): enable async endpoint handler rule 2026-04-18 19:27:43 +01:00
Peter Steinberger
84aed919a9 fix: restore CI restart and provider compat 2026-04-18 19:24:08 +01:00
Peter Steinberger
162bf51adb refactor(google): move thinking policy into plugin 2026-04-18 19:22:27 +01:00
Peter Steinberger
28fe0296c4 fix: support Gemini latest thinking config 2026-04-18 19:22:27 +01:00
Peter Steinberger
00e613f12d refactor: move provider-specific tests to extensions 2026-04-18 19:17:27 +01:00
Peter Steinberger
7474b52584 fix: respect web search SecretRef credentials 2026-04-18 19:08:50 +01:00
Peter Steinberger
438799e929 fix: log detached service restart attempts 2026-04-18 19:08:36 +01:00
Peter Steinberger
28be124cc1 refactor: centralize restart log conventions 2026-04-18 19:08:35 +01:00
Peter Steinberger
a7e029fde9 refactor: cache provider tool runtimes 2026-04-18 19:05:00 +01:00
lukeboyett
c39314c14a fix(agents): prefer target agent's bound Matrix account for subagent spawns (#67508)
Merged via squash.

Prepared head SHA: 9300111038
Co-authored-by: lukeboyett <46942646+lukeboyett@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-04-18 14:02:53 -04:00
Peter Steinberger
3f3bc97cd3 chore(lint): enable warning comments rule 2026-04-18 18:55:18 +01:00
Peter Steinberger
235cdb3f81 refactor: remove ollama core facades 2026-04-18 18:53:04 +01:00
Peter Steinberger
6b525023d4 fix: polish Slack thread starter context (#68594) 2026-04-18 18:45:29 +01:00
Peter Steinberger
5cc4426f88 test: align qa multipass pnpm expectation 2026-04-18 18:39:03 +01:00
Peter Steinberger
089e038dfe fix: harden macOS update restart helper (#68492) (thanks @hclsys) 2026-04-18 18:39:03 +01:00
HCL
4a870300dd fix(update-cli): capture macOS launchctl stderr to a log file instead of /dev/null
The macOS restart helper emitted by `openclaw update` (darwin branch of
`prepareRestartScript`) wrote the gateway restart script with every
`launchctl` stderr redirected to `/dev/null` and the final fallback
`kickstart` chained with `|| true`. When bootstrap/kickstart failed
(plist-on-disk race, schema rejection, stale job, bootout recovery
edge cases), the script exited 0, the updater declared success, and
the gateway silently stayed offline.

The reporter saw a ~25 minute production outage before noticing the
messages going unanswered across Telegram/Discord/Feishu.

Route stderr to `~/.openclaw/logs/update-restart.log` via `exec 2>>`,
drop `2>/dev/null` on every launchctl call, and remove the `|| true`
swallow on the fallback kickstart so a genuine failure exits non-zero
and leaves a durable audit trail. Log directory creation is best-effort
via `mkdir -p ... 2>/dev/null || true` since it normally already exists
from the gateway's own logging path. Self-cleanup of the script file
via `rm -f "$0"` is retained because the log, not the script, is the
useful artifact after the fact.

Adds a targeted regression test `captures macOS launchctl stderr to
~/.openclaw/logs/update-restart.log` alongside the existing darwin
restart-script test. The existing test's assertions about the
kickstart/enable/bootstrap fallback chain + self-cleanup all still pass.

Fixes #68486
2026-04-18 18:39:03 +01:00
Peter Steinberger
90c1ab2cef build: add tsgo profiler 2026-04-18 18:39:01 +01:00
Peter Steinberger
16bd427cb6 test: speed apply-patch and exec approval hotspots 2026-04-18 18:33:16 +01:00
Peter Steinberger
e45a50c828 perf: narrow subagent test runtime seams 2026-04-18 18:33:15 +01:00
Peter Steinberger
4180e7cd59 test: dedupe skills and model config coverage 2026-04-18 18:33:15 +01:00
Peter Steinberger
6d776593ea perf: lazy-load skills install extraction seams 2026-04-18 18:33:15 +01:00
Peter Steinberger
df525b90f2 chore(lint): enable unnecessary type parameter rule 2026-04-18 18:31:13 +01:00
Peter Steinberger
630f2bcabe fix: harden published gateway secret placeholders 2026-04-18 18:29:10 +01:00
Coy Geek
106b770c40 Gateway: reject published placeholder tokens 2026-04-18 18:29:10 +01:00
Coy Geek
960bc52e3c fix(install): remove published gateway token placeholder
Co-authored-by: opencode <opencode@users.noreply.github.com>
2026-04-18 18:29:10 +01:00
Peter Steinberger
1a7d89e85b docs: add WeChat channel guide 2026-04-18 18:26:40 +01:00
Peter Steinberger
3d994aa03b docs: clarify tsgo typecheck lanes 2026-04-18 18:24:07 +01:00
Peter Steinberger
72979129fb build: split tsgo core and extension graphs 2026-04-18 18:22:26 +01:00
Peter Steinberger
e11039087c build: add targeted tsgo test graphs 2026-04-18 18:12:44 +01:00
Peter Steinberger
cd2ef0f3a3 chore(lint): enable low-noise rules 2026-04-18 18:09:18 +01:00
Peter Steinberger
07785c6dbc build: split tsgo prod and test graphs 2026-04-18 18:06:29 +01:00
Peter Steinberger
753183e081 build(deps): update workspace dependencies 2026-04-18 18:04:56 +01:00
Peter Steinberger
c95d6049c2 chore(lint): preserve oxlint rule baseline 2026-04-18 18:04:56 +01:00
Peter Steinberger
76891c9cf8 fix: exclude ancestor pids from stale gateway cleanup (#68517) (thanks @openperf) 2026-04-18 18:03:55 +01:00
openperf
8aadca4c3e fix(infra/restart): exclude ancestor pids from stale-gateway cleanup
The stale-gateway cleanup filter already refused to kill process.pid —
acknowledging the invariant that terminating a process whose death
cascades into the caller is never safe. That invariant was applied only
to the caller itself, not to its ancestors, which is why the
openclaw-weixin sidecar triggered an unbounded restart loop: the
sidecar's cleanup SIGTERM'd its parent gateway, the supervisor
restarted the gateway, the gateway re-spawned the sidecar, the cleanup
ran again.

Complete the invariant by excluding the full self+ancestor PID set in
both the lsof (Unix) and PowerShell/netstat (Windows) cleanup paths.
Walk uses process.ppid unconditionally (Node built-in, no spawn) and
/proc/<pid>/status on Linux for transitive ancestors, with graceful
degradation where /proc is unavailable.
2026-04-18 18:03:55 +01:00
Peter Steinberger
aad9a833c0 fix: polish Slack thread fetch diagnostics (#68594) (thanks @martingarramon) 2026-04-18 17:55:05 +01:00
Martin Garramon
6368559c02 chore(scripts): bump slack media.ts fetch-allowlist line numbers
The `lint:tmp:no-raw-channel-fetch` allowlist pins exact line numbers
(scripts/check-no-raw-channel-fetch.mjs:63-65). The previous commit
added `import { logVerbose } from "openclaw/plugin-sdk/runtime-env";`
on line 8 of `extensions/slack/src/monitor/media.ts`, shifting the
three allowlisted raw `fetch()` callsites from 96/115/120 → 97/116/121.
Updates the allowlist to match the new positions. No behavior change —
the same callsites remain allowlisted.
2026-04-18 17:55:05 +01:00
Martin Garramon
31e5cd6376 fix(slack): surface silent errors in thread starter/history fetch
Fixes #62571. `resolveSlackThreadStarter` and `resolveSlackThreadHistory`
in `extensions/slack/src/monitor/media.ts` swallowed ALL errors with bare
`catch {}` blocks — auth failures, rate-limit rejections, scope errors,
and network blips all mapped to the same silent `null` / `[]` fallback.
Operators had no way to distinguish "genuinely empty thread" from
"Slack rejected our call".

Replaces both bare catches with `logVerbose` calls that include the
channel, thread ts, and error message. Behavior is preserved — callers
still receive `null` / `[]` — but the failure reason now shows up in
verbose logs, matching the pattern already used elsewhere in the Slack
extension (see `monitor/context.ts:285`, `send.ts:140`, `actions.ts:49`).

Testing:
- New `describe("resolveSlackThreadStarter", ...)` block with 4 tests
  (previously uncovered): success path, empty-text skip, Error throw
  surfaces via logVerbose with channel/ts/reason, non-Error throw value
  surfaces via String(err).
- Existing `resolveSlackThreadHistory` throws test upgraded to assert
  the logVerbose call with channel/ts/reason.
- `pnpm vitest run extensions/slack/src/monitor/media.test.ts` → 35
  passed (31 previous + 4 new).
2026-04-18 17:55:05 +01:00
Peter Steinberger
e7d33b4870 refactor: finish dynamic import cleanup 2026-04-18 17:54:38 +01:00
Peter Steinberger
f38727acd9 fix(google): cover gemini pro zero thinking budget (#68607) (thanks @josmithiii) 2026-04-18 17:49:58 +01:00
Julius Smith
8c5a4eb866 fix(google): strip thinkingBudget=0 for gemini-2.5-pro thinking-required model
Gemini 2.5 Pro only works in thinking mode and rejects thinkingBudget=0
with 'Budget 0 is invalid. This model only works in thinking mode.' The
existing sanitizer in the embedded runner only handled negative budgets;
now it also removes zero budgets for the thinking-required model so the
API uses its default thinking behavior. When thinkingBudget was the only
key in thinkingConfig, the empty object is also removed to match the
Gemma 4 cleanup path.
2026-04-18 17:49:58 +01:00
Peter Steinberger
ca1aa08709 test: tighten async wait boundaries 2026-04-18 17:42:28 +01:00
Peter Steinberger
54f121f843 test: speed up subagent runtime tests 2026-04-18 17:42:02 +01:00
Peter Steinberger
fa2f53993a test: trim skills and bundle mcp overhead 2026-04-18 17:42:02 +01:00
Peter Steinberger
53239102f8 test: speed up agent model auth tests 2026-04-18 17:42:02 +01:00
Vincent Koc
6f9cebf1ca test(agents): relax exec wake payload assertions 2026-04-18 08:44:12 -07:00
Vincent Koc
791dbf4f9d fix(openrouter): heal stale provider base urls (#68574)
* fix(openrouter): heal stale provider base urls

* chore(changelog): fix openrouter baseurl entry placement

* fix(arcee): keep catalog config optional
2026-04-18 08:42:51 -07:00
Peter Steinberger
cdaa70facb refactor: cache repeated lazy imports 2026-04-18 16:32:53 +01:00
Vincent Koc
d13869aab9 fix(models): resolve openrouter compat aliases (#68579)
* fix(models): resolve openrouter compat aliases

* fix(models): cover openrouter free interactive alias

* fix(models): mirror openrouter compat aliases in runtime resolver

* fix(models): align openrouter free allowlist aliases
2026-04-18 08:24:34 -07:00
Peter Steinberger
464cbbc9f9 perf: trim plugin and skills test overhead 2026-04-18 16:23:00 +01:00
Peter Steinberger
aa73df571d perf: narrow auth test mocks 2026-04-18 16:23:00 +01:00
Peter Steinberger
4852935e8e perf: speed exec event test waits 2026-04-18 16:23:00 +01:00
Peter Steinberger
c035c5c0d2 refactor: cache lazy runtime imports 2026-04-18 16:18:26 +01:00
Vincent Koc
68502c90d1 fix(openrouter): parse visible reasoning_details output (#68577)
* fix(openrouter): parse visible reasoning_details output

* fix(openrouter): preserve reasoning_details ordering

* fix(openrouter): harden reasoning details compat

* fix(openrouter): queue post-tool-call reasoning text

* chore(config): refresh generated schema baselines

* fix(openrouter): keep fallback reasoning with visible details

* fix(openrouter): bound streaming tool-call buffers
2026-04-18 08:18:13 -07:00
Peter Steinberger
66385670e4 refactor: reduce unnecessary dynamic imports 2026-04-18 16:15:33 +01:00
Peter Steinberger
3f2e73b723 chore(release): bump version to 2026.4.18 2026-04-18 15:46:33 +01:00
Peter Steinberger
cf88e4876d docs(changelog): prepare 2026.4.18 notes 2026-04-18 15:46:26 +01:00
2876 changed files with 114903 additions and 66231 deletions

View File

@@ -1 +0,0 @@
Maintainer skills now live in [`openclaw/maintainers`](https://github.com/openclaw/maintainers/).

View File

@@ -14,6 +14,36 @@ Use this skill for release and publish-time workflow. Keep ordinary development
- This skill should be sufficient to drive the normal release flow end-to-end.
- Use the private maintainer release docs for credentials, recovery steps, and mac signing/notary specifics, and use `docs/reference/RELEASING.md` for public policy.
- Core `openclaw` publish is manual `workflow_dispatch`; creating or pushing a tag does not publish by itself.
- Normal release work happens on a branch cut from `main`, not directly on
`main`. Use `release/YYYY.M.D` for the branch name.
- If the operator asks for a release without saying stable/full, default to
beta only. Continue from beta to stable only when the operator explicitly asks
for the full release or an automated beta-and-stable train.
- Before release branching, pull latest `main` and confirm current `main` CI is
green. Then branch from that commit so regular development can continue on
`main` while release validation runs.
- Before release branching, commit any dirty files in coherent groups, push,
pull/rebase, then run `/changelog` on `main` and commit/push/pull that
changelog rewrite immediately before creating the release branch.
- Do not delete or rewrite beta tags after they leave the machine. If a
published or pushed beta needs a fix, commit the fix on the release branch and
increment to the next `-beta.N`.
- For a beta release train, run the full pre-npm test roster before publishing
each beta. After a beta is published, run the smaller published-install roster
focused on install/update/Docker/Parallels. If anything fails, fix it on the
release branch, commit/push/pull, increment beta number, and repeat. Operators
may authorize up to 4 autonomous beta attempts; after 4 failed beta attempts,
stop and report.
- Use `/changelog` before version/tag preparation so the top changelog section
is deduped and ordered by user impact.
- Do not create beta-specific `CHANGELOG.md` headings. Beta releases use the
stable base version section, for example `v2026.4.20-beta.1` uses
`## 2026.4.20` release notes.
- When any beta or stable release is live, make a best-effort Discord
announcement using Peter's bot token from `.profile`; do not block or roll
back the release if the announcement fails.
- When asked to announce on X, use `~/Projects/bird/bird` and follow the
release tweet style below.
## Keep release channel naming aligned
@@ -37,7 +67,9 @@ Use this skill for release and publish-time workflow. Keep ordinary development
- For fallback correction tags like `vYYYY.M.D-N`, the repo version locations still stay at `YYYY.M.D`.
- “Bump version everywhere” means all version locations above except `appcast.xml`.
- Release signing and notary credentials live outside the repo in the private maintainer docs.
- Every OpenClaw release ships the npm package and macOS app together.
- Every stable OpenClaw release ships the npm package and macOS app together.
Beta releases normally ship npm/package artifacts first and skip mac app
build/sign/notarize unless the operator requests mac beta validation.
- The production Sparkle feed lives at `https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml`, and the canonical published file is `appcast.xml` on `main` in the `openclaw` repo.
- That shared production Sparkle feed is stable-only. Beta mac releases may
upload assets to the GitHub prerelease, but they must not replace the shared
@@ -53,17 +85,77 @@ Use this skill for release and publish-time workflow. Keep ordinary development
- When cutting a mac release with a beta GitHub prerelease:
- tag `vYYYY.M.D-beta.N` from the release commit
- create a prerelease titled `openclaw YYYY.M.D-beta.N`
- use release notes from the matching `CHANGELOG.md` version section
- use release notes from the stable base `CHANGELOG.md` version section
(`## YYYY.M.D`), not a beta-specific heading
- attach at least the zip and dSYM zip, plus dmg if available
- Keep the top version entries in `CHANGELOG.md` sorted by impact:
- `### Changes` first
- `### Fixes` deduped with user-facing fixes first
## Write release tweets
Use the OpenClaw account's existing release-post style:
- Format: `OpenClaw YYYY.M.D 🦞` or `🦞 OpenClaw YYYY.M.D is live`, blank line,
then 3-4 emoji-led bullets, blank line, one short punchline, then the release
link.
- For beta: say `OpenClaw YYYY.M.D-beta.N 🦞` or `OpenClaw YYYY.M.D beta N is
live`; keep it clearly beta and avoid implying stable promotion.
- Lead with user-visible capabilities, then important integrations, then
reliability/security/install fixes. Compress "lots of fixes" into one
readable bullet.
- Tone: high-signal, slightly cheeky, confident, not corporate. One joke is
enough. Avoid punching down, insulting users, or promising what was not
verified.
- Length: release tweets are always standard tweets under 280 characters. Trim
to 3-4 bullets and count the final text before posting.
- Links/media: include the GitHub release or changelog link at the end. Add a
short docs follow-up reply only when there is a standout feature that needs
setup instructions.
- Hotfix/correction: be direct and accountable. State what slipped, what is
fixed, and the new version. Keep jokes out of incident-style posts.
Examples to adapt:
```text
OpenClaw 2026.4.20-beta.1 🦞
🐳 Docker install/update smoke
🖥️ Parallels upgrade checks
🔧 Package verification tightened
Beta first. Stable after the gauntlet.
<release link>
```
```text
OpenClaw 2026.4.20 🦞
🚀 Faster install + update
🐳 Docker + Parallels verified
🍎 macOS signed + notarized
🔧 Channel/plugin fixes
Good boring release. Best kind.
<release link>
```
```text
Packaging issue in 2026.4.20-beta.1.
2026.4.20-beta.2 fixes install/update verification. No tag rewrites; beta moves
forward.
Upgrade with the beta channel.
<release link>
```
## Run publish-time validation
Before tagging or publishing, run:
```bash
pnpm check:architecture
pnpm build
pnpm ui:build
pnpm release:check
@@ -106,16 +198,46 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
## Check all relevant release builds
- Always validate the OpenClaw npm release path before creating the tag.
- Source Peter's profile before live release validation so OpenAI and Anthropic
credentials are available without printing secrets:
`set -a; source "$HOME/.profile"; set +a`.
- Release QA and Parallels validation for this train must use both
`OPENAI_API_KEY` and `ANTHROPIC_API_KEY`. If either is missing after sourcing
`.profile`, stop before starting the long lanes and report the missing key.
- Default release checks:
- `pnpm check`
- `pnpm check:test-types`
- `pnpm check:architecture`
- `pnpm build`
- `pnpm ui:build`
- `pnpm release:check`
- `OPENCLAW_INSTALL_SMOKE_SKIP_NONROOT=1 pnpm test:install:smoke`
- Full pre-npm beta test roster:
- default release checks above
- all Docker tests: `pnpm test:docker:all`, plus standalone Docker live lanes
not covered by the aggregate when operator says "all docker tests":
`pnpm test:docker:live-acp-bind`, `pnpm test:docker:live-cli-backend`, and
`pnpm test:docker:live-codex-harness`
- all Parallels install/update tests:
`pnpm test:parallels:npm-update -- --json` plus any needed individual
rerun lanes from `openclaw-parallels-smoke`
- all QA release validation:
OpenAI live suite with `openai/gpt-5.4` in fast mode, Anthropic live suite
with `anthropic/claude-opus-4-6`, and the repo-backed character evals
- Post-published beta verification roster:
- `node --import tsx scripts/openclaw-npm-postpublish-verify.ts <beta-version>`
- install/update smoke against the published beta channel
- Docker install/update coverage that exercises the published beta package
- Parallels published beta install/update coverage with both OpenAI and
Anthropic provider keys available
- targeted QA reruns only for areas touched by fixes after the full pre-npm
roster, unless the operator requests the full QA roster again
- Check all release-related build surfaces touched by the release, not only the npm package.
- For beta-style full e2e batteries, hard-cap top-level long lanes instead of letting them run indefinitely. Use host `timeout --foreground`/`gtimeout --foreground` caps such as:
- `45m` for `OPENCLAW_INSTALL_SMOKE_SKIP_NONROOT=1 pnpm test:install:smoke`
- `90m` for `pnpm test:docker:all`
- `60m` each for standalone Docker live lanes
- `180m` for the full QA live OpenAI + Anthropic roster
- Parallels caps from the `openclaw-parallels-smoke` skill
If a lane hits its cap, stop and inspect/fix the affected lane before continuing; do not continue to wait on the same process.
- Actual npm install/update phases are capped at 5 minutes. If `npm install -g`, installer package install, or `openclaw update` takes longer than 300s in release e2e, stop treating the run as healthy progress and debug the installer/updater or harness.
@@ -129,6 +251,8 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
- Any fix after preflight means a new commit. Delete and recreate the tag and
matching GitHub release from the fixed commit, then rerun preflight from
scratch before publishing.
Exception: never delete or recreate a beta tag that has already been pushed or
published; increment to the next beta number instead.
- For stable mac releases, generate the signed `appcast.xml` before uploading
public release assets so the updater feed cannot lag the published binaries.
- Serialize stable appcast-producing runs across tags so two releases do not
@@ -139,14 +263,13 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
## Use the right auth flow
- OpenClaw publish uses GitHub trusted publishing.
- Stable npm promotion from `beta` to `latest` is an explicit mode on
`.github/workflows/openclaw-npm-release.yml`, but it still needs a valid
`NPM_TOKEN` because `npm dist-tag` management is separate from trusted
publishing.
- Direct stable publishes can also run the same workflow with
`sync_stable_dist_tags=true` to point both `latest` and `beta` at the
already-published stable version. This also needs the `npm-release`
environment approval and `NPM_TOKEN`.
- Stable npm promotion from `beta` to `latest` uses the private
`openclaw/releases-private/.github/workflows/openclaw-npm-dist-tags.yml`
workflow because `npm dist-tag` management needs `NPM_TOKEN`, while the
public npm release workflow stays OIDC-only.
- Direct stable publishes can also use that private dist-tag workflow to point
`beta` at the already-published `latest` version when the operator wants both
tags aligned immediately.
- The publish run must be started manually with `workflow_dispatch`.
- The npm workflow and the private mac publish workflow accept
`preflight_only=true` to run validation/build/package steps without uploading
@@ -162,8 +285,9 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
- `preflight_only=true` on the npm workflow is also the right way to validate an
existing tag after publish; it should keep running the build checks even when
the npm version is already published.
- Validation-only runs may be dispatched from a branch when you are testing a
workflow change before merge.
- npm validation-only preflight may still be dispatched from ordinary branches
when testing workflow changes before merge. Release checks and real publish
use only `main` or `release/YYYY.M.D`.
- `.github/workflows/macos-release.yml` in `openclaw/openclaw` is now a
public validation-only handoff. It validates the tag/release state and points
operators to the private repo. It still rebuilds the JS outputs needed for
@@ -171,7 +295,7 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
artifacts.
- `openclaw/releases-private/.github/workflows/openclaw-macos-validate.yml`
is the required private mac validation lane for `swift test`; keep it green
before any real mac publish run starts.
before any real stable mac publish run starts.
- Real mac preflight and real mac publish both use
`openclaw/releases-private/.github/workflows/openclaw-macos-publish.yml`.
- The private mac validation lane runs on GitHub's standard macOS runner.
@@ -181,10 +305,15 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
instead of uploading public GitHub release assets.
- Private smoke-test runs upload ad-hoc, non-notarized build artifacts as
workflow artifacts and intentionally skip stable `appcast.xml` generation.
- npm preflight, public mac validation, private mac validation, and private mac
preflight must all pass before any real publish run starts.
- Real publish runs must be dispatched from `main`; branch-dispatched publish
attempts should fail before the protected environment is reached.
- For stable releases, npm preflight, public mac validation, private mac
validation, and private mac preflight must all pass before any real publish
run starts. For beta releases, npm preflight plus the selected Docker,
install/update, Parallels, and release-check lanes are sufficient unless mac
beta validation was explicitly requested.
- Real publish runs may be dispatched from `main` or from a
`release/YYYY.M.D` branch. For release-branch runs, the tag must be contained
in that release branch, and the real publish must reuse a successful preflight
from the same branch.
- The release workflows stay tag-based; rely on the documented release sequence
rather than workflow-level SHA pinning.
- The `npm-release` environment must be approved by `@openclaw/openclaw-release-managers` before publish continues.
@@ -245,58 +374,82 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
1. Confirm the operator explicitly wants to cut a release.
2. Choose the exact target version and git tag.
3. Make every repo version location match that tag before creating it.
4. Update `CHANGELOG.md` and assemble the matching GitHub release notes.
5. Run the full preflight for all relevant release builds, including mac readiness.
6. Confirm the target npm version is not already published.
7. Create and push the git tag.
8. Create or refresh the matching GitHub release.
9. Start `.github/workflows/openclaw-npm-release.yml` with `preflight_only=true`
and choose the intended `npm_dist_tag` (`beta` default; `latest` only for
an intentional direct stable publish). Wait for it to pass. Save that run id
because the real publish requires it to reuse the prepared npm tarball.
10. Start `.github/workflows/macos-release.yml` in `openclaw/openclaw` and wait
for the public validation-only run to pass.
11. Start
3. Commit any dirty files in coherent groups, push, pull/rebase, and verify the
worktree is clean.
4. Pull latest `main` and confirm current `main` CI is green.
5. Run `/changelog` for the stable base target version on `main`, commit the
changelog rewrite immediately, push, and pull/rebase. For beta releases,
keep the changelog heading as `## YYYY.M.D`, not `## YYYY.M.D-beta.N`.
6. Create `release/YYYY.M.D` from that post-changelog `main` commit.
7. Make every repo version location match the beta tag before creating it.
8. Commit release preparation changes on the release branch and push the branch.
9. Run the full pre-npm beta test roster from the release branch before any npm
preflight or publish.
10. For beta releases, skip mac app build/sign/notarize unless beta scope or a
release blocker specifically requires it. For stable releases, include the
mac app, signing, notarization, and appcast path.
11. Confirm the target npm version is not already published.
12. Create and push the git tag from the release branch.
13. Create or refresh the matching GitHub release.
14. Start `.github/workflows/openclaw-npm-release.yml` from the release branch
with `preflight_only=true`
and choose the intended `npm_dist_tag` (`beta` default; `latest` only for
an intentional direct stable publish). Wait for it to pass. Save that run id
because the real publish requires it to reuse the prepared npm tarball.
15. For stable releases, start `.github/workflows/macos-release.yml` in
`openclaw/openclaw` and wait for the public validation-only run to pass.
16. For stable releases, start
`openclaw/releases-private/.github/workflows/openclaw-macos-validate.yml`
with the same tag and wait for the private mac validation lane to pass.
12. Start
17. For stable releases, start
`openclaw/releases-private/.github/workflows/openclaw-macos-publish.yml`
with `preflight_only=true` and wait for it to pass. Save that run id because
the real publish requires it to reuse the notarized mac artifacts.
13. If any preflight or validation run fails, fix the issue on a new commit,
18. If any preflight or validation run fails, fix the issue on a new commit,
delete the tag and matching GitHub release, recreate them from the fixed
commit, and rerun all relevant preflights from scratch before continuing.
Never reuse old preflight results after the commit changes.
14. Start `.github/workflows/openclaw-npm-release.yml` with the same tag for
the real publish, choose `npm_dist_tag` (`beta` default, `latest` only when
you intentionally want direct stable publish), keep it the same as the
preflight run, and pass the successful npm `preflight_run_id`.
15. Wait for `npm-release` approval from `@openclaw/openclaw-release-managers`.
16. If the stable release was published to `beta`, start
`.github/workflows/openclaw-npm-release.yml` again after beta validation
passes with the same stable tag, `promote_beta_to_latest=true`,
`preflight_only=false`, empty `preflight_run_id`, and `npm_dist_tag=beta`,
then verify `latest` now points at that version.
17. If the stable release was published directly to `latest` and `beta` should
follow it, start `.github/workflows/openclaw-npm-release.yml` again with
the same stable tag, `sync_stable_dist_tags=true`,
`promote_beta_to_latest=false`, `preflight_only=false`, empty
`preflight_run_id`, and `npm_dist_tag=latest`, then verify both `latest`
and `beta` point at that version.
18. Start
Never reuse old preflight results after the commit changes. For pushed or
published beta tags, do not delete/recreate; increment to the next beta tag.
19. Start `.github/workflows/openclaw-npm-release.yml` from the same branch with
the same tag for the real publish, choose `npm_dist_tag` (`beta` default,
`latest` only when you intentionally want direct stable publish), keep it
the same as the preflight run, and pass the successful npm
`preflight_run_id`.
20. Wait for `npm-release` approval from `@openclaw/openclaw-release-managers`.
21. Run postpublish verification:
`node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>`.
22. Run the post-published beta verification roster. If any lane fails after
the beta tag/package is pushed or published, fix, commit/push/pull,
increment to the next beta tag, and restart at the full pre-npm beta test
roster for the new beta. If a pre-npm lane fails before any tag/package
leaves the machine, fix and rerun the same intended beta attempt. Repeat up
to the operator's authorized beta-attempt limit, normally 4.
23. Announce the beta/stable release on Discord best-effort using Peter's bot
token from `.profile`.
24. If the operator requested beta only, stop after beta verification and the
announcement.
25. If the stable release was published to `beta`, start the private
`openclaw/releases-private/.github/workflows/openclaw-npm-dist-tags.yml`
workflow after beta validation passes to promote that stable version from
`beta` to `latest`, then verify `latest` now points at that version.
26. If the stable release was published directly to `latest` and `beta` should
follow it, start that same private dist-tag workflow to point `beta` at the
stable version, then verify both `latest` and `beta` point at that version.
27. For stable releases, start
`openclaw/releases-private/.github/workflows/openclaw-macos-publish.yml`
for the real publish with the successful private mac `preflight_run_id` and
wait for success.
19. Verify the successful real private mac run uploaded the `.zip`, `.dmg`,
28. Verify the successful real private mac run uploaded the `.zip`, `.dmg`,
and `.dSYM.zip` artifacts to the existing GitHub release in
`openclaw/openclaw`.
20. For stable releases, download `macos-appcast-<tag>` from the successful
private mac run, update `appcast.xml` on `main`, and verify the feed.
21. For beta releases, publish the mac assets but expect no shared production
29. For stable releases, download `macos-appcast-<tag>` from the successful
private mac run, update `appcast.xml` on `main`, and verify the feed. Merge
or cherry-pick release branch changes back to `main` after stable succeeds.
30. For beta releases, publish the mac assets only when intentionally requested;
expect no shared production
`appcast.xml` artifact and do not update the shared production feed unless a
separate beta feed exists.
22. After publish, verify npm and the attached release artifacts.
31. After publish, verify npm and the attached release artifacts.
## GHSA advisory work

View File

@@ -0,0 +1,134 @@
---
name: openclaw-test-performance
description: Benchmark, diagnose, and optimize OpenClaw test performance without losing coverage. Use when Codex needs to reassess `pnpm test`, compare grouped Vitest reports, identify CPU/memory/import hotspots, fix slow tests or cold runtime paths, preserve behavior proofs, update the performance report, add AGENTS guardrails, and make scoped commits/pushes for OpenClaw test-speed work.
---
# OpenClaw Test Performance
Use evidence first. The goal is real `pnpm test` speed/RSS improvement with
coverage intact, not runner tuning by guesswork.
## Workflow
1. Read the relevant local `AGENTS.md` files before editing:
- `src/agents/AGENTS.md` for agent/import hotspots.
- `src/channels/AGENTS.md` and `src/plugins/AGENTS.md` for plugin/channel
laziness.
- `src/gateway/AGENTS.md` for server lifecycle tests.
- `test/helpers/AGENTS.md` and `test/helpers/channels/AGENTS.md` for shared
contract helpers.
- `src/infra/outbound/AGENTS.md` for outbound/media/action tests.
2. Establish a baseline before changing code:
- Prefer `pnpm test:perf:groups --full-suite --allow-failures --output <file>`
for full-suite ranking.
- For a scoped hotspot use:
`/usr/bin/time -l pnpm test <file-or-files> --maxWorkers=1 --reporter=verbose`
- For import-heavy suspicion add:
`OPENCLAW_VITEST_IMPORT_DURATIONS=1 OPENCLAW_VITEST_PRINT_IMPORT_BREAKDOWN=1`.
3. Separate wall/runner noise from real file cost:
- Compare Vitest duration, test body timing, import breakdown, wall time, and
max RSS.
- Re-run single files when grouped/full-suite numbers look stale or noisy.
- If a full-suite grouped run reports a lane failure but JSON says tests
passed, capture that as harness/noise and verify the suspect file directly.
4. Pick the next attack by return and risk:
- High return: one file/test dominates seconds or RSS and has a clear root.
- Lower risk: static descriptors, target parsing, routing, auth bypass,
setup hints, registry fixtures, or test server lifecycle.
- Higher risk: real memory/runtime behavior, live providers, protocol
contracts, or broad production refactors.
5. Fix the root cause, not the symptom:
- Move static metadata/parsing into narrow helpers or lightweight artifacts
reused by full runtime and fast paths.
- Prefer dependency injection, loaded-plugin-only lookup, explicit fixtures,
and pure helpers over broad mocks.
- Reuse suite-level servers/clients when a fresh handshake is irrelevant.
- Keep schedulers/background loops off unless the test proves scheduling.
6. Preserve coverage shape:
- Do not delete a slow integration proof unless the exact production
composition is extracted into a named helper and tested.
- Keep one cheap integration smoke when cross-component wiring matters.
- State explicitly what incidental coverage was removed, if any.
7. Re-benchmark the same command after the change and compute seconds plus
percent gain.
8. Update the running report when requested or when this thread is tracking one.
Include before/after commands, artifacts, coverage notes, verification, and
next attack order.
9. Commit with `scripts/committer "<message>" <paths...>` and push when the
user asked for commits/pushes. Stage only files touched for this attack.
## Common Root Causes
- Full bundled channel/plugin runtime loaded for static data.
- `getChannelPlugin()` fallback used when an already-loaded fixture or pure
parser would suffice.
- Broad `api.ts`, `runtime-api.ts`, `test-api.ts`, or plugin-sdk barrels pulled
into hot tests.
- Partial-real mocks using `importActual()` around broad modules.
- `vi.resetModules()` plus fresh imports in per-test loops.
- Test plugin registry seeded in `beforeAll` while runtime state resets in
`afterEach`.
- Per-test gateway/server/client startup when state reset would suffice.
- Runtime/default model/auth selection paid by idle snapshots or fixtures.
- Plugin-owned media/action discovery triggered before checking whether args
contain plugin-owned fields.
## Benchmark Commands
Scoped file:
```bash
timeout 240 /usr/bin/time -l pnpm test <file> --maxWorkers=1 --reporter=verbose
```
Scoped file with import breakdown:
```bash
timeout 240 /usr/bin/time -l env \
OPENCLAW_VITEST_IMPORT_DURATIONS=1 \
OPENCLAW_VITEST_PRINT_IMPORT_BREAKDOWN=1 \
pnpm test <file> --maxWorkers=1 --reporter=verbose
```
Grouped suite:
```bash
pnpm test:perf:groups --full-suite --allow-failures \
--output .artifacts/test-perf/<name>.json
```
Reuse an existing Vitest JSON report:
```bash
pnpm test:perf:groups --report <vitest-json> \
--output .artifacts/test-perf/<name>.json
```
## Verification
- Always run the targeted test surface that proves the change.
- Run `pnpm check` before commit unless the change is docs-only and the hook
handles it.
- Run `pnpm build` when touching lazy-loading, bundled artifacts, package
boundaries, dynamic imports, build output, or public surfaces.
- If deps are missing/stale, run `pnpm install` and retry the exact failed
command once.
- Use the report format:
```markdown
| Metric | Before | After | Gain |
| -------------- | -----: | ----: | ------------: |
| File wall time | `Xs` | `Ys` | `-Zs` (`P%`) |
| Max RSS | `XMB` | `YMB` | `-ZMB` (`P%`) |
```
## Handoff
Keep the final concise:
- Root cause.
- Files changed.
- Before/after numbers.
- Coverage retained.
- Verification commands.
- Commit hash and push status.

View File

@@ -0,0 +1,6 @@
interface:
display_name: "OpenClaw Test Performance"
short_description: "Benchmark and fix slow OpenClaw tests"
default_prompt: "Use $openclaw-test-performance to reassess the OpenClaw test benchmark, identify the next real hotspot, fix it without losing coverage, update the report, and commit scoped changes."
policy:
allow_implicit_invocation: false

View File

@@ -19,10 +19,6 @@ inputs:
description: Whether to install Bun alongside Node.
required: false
default: "true"
use-sticky-disk:
description: Request Blacksmith sticky-disk pnpm caching on trusted runs; pull_request runs fall back to actions/cache.
required: false
default: "false"
install-deps:
description: Whether to run pnpm install after environment setup.
required: false
@@ -45,7 +41,6 @@ runs:
with:
pnpm-version: ${{ inputs.pnpm-version }}
cache-key-suffix: ${{ inputs.cache-key-suffix }}
use-sticky-disk: ${{ inputs.use-sticky-disk }}
- name: Setup Bun
if: inputs.install-bun == 'true'
@@ -64,7 +59,12 @@ runs:
- name: Capture node path
if: inputs.install-deps == 'true'
shell: bash
run: echo "NODE_BIN=$(dirname "$(node -p "process.execPath")")" >> "$GITHUB_ENV"
run: |
node_bin="$(dirname "$(node -p 'process.execPath')")"
if command -v cygpath >/dev/null 2>&1; then
node_bin="$(cygpath -u "$node_bin")"
fi
echo "NODE_BIN=$node_bin" >> "$GITHUB_ENV"
- name: Install dependencies
if: inputs.install-deps == 'true'

View File

@@ -9,16 +9,12 @@ inputs:
description: Suffix appended to the cache key.
required: false
default: "node24"
use-sticky-disk:
description: Use Blacksmith sticky disks instead of actions/cache for pnpm store on trusted runs; pull_request runs fall back to actions/cache.
required: false
default: "false"
use-restore-keys:
description: Whether to use restore-keys fallback for actions/cache.
required: false
default: "true"
use-actions-cache:
description: Whether to restore/save pnpm store with actions/cache, including pull_request fallback when sticky disks are disabled.
description: Whether to restore/save pnpm store with actions/cache.
required: false
default: "true"
runs:
@@ -50,24 +46,15 @@ runs:
shell: bash
run: echo "path=$(pnpm store path --silent)" >> "$GITHUB_OUTPUT"
- name: Mount pnpm store sticky disk
# Keep persistent sticky-disk state off untrusted PR runs.
if: inputs.use-sticky-disk == 'true' && github.event_name != 'pull_request'
uses: useblacksmith/stickydisk@v1
with:
key: ${{ github.repository }}-pnpm-store-${{ runner.os }}-${{ github.ref_name }}-${{ inputs.cache-key-suffix }}-${{ hashFiles('pnpm-lock.yaml') }}
path: ${{ steps.pnpm-store.outputs.path }}
- name: Restore pnpm store cache (exact key only)
# PRs that request sticky disks still need a safe cache restore path.
if: inputs.use-actions-cache == 'true' && (inputs.use-sticky-disk != 'true' || github.event_name == 'pull_request') && inputs.use-restore-keys != 'true'
if: inputs.use-actions-cache == 'true' && inputs.use-restore-keys != 'true'
uses: actions/cache@v5
with:
path: ${{ steps.pnpm-store.outputs.path }}
key: ${{ runner.os }}-pnpm-store-${{ inputs.cache-key-suffix }}-${{ hashFiles('pnpm-lock.yaml') }}
- name: Restore pnpm store cache (with fallback keys)
if: inputs.use-actions-cache == 'true' && (inputs.use-sticky-disk != 'true' || github.event_name == 'pull_request') && inputs.use-restore-keys == 'true'
if: inputs.use-actions-cache == 'true' && inputs.use-restore-keys == 'true'
uses: actions/cache@v5
with:
path: ${{ steps.pnpm-store.outputs.path }}

View File

@@ -49,14 +49,14 @@
- TypeScript (ESM), strict typing, avoid `any`
- Keep files under ~700 LOC - extract helpers when larger
- Colocated tests: `*.test.ts` next to source files
- Run `pnpm check` before commits (lint + format)
- Run `pnpm tsgo` for type checking
- Run `pnpm check` before commits (production type check + lint + format)
- Run `pnpm check:test-types` when you need test type coverage, or `pnpm tsgo:all` for a full production plus test type sweep
## Stack & Commands
- **Package manager**: pnpm (`pnpm install`)
- **Dev**: `pnpm openclaw ...` or `pnpm dev`
- **Type-check**: `pnpm tsgo`
- **Type-check**: `pnpm tsgo` (core production), `pnpm tsgo:prod` (core + extension production), `pnpm check:test-types` (tests)
- **Lint/format**: `pnpm check`
- **Tests**: `pnpm test`
- **Build**: `pnpm build`

File diff suppressed because it is too large Load Diff

View File

@@ -81,7 +81,6 @@ jobs:
uses: ./.github/actions/setup-node-env
with:
install-bun: "false"
use-sticky-disk: "false"
- name: Setup Python
if: matrix.needs_python

View File

@@ -121,7 +121,6 @@ jobs:
uses: ./.github/actions/setup-node-env
with:
install-bun: "false"
use-sticky-disk: "false"
- name: Ensure translation provider secrets exist
env:

View File

@@ -18,7 +18,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout source repo
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0

View File

@@ -64,7 +64,6 @@ jobs:
with:
install-bun: "false"
install-deps: "false"
use-sticky-disk: "false"
- name: Build install-smoke CI manifest
id: manifest
@@ -86,7 +85,7 @@ jobs:
install-smoke:
needs: [preflight]
if: needs.preflight.outputs.run_install_smoke == 'true'
runs-on: blacksmith-16vcpu-ubuntu-2404
runs-on: blacksmith-32vcpu-ubuntu-2404
env:
DOCKER_BUILD_SUMMARY: "false"
DOCKER_BUILD_RECORD_UPLOAD: "false"
@@ -94,11 +93,11 @@ jobs:
- name: Checkout CLI
uses: actions/checkout@v6
- name: Set up Docker Builder
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
- name: Set up Blacksmith Docker Builder
uses: useblacksmith/setup-docker-builder@ac083cc84672d01c60d5e8561d0a939b697de542 # v1
# Blacksmith can fall back to the local docker driver, which rejects gha
# cache export/import. Keep smoke builds driver-agnostic.
# Blacksmith's builder owns the Docker layer cache; keep smoke builds off
# explicit gha cache directives so local tags still load cleanly.
- name: Build root Dockerfile smoke image
uses: useblacksmith/build-push-action@cbd1f60d194a98cb3be5523b15134501eaf0fbf3 # v2
with:
@@ -202,7 +201,6 @@ jobs:
with:
install-bun: "false"
install-deps: "true"
use-sticky-disk: "false"
- name: Run installer docker tests
env:

View File

@@ -50,7 +50,6 @@ jobs:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "false"
use-sticky-disk: "false"
- name: Ensure matching GitHub release exists
env:

View File

@@ -230,7 +230,6 @@ jobs:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "true"
use-sticky-disk: "false"
- name: Validate live cache credentials
run: |
@@ -267,7 +266,6 @@ jobs:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "true"
use-sticky-disk: "false"
- name: Build dist for repo E2E
run: pnpm build
@@ -313,7 +311,6 @@ jobs:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "true"
use-sticky-disk: "false"
- name: Build dist for special E2E
if: |
@@ -384,6 +381,12 @@ jobs:
timeout_minutes: 75
release_path: true
openwebui_only: false
- suite_id: docker-bundled-channel-deps
label: Bundled Channel Runtime Deps Docker E2E
command: pnpm test:docker:bundled-channel-deps
timeout_minutes: 75
release_path: true
openwebui_only: false
- suite_id: docker-doctor-switch
label: Doctor Install Switch Docker E2E
command: pnpm test:docker:doctor-switch
@@ -465,7 +468,6 @@ jobs:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "true"
use-sticky-disk: "false"
- name: Hydrate live auth/profile inputs
run: bash scripts/ci-hydrate-live-auth.sh
@@ -608,7 +610,6 @@ jobs:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "true"
use-sticky-disk: "false"
- name: Hydrate live auth/profile inputs
run: bash scripts/ci-hydrate-live-auth.sh

View File

@@ -4,7 +4,7 @@ on:
workflow_dispatch:
inputs:
tag:
description: Release tag to publish, or a full 40-character main commit SHA for validation-only preflight (for example v2026.3.22 or 0123456789abcdef0123456789abcdef01234567)
description: Release tag to publish, or a full 40-character workflow-branch commit SHA for validation-only preflight (for example v2026.3.22 or 0123456789abcdef0123456789abcdef01234567)
required: true
type: string
preflight_only:
@@ -85,7 +85,6 @@ jobs:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "true"
use-sticky-disk: "false"
- name: Ensure version is not already published
env:
@@ -110,6 +109,16 @@ jobs:
OPENCLAW_LOCAL_CHECK: "0"
run: pnpm check
- name: Check test types
env:
OPENCLAW_LOCAL_CHECK: "0"
run: pnpm check:test-types
- name: Check architecture
env:
OPENCLAW_LOCAL_CHECK: "0"
run: pnpm check:architecture
- name: Build
run: pnpm build
@@ -122,19 +131,20 @@ jobs:
OPENCLAW_NPM_RELEASE_SKIP_PACK_CHECK: "1"
RELEASE_REF: ${{ inputs.tag }}
PREFLIGHT_ONLY: ${{ inputs.preflight_only }}
RELEASE_MAIN_REF: origin/main
WORKFLOW_REF_NAME: ${{ github.ref_name }}
OPENCLAW_NPM_PUBLISH_TAG: ${{ inputs.npm_dist_tag }}
run: |
set -euo pipefail
RELEASE_SHA=$(git rev-parse HEAD)
export RELEASE_SHA RELEASE_MAIN_REF
# Fetch the full main ref so merge-base ancestry checks keep working
# for older tagged commits that are still contained in main.
git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main
RELEASE_BRANCH_REF="refs/remotes/origin/${WORKFLOW_REF_NAME}"
export RELEASE_SHA RELEASE_BRANCH_REF
# Fetch the workflow branch so merge-base ancestry checks keep working
# for older tagged commits contained in a release branch.
git fetch --no-tags origin "+refs/heads/${WORKFLOW_REF_NAME}:refs/remotes/origin/${WORKFLOW_REF_NAME}"
if [[ "${RELEASE_REF}" =~ ^[0-9a-fA-F]{40}$ ]]; then
MAIN_SHA="$(git rev-parse origin/main)"
if [[ "${RELEASE_SHA}" != "${MAIN_SHA}" ]]; then
echo "Validation-only SHA mode only supports the current origin/main HEAD." >&2
BRANCH_SHA="$(git rev-parse "${RELEASE_BRANCH_REF}")"
if [[ "${RELEASE_SHA}" != "${BRANCH_SHA}" ]]; then
echo "Validation-only SHA mode only supports the current ${WORKFLOW_REF_NAME} HEAD." >&2
exit 1
fi
RELEASE_TAG="v$(node -p "require('./package.json').version")"
@@ -144,6 +154,8 @@ jobs:
RELEASE_TAG="${RELEASE_REF}"
export RELEASE_TAG
fi
RELEASE_MAIN_REF="${RELEASE_BRANCH_REF}"
export RELEASE_MAIN_REF
pnpm release:openclaw:npm:check
# KEEP THIS LANE LIMITED TO FAST, REPEATABLE RELEASE READINESS CHECKS.
@@ -244,13 +256,13 @@ jobs:
permissions:
contents: read
steps:
- name: Require main workflow ref for publish
- name: Require main or release workflow ref for publish
env:
WORKFLOW_REF: ${{ github.ref }}
run: |
set -euo pipefail
if [[ "${WORKFLOW_REF}" != "refs/heads/main" ]]; then
echo "Real publish runs must be dispatched from main. Use preflight_only=true for branch validation."
if [[ "${WORKFLOW_REF}" != "refs/heads/main" ]] && [[ ! "${WORKFLOW_REF}" =~ ^refs/heads/release/[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*$ ]]; then
echo "Real publish runs must be dispatched from main or release/YYYY.M.D. Use preflight_only=true for other branch validation."
exit 1
fi
@@ -303,7 +315,6 @@ jobs:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "false"
use-sticky-disk: "false"
- name: Ensure version is not already published
run: |
@@ -321,10 +332,11 @@ jobs:
env:
GH_TOKEN: ${{ github.token }}
PREFLIGHT_RUN_ID: ${{ inputs.preflight_run_id }}
EXPECTED_PREFLIGHT_BRANCH: ${{ github.ref_name }}
run: |
set -euo pipefail
RUN_JSON="$(gh run view "$PREFLIGHT_RUN_ID" --repo "$GITHUB_REPOSITORY" --json workflowName,headBranch,event,conclusion,url)"
printf '%s' "$RUN_JSON" | node -e 'const fs = require("node:fs"); const run = JSON.parse(fs.readFileSync(0, "utf8")); const checks = [["workflowName", "OpenClaw NPM Release"], ["headBranch", "main"], ["event", "workflow_dispatch"], ["conclusion", "success"]]; for (const [key, expected] of checks) { if (run[key] !== expected) { console.error(`Referenced npm preflight run ${process.env.PREFLIGHT_RUN_ID} must have ${key}=${expected}, got ${run[key] ?? "<missing>"}.`); process.exit(1); } } console.log(`Using npm preflight run ${process.env.PREFLIGHT_RUN_ID}: ${run.url}`);'
printf '%s' "$RUN_JSON" | node -e 'const fs = require("node:fs"); const run = JSON.parse(fs.readFileSync(0, "utf8")); const checks = [["workflowName", "OpenClaw NPM Release"], ["headBranch", process.env.EXPECTED_PREFLIGHT_BRANCH], ["event", "workflow_dispatch"], ["conclusion", "success"]]; for (const [key, expected] of checks) { if (run[key] !== expected) { console.error(`Referenced npm preflight run ${process.env.PREFLIGHT_RUN_ID} must have ${key}=${expected}, got ${run[key] ?? "<missing>"}.`); process.exit(1); } } console.log(`Using npm preflight run ${process.env.PREFLIGHT_RUN_ID}: ${run.url}`);'
- name: Download prepared npm tarball
uses: actions/download-artifact@v8
@@ -340,14 +352,15 @@ jobs:
env:
OPENCLAW_NPM_RELEASE_SKIP_PACK_CHECK: "1"
RELEASE_TAG: ${{ inputs.tag }}
RELEASE_MAIN_REF: origin/main
WORKFLOW_REF_NAME: ${{ github.ref_name }}
run: |
set -euo pipefail
RELEASE_SHA=$(git rev-parse HEAD)
RELEASE_MAIN_REF="refs/remotes/origin/${WORKFLOW_REF_NAME}"
export RELEASE_SHA RELEASE_TAG RELEASE_MAIN_REF
# Fetch the full main ref so merge-base ancestry checks keep working
# for older tagged commits that are still contained in main.
git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main
# Fetch the workflow branch so merge-base ancestry checks keep working
# for older tagged commits contained in a release branch.
git fetch --no-tags origin "+refs/heads/${WORKFLOW_REF_NAME}:refs/remotes/origin/${WORKFLOW_REF_NAME}"
pnpm release:openclaw:npm:check
- name: Verify prepared tarball provenance

View File

@@ -4,7 +4,7 @@ on:
workflow_dispatch:
inputs:
ref:
description: Existing release tag or current full 40-character main commit SHA to validate (for example v2026.4.12 or 0123456789abcdef0123456789abcdef01234567)
description: Existing release tag or current full 40-character workflow-branch commit SHA to validate (for example v2026.4.12 or 0123456789abcdef0123456789abcdef01234567)
required: true
type: string
provider:
@@ -45,13 +45,13 @@ jobs:
provider: ${{ steps.inputs.outputs.provider }}
mode: ${{ steps.inputs.outputs.mode }}
steps:
- name: Require main workflow ref for release checks
- name: Require main or release workflow ref for release checks
env:
WORKFLOW_REF: ${{ github.ref }}
run: |
set -euo pipefail
if [[ "${WORKFLOW_REF}" != "refs/heads/main" ]]; then
echo "Release checks must be dispatched from main so the workflow logic and secrets stay canonical." >&2
if [[ "${WORKFLOW_REF}" != "refs/heads/main" ]] && [[ ! "${WORKFLOW_REF}" =~ ^refs/heads/release/[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*$ ]]; then
echo "Release checks must be dispatched from main or release/YYYY.M.D so workflow logic and secrets stay controlled." >&2
exit 1
fi
@@ -61,7 +61,7 @@ jobs:
run: |
set -euo pipefail
if [[ ! "${RELEASE_REF}" =~ ^v[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*((-beta\.[1-9][0-9]*)|(-[1-9][0-9]*))?$ ]] && [[ ! "${RELEASE_REF}" =~ ^[0-9a-fA-F]{40}$ ]]; then
echo "Expected an existing release tag or current full 40-character main commit SHA, got: ${RELEASE_REF}" >&2
echo "Expected an existing release tag or current full 40-character workflow-branch commit SHA, got: ${RELEASE_REF}" >&2
exit 1
fi
@@ -75,20 +75,22 @@ jobs:
id: ref
run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
- name: Validate selected ref is on main
- name: Validate selected ref is on workflow branch
env:
RELEASE_REF: ${{ inputs.ref }}
WORKFLOW_REF_NAME: ${{ github.ref_name }}
run: |
set -euo pipefail
git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main
RELEASE_BRANCH_REF="refs/remotes/origin/${WORKFLOW_REF_NAME}"
git fetch --no-tags origin "+refs/heads/${WORKFLOW_REF_NAME}:refs/remotes/origin/${WORKFLOW_REF_NAME}"
if [[ "${RELEASE_REF}" =~ ^[0-9a-fA-F]{40}$ ]]; then
MAIN_SHA="$(git rev-parse origin/main)"
if [[ "$(git rev-parse HEAD)" != "${MAIN_SHA}" ]]; then
echo "Commit SHA mode only supports the current origin/main HEAD. Use a release tag for older commits." >&2
BRANCH_SHA="$(git rev-parse "${RELEASE_BRANCH_REF}")"
if [[ "$(git rev-parse HEAD)" != "${BRANCH_SHA}" ]]; then
echo "Commit SHA mode only supports the current ${WORKFLOW_REF_NAME} HEAD. Use a release tag for older commits." >&2
exit 1
fi
else
git merge-base --is-ancestor HEAD origin/main
git merge-base --is-ancestor HEAD "${RELEASE_BRANCH_REF}"
fi
- name: Capture selected inputs

View File

@@ -48,7 +48,7 @@ jobs:
OPENCLAW_LIVE_SETUP_TOKEN_VALUE: ""
steps:
- name: Checkout PR
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Install pnpm
uses: pnpm/action-setup@v4

View File

@@ -53,7 +53,6 @@ jobs:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "false"
use-sticky-disk: "false"
- name: Resolve checked-out ref
id: ref
@@ -160,7 +159,6 @@ jobs:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "true"
use-sticky-disk: "false"
install-deps: "false"
- name: Checkout ClawHub CLI source
@@ -220,7 +218,6 @@ jobs:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "true"
use-sticky-disk: "false"
install-deps: "false"
- name: Checkout ClawHub CLI source

View File

@@ -63,7 +63,6 @@ jobs:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "false"
use-sticky-disk: "false"
- name: Resolve checked-out ref
id: ref
@@ -161,7 +160,6 @@ jobs:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "false"
use-sticky-disk: "false"
install-deps: "false"
- name: Preview publish command
@@ -196,7 +194,6 @@ jobs:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "false"
use-sticky-disk: "false"
install-deps: "false"
- name: Ensure version is not already published

View File

@@ -92,7 +92,6 @@ jobs:
uses: ./.github/actions/setup-node-env
with:
install-bun: "false"
use-sticky-disk: "false"
- name: Check config docs drift statefile
run: pnpm config:docs:check

View File

@@ -9,20 +9,40 @@
"rules": {
"curly": "error",
"eslint-plugin-unicorn/prefer-array-find": "error",
"eslint/no-array-constructor": "error",
"eslint/no-await-in-loop": "off",
"eslint/no-new": "error",
"eslint/no-object-constructor": "error",
"eslint/no-return-assign": "error",
"eslint/no-shadow": "off",
"eslint/no-useless-call": "error",
"eslint/no-useless-computed-key": "error",
"eslint/no-useless-concat": "error",
"eslint/no-useless-constructor": "error",
"eslint/no-warning-comments": "error",
"eslint/no-unmodified-loop-condition": "error",
"eslint-plugin-unicorn/prefer-set-size": "error",
"oxc/no-accumulating-spread": "error",
"oxc/no-async-endpoint-handlers": "off",
"oxc/no-map-spread": "off",
"oxc/no-async-endpoint-handlers": "error",
"oxc/no-map-spread": "error",
"typescript/consistent-return": "error",
"typescript/no-explicit-any": "error",
"typescript/no-extraneous-class": "error",
"typescript/no-meaningless-void-operator": "error",
"typescript/no-unnecessary-type-assertion": "error",
"typescript/no-unnecessary-type-arguments": "error",
"typescript/no-unnecessary-type-constraint": "error",
"typescript/no-unnecessary-type-conversion": "error",
"typescript/no-unnecessary-type-parameters": "error",
"typescript/no-unsafe-type-assertion": "off",
"typescript/no-useless-default-assignment": "error",
"typescript/prefer-ts-expect-error": "error",
"unicorn/consistent-function-scoping": "off",
"unicorn/no-unnecessary-array-flat-depth": "error",
"unicorn/no-unnecessary-array-splice-count": "error",
"unicorn/no-unnecessary-slice-end": "error",
"unicorn/no-useless-promise-resolve-reject": "error",
"unicorn/prefer-date-now": "error",
"unicorn/prefer-set-size": "error",
"unicorn/require-post-message-target-origin": "error"
},
@@ -47,6 +67,13 @@
"**/node_modules/**"
],
"overrides": [
{
"files": ["src/security/**"],
"rules": {
"eslint/no-warning-comments": "off",
"oxc/no-map-spread": "off"
}
},
{
"files": [
"**/*.test.ts",

View File

@@ -17,5 +17,6 @@
"typescript.preferences.importModuleSpecifierEnding": "js",
"typescript.reportStyleChecksAsWarnings": false,
"typescript.updateImportsOnFileMove.enabled": "always",
"typescript.tsdk": "node_modules/typescript/lib"
"typescript.tsdk": "node_modules/typescript/lib",
"makefile.configureOnOpen": false
}

463
AGENTS.md
View File

@@ -1,322 +1,199 @@
# Repository Guidelines
# AGENTS.MD
- Repo: https://github.com/openclaw/openclaw
- In chat replies, file references must be repo-root relative only (example: `extensions/telegram/src/index.ts:80`); never absolute paths or `~/...`.
- Do not edit files covered by security-focused `CODEOWNERS` rules unless a listed owner explicitly asked for the change or is already reviewing it with you. Treat those paths as restricted surfaces, not drive-by cleanup.
Telegraph style. Root rules only. Read scoped `AGENTS.md` before touching a subtree.
## Project Structure & Module Organization
## Start
- Source code: `src/` (CLI wiring in `src/cli`, commands in `src/commands`, infra in `src/infra`, media pipeline in `src/media`, web provider helpers in `src/web` and `src/plugins/web-*provider*.ts`).
- Tests: colocated `*.test.ts`.
- Docs: `docs/` (images, queue, Pi config). Built output lives in `dist/`.
- Nomenclature: use "plugin" / "plugins" in docs, UI, changelogs, and contributor guidance. The bundled workspace plugin tree remains the internal package layout to avoid repo-wide churn from a rename.
- Bundled plugin naming: for repo-owned workspace plugins, keep the canonical plugin id aligned across `openclaw.plugin.json:id`, the default workspace folder name, and package names anchored to the same id (`@openclaw/<id>` or approved suffix forms like `-provider`, `-plugin`, `-speech`, `-sandbox`, `-media-understanding`). Keep `openclaw.install.npmSpec` equal to the package name and `openclaw.channel.id` equal to the plugin id when present. Exceptions must be explicit and covered by the repo invariant test.
- Plugins: live in the bundled workspace plugin tree (workspace packages). Keep plugin-only deps in the extension `package.json`; do not add them to the root `package.json` unless core uses them.
- Plugins: install runs `npm install --omit=dev` in plugin dir; runtime deps must live in `dependencies`. Avoid `workspace:*` in `dependencies` (npm install breaks); put `openclaw` in `devDependencies` or `peerDependencies` instead (runtime resolves `openclaw/plugin-sdk` via jiti alias).
- Import boundaries: extension production code should treat `openclaw/plugin-sdk/*` plus local `api.ts` / `runtime-api.ts` barrels as the public surface. Do not import core `src/**`, `src/plugin-sdk-internal/**`, or another extension's `src/**` directly.
- Installers served from `https://openclaw.ai/*`: live in the sibling repo `../openclaw.ai` (`public/install.sh`, `public/install-cli.sh`, `public/install.ps1`).
- Messaging channels: always consider **all** built-in + extension channels when refactoring shared logic (routing, allowlists, pairing, command gating, onboarding, docs).
- Core channel docs: `docs/channels/`
- Core channel code: `src/channels`, `src/routing`, `src/web`
- Bundled plugin channels: `extensions/<channel>/` (for example Discord, Telegram, Slack, Matrix, Zalo, ZaloUser, Voice Call)
- When adding channels/plugins/apps/docs, update `.github/labeler.yml` and create matching GitHub labels (use existing channel/plugin label colors).
- Repo: `https://github.com/openclaw/openclaw`
- Replies: repo-root file refs only, e.g. `extensions/telegram/src/index.ts:80`. No absolute paths, no `~/`.
- CODEOWNERS: maintenance/refactors/tests are ok. For larger behavior, product, security, or ownership-sensitive changes, get a listed owner request/review first.
- First pass: run docs list (`bin/docs-list` or `pnpm docs:list`; ignore if unavailable), then read only relevant docs/guides.
- Missing deps: run `pnpm install`, rerun once, then report first actionable error.
- Use "plugin/plugins" in docs/UI/changelog. `extensions/` remains internal workspace layout.
- Add channel/plugin/app/doc surface: update `.github/labeler.yml` and matching GitHub labels.
- New `AGENTS.md`: add sibling `CLAUDE.md` symlink to it.
## Architecture Boundaries
## Repo Map
- Start here for the repo map:
- bundled workspace plugin tree = bundled plugins and the closest example surface for third-party plugins
- `src/plugin-sdk/*` = the public plugin contract that extensions are allowed to import
- `src/channels/*` = core channel implementation details behind the plugin/channel boundary
- `src/plugins/*` = plugin discovery, manifest validation, loader, registry, and contract enforcement
- `src/gateway/protocol/*` = typed Gateway control-plane and node wire protocol
- Progressive disclosure lives in local boundary guides:
- repo root `AGENTS.md`
- bundled-plugin-tree `extensions/AGENTS.md`
- `src/plugin-sdk/AGENTS.md`
- `src/channels/AGENTS.md`
- `src/plugins/AGENTS.md`
- `src/gateway/protocol/AGENTS.md`
- Workflow hygiene:
- Do not grep or existence-check every `docs/*.md`, `AGENTS.md`, or guide path mentioned in this file before starting work.
- Read only the guides and docs that are directly relevant to the files or boundary you are touching.
- Only do full broken-link or missing-guide sweeps when the task is explicitly about docs or repo-instruction maintenance.
- Plugin and extension boundary:
- Public docs: `docs/plugins/building-plugins.md`, `docs/plugins/architecture.md`, `docs/plugins/sdk-overview.md`, `docs/plugins/sdk-entrypoints.md`, `docs/plugins/sdk-runtime.md`, `docs/plugins/manifest.md`, `docs/plugins/sdk-channel-plugins.md`, `docs/plugins/sdk-provider-plugins.md`
- Definition files: `src/plugin-sdk/plugin-entry.ts`, `src/plugin-sdk/core.ts`, `src/plugin-sdk/provider-entry.ts`, `src/plugin-sdk/channel-contract.ts`, `scripts/lib/plugin-sdk-entrypoints.json`, `package.json`
- Invariant: core must stay extension-agnostic. Adding a bundled or third-party extension should not require unrelated core edits just to teach core that the extension exists.
- Rule: extensions must cross into core only through `openclaw/plugin-sdk/*`, manifest metadata, and documented runtime helpers. Do not import `src/**` from extension production code.
- Rule: core code and tests must not deep-import bundled plugin internals such as a plugin's `src/**` files or `onboard.js`. If core needs a bundled plugin helper, expose it through that plugin's `api.ts` and, when it is a real cross-package contract, through `src/plugin-sdk/<id>.ts`.
- Rule: do not add hardcoded bundled extension/provider/channel/capability id lists, maps, or named special cases in core when a manifest, capability, registry, or plugin-owned contract can express the same behavior.
- Rule: extension-owned compatibility behavior belongs to the owning extension. Core may orchestrate generic doctor/config flows, but extension-specific legacy repairs, detection rules, onboarding, auth detection, and provider defaults should live in plugin-owned contracts.
- Rule: for legacy config specifically, prefer doctor-owned repair paths over startup/load-time core migrations. Do not add new plugin-specific legacy migration logic to shared core/runtime surfaces when `openclaw doctor --fix` can own it.
- Rule: when a test is asserting extension-specific behavior, keep that coverage in the owning extension when feasible. Core tests should assert generic contracts and registry/capability behavior, not extension internals.
- Refactor trigger: if you encounter core code or tests that name a specific extension/provider/channel for extension-owned behavior, refactor toward a generic registry/capability/plugin-owned seam instead of adding another special case.
- Compatibility: new plugin seams are allowed, but they must be added as documented, backwards-compatible, versioned contracts. We have third-party plugins in the wild and do not break them casually.
- Channel boundary:
- Public docs: `docs/plugins/sdk-channel-plugins.md`, `docs/plugins/architecture.md`
- Definition files: `src/channels/plugins/types.plugin.ts`, `src/channels/plugins/types.core.ts`, `src/channels/plugins/types.adapters.ts`, `src/plugin-sdk/core.ts`, `src/plugin-sdk/channel-contract.ts`
- Rule: `src/channels/**` is core implementation. If plugin authors need a new seam, add it to the Plugin SDK instead of telling them to import channel internals.
- Provider/model boundary:
- Public docs: `docs/plugins/sdk-provider-plugins.md`, `docs/concepts/model-providers.md`, `docs/plugins/architecture.md`
- Definition files: `src/plugins/types.ts`, `src/plugin-sdk/provider-entry.ts`, `src/plugin-sdk/provider-auth.ts`, `src/plugin-sdk/provider-catalog-shared.ts`, `src/plugin-sdk/provider-model-shared.ts`
- Rule: core owns the generic inference loop; provider plugins own provider-specific behavior through registration and typed hooks. Do not solve provider needs by reaching into unrelated core internals.
- Rule: avoid ad hoc reads of `plugins.entries.<id>.config` from unrelated core code. If core needs plugin-owned auth/config behavior, add or use a generic seam (`resolveSyntheticAuth`, public SDK/helper facades, manifest metadata, plugin auto-enable hooks) and honor plugin disablement plus SecretRef semantics.
- Rule: vendor-owned tools and settings belong in the owning plugin. Do not add provider-specific tool config, secret collection, or runtime enablement to core `tools.*` surfaces unless the tool is intentionally core-owned.
- Gateway protocol boundary:
- Public docs: `docs/gateway/protocol.md`, `docs/gateway/bridge-protocol.md`, `docs/concepts/architecture.md`
- Definition files: `src/gateway/protocol/schema.ts`, `src/gateway/protocol/schema/*.ts`, `src/gateway/protocol/index.ts`
- Rule: protocol changes are contract changes. Prefer additive evolution; incompatible changes require explicit versioning, docs, and client/codegen follow-through.
- Config contract boundary:
- Canonical public config lives in exported config types, zod/schema surfaces, schema help/labels, generated config metadata, config baselines, and any user-facing gateway/config payloads. Keep those surfaces aligned.
- When a legacy config key is retired from the public contract, remove it from every public config surface above. Keep backward compatibility only through raw-config migration/doctor seams unless explicit product policy says otherwise.
- Do not reintroduce removed legacy aliases into public types/schema/help/baselines “for convenience”. If old configs still need to load, handle that in `legacy.migrations.*`, config ingest, or `openclaw doctor --fix`.
- `hooks.internal.entries` is the canonical public hook config model. `hooks.internal.handlers` is compatibility-only input and must not be re-exposed in public schema/help/baseline surfaces.
- Bundled plugin contract boundary:
- Public docs: `docs/plugins/architecture.md`, `docs/plugins/manifest.md`, `docs/plugins/sdk-overview.md`
- Definition files: `src/plugins/contracts/registry.ts`, `src/plugins/types.ts`, `src/plugins/public-surface-loader.ts`, `src/plugins/public-surface-runtime.ts`, `src/plugins/provider-public-artifacts.ts`, `src/plugins/web-provider-public-artifacts.ts`
- Rule: keep manifest metadata, runtime registration, public SDK exports, and contract tests aligned. Do not create a hidden path around the declared plugin interfaces.
- Extension test boundary:
- Keep extension-owned onboarding/config/provider coverage under the owning bundled plugin package when feasible.
- If core tests need bundled plugin behavior, consume it through public `src/plugin-sdk/<id>.ts` facades or the plugin's `api.ts`, not private extension modules.
- Shared helpers under `test/helpers/**` are part of that same boundary. Do not hardcode repo-relative `extensions/**` imports there, and do not keep plugin-local deep mocks in shared helpers just because multiple tests use them.
- When core tests or shared helpers need bundled plugin public surfaces, use `src/test-utils/bundled-plugin-public-surface.ts` for `api.ts`, `runtime-api.ts`, `contract-api.ts`, `test-api.ts`, plugin entrypoint `index.js`, and resolved module ids for dynamic import or mocking.
- If a core test is asserting extension-specific behavior instead of a generic contract, move it to the owning extension package.
- Scoped guides still matter:
- `extensions/AGENTS.md` expands extension/plugin boundary rules.
- `src/channels/AGENTS.md` expands core channel boundary and hot-path rules.
- `src/plugin-sdk/AGENTS.md` expands public SDK contract rules.
- `src/plugins/AGENTS.md` expands plugin loading, registry, and manifest rules.
- `src/gateway/protocol/AGENTS.md` expands typed Gateway protocol rules.
- `src/gateway/AGENTS.md` expands Gateway server hot-path and plugin artifact rules.
- `src/agents/AGENTS.md` expands agent test/import performance rules.
- `test/helpers/AGENTS.md` and `test/helpers/channels/AGENTS.md` expand shared test helper boundary rules.
- Plugin architecture direction:
- Keep a manifest-first control plane: discovery, validation, enablement, setup hints, and activation planning should stay metadata-driven by default.
- Keep runtime execution separate: actual provider/channel/tool execution should resolve through narrow targeted loaders, not broad registry materialization.
- Host loads plugins; plugins do not load host internals. Prefer a small versioned host/kernel seam plus documented SDK entrypoints over ambient reachability.
- Treat broad runtime registries and mutable global plugin state as transitional compatibility surfaces, not the target architecture.
- If a setup or config flow truly needs plugin runtime, make that explicit instead of silently importing runtime code on the cold path.
- Core TS: `src/`, `ui/`, `packages/`
- Bundled plugins: `extensions/`
- Plugin SDK/public contract: `src/plugin-sdk/*`
- Core channel internals: `src/channels/*`
- Plugin loader/registry/contracts: `src/plugins/*`
- Gateway protocol: `src/gateway/protocol/*`
- Docs: `docs/`
- Apps: `apps/`, `Swabble/`
- Installers served from `openclaw.ai`: sibling `../openclaw.ai`
## Scoped Workflow Guides
Scoped guides:
- `docs/AGENTS.md` owns Mintlify docs, docs links, and docs i18n rules.
- `ui/AGENTS.md` owns Control UI i18n and generated locale rules.
- `scripts/AGENTS.md` owns script-runner, local-check lock, and test/lint wrapper rules.
- `extensions/AGENTS.md`: bundled plugin rules
- `src/plugin-sdk/AGENTS.md`: public SDK rules
- `src/channels/AGENTS.md`: channel core rules
- `src/plugins/AGENTS.md`: plugin loader/registry rules
- `src/gateway/AGENTS.md`, `src/gateway/protocol/AGENTS.md`: gateway/protocol rules
- `src/agents/AGENTS.md`: agent import/test perf rules
- `test/helpers/AGENTS.md`, `test/helpers/channels/AGENTS.md`: shared test helpers
- `docs/AGENTS.md`, `ui/AGENTS.md`, `scripts/AGENTS.md`: docs/UI/scripts
## exe.dev VM ops (general)
## Architecture
- Access: stable path is `ssh exe.dev` then `ssh vm-name` (assume SSH key already set).
- SSH flaky: use exe.dev web terminal or Shelley (web agent); keep a tmux session for long ops.
- Update: `sudo npm i -g openclaw@latest` (global install needs root on `/usr/lib/node_modules`).
- Config: use `openclaw config set ...`; ensure `gateway.mode=local` is set.
- Discord: store raw token only (no `DISCORD_BOT_TOKEN=` prefix).
- Restart: stop old gateway and run:
`pkill -9 -f openclaw-gateway || true; nohup openclaw gateway run --bind loopback --port 18789 --force > /tmp/openclaw-gateway.log 2>&1 &`
- Verify: `openclaw channels status --probe`, `ss -ltnp | rg 18789`, `tail -n 120 /tmp/openclaw-gateway.log`.
- Core must stay extension-agnostic. No core special cases for bundled plugin/provider/channel ids when manifest/registry/capability contracts can express it.
- Extensions cross into core only via `openclaw/plugin-sdk/*`, manifest metadata, injected runtime helpers, and documented local barrels (`api.ts`, `runtime-api.ts`).
- Extension production code must not import core `src/**`, `src/plugin-sdk-internal/**`, another extension's `src/**`, or relative paths outside its package.
- Core code/tests must not deep-import plugin internals (`extensions/*/src/**`, `onboard.js`). Use plugin `api.ts` / public SDK facade / generic contract.
- Extension-owned behavior stays in the extension: legacy repair, detection, onboarding, auth/provider defaults, provider tools/settings.
- Legacy config repair: prefer doctor/fix paths over startup/load-time core migrations.
- If a core test asserts extension-specific behavior, move it to the owning extension or a generic contract test.
- New seams: backwards-compatible, documented, versioned. Third-party plugins exist.
- Channels: `src/channels/**` is implementation. Plugin authors get SDK seams, not channel internals.
- Providers: core owns generic inference loop; provider plugins own provider-specific auth/catalog/runtime hooks.
- Gateway protocol changes are contract changes: additive first; incompatible needs versioning/docs/client follow-through.
- Config contract: keep exported types, schema/help, generated metadata, baselines, docs aligned. Retired public keys stay retired; compatibility belongs in raw migration/doctor paths.
- Plugin architecture direction: manifest-first control plane; targeted runtime loaders; no hidden paths around declared contracts; broad mutable registries are transitional.
- Prompt-cache rule: deterministic ordering for maps/sets/registries/plugin lists/files/network results before model/tool payloads. Preserve old transcript bytes when possible.
## Build, Test, and Development Commands
## Commands
- Runtime baseline: Node **22+** (keep Node + Bun paths working).
- Install deps: `pnpm install`
- If deps are missing (for example `node_modules` missing, `vitest not found`, or `command not found`), run the repos package-manager install command (prefer lockfile/README-defined PM), then rerun the exact requested command once. Apply this to test/build/lint/typecheck/dev commands; if retry still fails, report the command and first actionable error.
- Pre-commit hooks are installed by the package `prepare` script (`git config core.hooksPath git-hooks`). The hook formats/lints staged source files and runs `pnpm check` unless the staged change is docs-only or `FAST_COMMIT=1` is set.
- `FAST_COMMIT=1` skips the repo-wide `pnpm check` inside the pre-commit hook only. The hook still runs targeted formatting/linting for staged files and restages formatter changes. Use it when you intentionally want a faster commit path and are running equivalent targeted verification manually. It does not change CI and does not change what `pnpm check` itself does.
- Also supported: `bun install` (keep `pnpm-lock.yaml` + Bun patching in sync when touching deps/patches).
- Prefer Bun for TypeScript execution (scripts, dev, tests): `bun <file.ts>` / `bunx <tool>`.
- Run CLI in dev: `pnpm openclaw ...` (bun) or `pnpm dev`.
- Node remains supported for running built output (`dist/*`) and production installs.
- Mac packaging (dev): `scripts/package-mac-app.sh` defaults to current arch.
- Type-check/build: `pnpm build`
- TypeScript checks: `pnpm tsgo`
- Lint/format: `pnpm check`
- Local agent/dev shells default to host-aware `OPENCLAW_LOCAL_CHECK=1` behavior for `pnpm tsgo` and `pnpm lint`; set `OPENCLAW_LOCAL_CHECK_MODE=throttled` to force the lower-memory profile, `OPENCLAW_LOCAL_CHECK_MODE=full` to keep lock-only behavior, or `OPENCLAW_LOCAL_CHECK=0` in CI/shared runs.
- Format check: `pnpm format:check` (oxfmt --check)
- Format fix: `pnpm format` or `pnpm format:fix` (oxfmt --write)
- Terminology:
- "gate" means a verification command or command set that must be green for the decision you are making.
- A local dev gate is the fast default loop, usually `pnpm check` plus any scoped test you actually need.
- A landing gate is the broader bar before pushing `main`, usually `pnpm check`, `pnpm test`, and `pnpm build` when the touched surface can affect build output, packaging, lazy-loading/module boundaries, or published surfaces.
- A CI gate is whatever the relevant workflow enforces for that lane (for example `check`, `check-additional`, `build-smoke`, or release validation).
- Local dev gate: prefer `pnpm check` for the normal edit loop. It keeps the repo-architecture policy guards out of the default local loop.
- CI architecture gate: `check-additional` enforces architecture and boundary policy guards that are intentionally kept out of the default local loop.
- Formatting gate: the pre-commit hook runs targeted formatting on staged source files before `pnpm check`. If you want a repo-wide formatting-only preflight locally, run `pnpm format:check` explicitly.
- If you need a fast commit loop, `FAST_COMMIT=1 git commit ...` skips the hooks repo-wide `pnpm check`; targeted formatting/linting still runs, so use that only when you are deliberately covering the touched surface some other way.
- Tests: `pnpm test` (vitest); coverage: `pnpm test:coverage`
- Generated baseline drift detection uses SHA-256 hash files under `docs/.generated/` (`.sha256` files tracked in git; full JSON baselines are gitignored, generated locally for inspection).
- Config schema drift uses `pnpm config:docs:gen` / `pnpm config:docs:check`.
- Plugin SDK API drift uses `pnpm plugin-sdk:api:gen` / `pnpm plugin-sdk:api:check`.
- If you change config schema/help or the public Plugin SDK surface, run the matching gen command and commit the updated `.sha256` hash file. Keep the two drift-check flows adjacent in scripts/workflows/docs guidance rather than inventing a third pattern.
- When `pnpm tsgo` fails, triage by coherent surface instead of by raw error count: rerun the gate, group failures by package/module/type contract, open the source-of-truth type or export file first, fix the root mismatch, then rerun `pnpm tsgo` before widening into downstream consumers. Check `origin/main` before doing broad cleanup because some apparent type debt is already fixed upstream.
- For narrowly scoped changes, prefer narrowly scoped tests that directly validate the touched behavior. If no meaningful scoped test exists, say so explicitly and use the next most direct validation available.
- Verification modes for work on `main`:
- Default mode: `main` is relatively stable. Count pre-commit hook coverage when it already verified the current tree, avoid rerunning the exact same checks just for ceremony, and prefer keeping CI/main green before landing.
- Fast-commit mode: `main` is moving fast and you intentionally optimize for shorter commit loops. Prefer explicit local verification close to the final landing point, and it is acceptable to use `--no-verify` for intermediate or catch-up commits after equivalent checks have already run locally.
- Preferred landing bar for pushes to `main`: in Default mode, favor `pnpm check` and `pnpm test` near the final rebase/push point when feasible. In fast-commit mode, verify the touched surface locally near landing without insisting every intermediate commit replay the full hook.
- Scoped tests prove the change itself. `pnpm test` remains the default `main` landing bar; scoped tests do not replace full-suite gates by default.
- Hard gate: if the change can affect build output, packaging, lazy-loading/module boundaries, or published surfaces, `pnpm build` MUST be run and MUST pass before pushing `main`.
- Default rule: do not land changes with failing format, lint, type, build, or required test checks when those failures are caused by the change or plausibly related to the touched surface. Fast-commit mode changes how verification is sequenced; it does not lower the requirement to validate and clean up the touched surface before final landing.
- For narrowly scoped changes, if unrelated failures already exist on latest `origin/main`, state that clearly, report the scoped tests you ran, and ask before broadening scope into unrelated fixes or landing despite those failures.
- Do not use scoped tests as permission to ignore plausibly related failures.
- Runtime: Node 22+. Keep Node and Bun paths working.
- Install: `pnpm install` (Bun supported; keep lockfiles/patches aligned if touched).
- Dev CLI: `pnpm openclaw ...` or `pnpm dev`.
- Build: `pnpm build`
- Smart local gate: `pnpm check:changed` (scoped typecheck/lint/guards + relevant tests)
- Explain smart gate: `pnpm changed:lanes --json`
- Pre-commit view: `pnpm check:changed --staged`
- Normal full prod sweep: `pnpm check` (prod typecheck/lint/guards, no tests)
- Full tests: `pnpm test`
- Changed tests only: `pnpm test:changed`
- Extension tests: `pnpm test:extensions` or `pnpm test extensions` = all extension shards; `pnpm test extensions/<id>` = one extension lane. Heavy channels/OpenAI have dedicated shards.
- Shard timing artifact: `.artifacts/vitest-shard-timings.json`; auto-used for balanced shard ordering. Disable with `OPENCLAW_TEST_PROJECTS_TIMINGS=0`.
- Targeted tests: `pnpm test <path-or-filter> [vitest args...]`; do not call raw `vitest`.
- Coverage: `pnpm test:coverage`
- Format check/fix: `pnpm format:check` / `pnpm format`
- Typecheck:
- `pnpm tsgo`: fastest core prod graph
- `pnpm tsgo:prod`: core + extensions prod graphs; used by `pnpm check`
- `pnpm check:test-types` / `pnpm tsgo:test`: all test graphs
- `pnpm tsgo:all`: all prod + test project refs
- Debug slices exist; do not present as normal user flow.
- Profile: `pnpm tsgo:profile [core-test|extensions-test|--all]`
- Type policy: use `tsgo`; do not add `tsc --noEmit`, `typecheck`, or `check:types` lanes. `tsc` only for declaration/package-boundary emit gaps.
- Lint:
- `pnpm lint`: core/extensions/scripts shards
- `pnpm lint:core`, `pnpm lint:extensions`, `pnpm lint:scripts`
- `pnpm lint:apps`: Swift/app surface, separate from TS lint
- `pnpm lint:all`: legacy comparison lane
- Local heavy-check behavior: `OPENCLAW_LOCAL_CHECK=1` default; `OPENCLAW_LOCAL_CHECK_MODE=throttled|full`; `OPENCLAW_LOCAL_CHECK=0` for CI/shared runs.
## Prompt Cache Stability
## Gates
- Treat prompt-cache stability as correctness/perf-critical, not cosmetic.
- Any code that assembles model or tool payloads from maps, sets, registries, plugin lists, MCP catalogs, filesystem reads, or network results must make ordering deterministic before building the request.
- Do not rewrite older transcript/history bytes on every turn unless you intentionally want to invalidate the cached prefix. Legacy cleanup, pruning, normalization, and migration logic should preserve recent prompt bytes when possible.
- If truncation or compaction is required, prefer mutating newest or tail content first so the cached prefix stays byte-identical for as long as possible.
- For cache-sensitive changes, require a regression test that proves turn-to-turn prefix stability or deterministic request assembly; helper-local tests alone are not enough.
- Pre-commit hook: staged format/lint, then `pnpm check:changed --staged`; docs/markdown-only skips changed-scope check; `FAST_COMMIT=1` skips changed-scope check only.
- Changed lanes:
- core prod => core prod typecheck + core tests
- core tests => core test typecheck/tests only
- extension prod => extension prod typecheck + extension tests
- extension tests => extension test typecheck/tests only
- public SDK/plugin contract => extension prod/test validation too
- unknown root/config => all lanes
- Local loop: prefer `pnpm check:changed`; use `pnpm test:changed` for tests only; use `pnpm check` for full prod TS/lint sweep without tests.
- Landing on `main`: verify touched surface near landing; default bar is `pnpm check` + `pnpm test` when feasible.
- Hard build gate: run/pass `pnpm build` before push if build output, packaging, lazy/module boundaries, or published surfaces can change.
- Do not land related failing format/lint/type/build/tests. If failures are unrelated on latest `origin/main`, say so and give scoped proof.
- CI architecture gate: `check-additional`; local equivalent `pnpm check:architecture`.
- Config docs drift: `pnpm config:docs:gen/check`
- Plugin SDK API drift: `pnpm plugin-sdk:api:gen/check`
- Generated docs baselines: tracked `docs/.generated/*.sha256`; full JSON ignored.
## Coding Style & Naming Conventions
## Code Style
- Language: TypeScript (ESM). Prefer strict typing; avoid `any`.
- Formatting/linting via Oxlint and Oxfmt.
- Never add `@ts-nocheck` and do not add inline lint suppressions by default. Fix root causes first; only keep a suppression when the code is intentionally correct, the rule cannot express that safely, and the comment explains why.
- Do not disable `no-explicit-any`; prefer real types, `unknown`, or a narrow adapter/helper instead. Update Oxlint/Oxfmt config only when required.
- Prefer `zod` or existing schema helpers at external boundaries such as config, webhook payloads, CLI/JSON output, persisted JSON, and third-party API responses.
- Prefer discriminated unions when parameter shape changes runtime behavior.
- Prefer `Result<T, E>`-style outcomes and closed error-code unions for recoverable runtime decisions.
- Keep human-readable strings for logs, CLI output, and UI; do not use freeform strings as the source of truth for internal branching.
- Avoid `?? 0`, empty-string, empty-object, or magic-string sentinels when they can change runtime meaning silently.
- If introducing a new optional field or nullable semantic in core logic, prefer an explicit union or dedicated type when the value changes behavior.
- New runtime control-flow code should not branch on `error: string` or `reason: string` when a closed code union would be reasonable.
- Dynamic import guardrail: do not mix `await import("x")` and static `import ... from "x"` for the same module in production code paths. If you need lazy loading, create a dedicated `*.runtime.ts` boundary (that re-exports from `x`) and dynamically import that boundary from lazy callers only.
- Dynamic import verification: after refactors that touch lazy-loading/module boundaries, run `pnpm build` and check for `[INEFFECTIVE_DYNAMIC_IMPORT]` warnings before submitting.
- Circular dependencies: keep both `pnpm check:import-cycles` and `pnpm check:madge-import-cycles` green; do not reintroduce runtime import cycles or madge-detected import loops.
- Extension SDK self-import guardrail: inside an extension package, do not import that same extension via `openclaw/plugin-sdk/<extension>` from production files. Route internal imports through a local barrel such as `./api.ts` or `./runtime-api.ts`, and keep the `plugin-sdk/<extension>` path as the external contract only.
- Extension package boundary guardrail: inside a bundled plugin package, do not use relative imports/exports that resolve outside that same package root. If shared code belongs in the plugin SDK, import `openclaw/plugin-sdk/<subpath>` instead of reaching into `src/plugin-sdk/**` or other repo paths via `../`.
- Extension API surface rule: `openclaw/plugin-sdk/<subpath>` is the only public cross-package contract for extension-facing SDK code. If an extension needs a new seam, add a public subpath first; do not reach into `src/plugin-sdk/**` by relative path.
- Never share class behavior via prototype mutation (`applyPrototypeMixins`, `Object.defineProperty` on `.prototype`, or exporting `Class.prototype` for merges). Use explicit inheritance/composition (`A extends B extends C`) or helper composition so TypeScript can typecheck.
- If this pattern is needed, stop and get explicit approval before shipping; default behavior is to split/refactor into an explicit class hierarchy and keep members strongly typed.
- In tests, prefer per-instance stubs over prototype mutation (`SomeClass.prototype.method = ...`) unless a test explicitly documents why prototype-level patching is required.
- Add brief code comments for tricky or non-obvious logic.
- Keep files concise; extract helpers instead of “V2” copies. Use existing patterns for CLI options and dependency injection via `createDefaultDeps`.
- Aim to keep files under ~700 LOC; guideline only (not a hard guardrail). Split/refactor when it improves clarity or testability.
- Naming: use **OpenClaw** for product/app/docs headings; use `openclaw` for CLI command, package/binary, paths, and config keys.
- Written English: use American spelling and grammar in code, comments, docs, and UI strings (e.g. "color" not "colour", "behavior" not "behaviour", "analyze" not "analyse").
- TypeScript ESM. Strict types. Avoid `any`; prefer real types/`unknown`/narrow adapters.
- No `@ts-nocheck`. No lint suppressions unless intentional and explained.
- External boundaries: prefer `zod` or existing schema helpers.
- Runtime branching: prefer discriminated unions / closed codes over freeform strings.
- Avoid magic sentinels like `?? 0`, empty object/string when semantics change.
- Dynamic import: do not mix static and dynamic import for same module in prod path. Use dedicated `*.runtime.ts` lazy boundary. After lazy-boundary edits, run `pnpm build` and check `[INEFFECTIVE_DYNAMIC_IMPORT]`.
- Cycles: keep `pnpm check:import-cycles` and architecture/madge cycle checks green.
- Classes: no prototype mixins/mutations. Use explicit inheritance/composition. Tests prefer per-instance stubs.
- Comments: brief only for non-obvious logic.
- File size: split around ~700 LOC when it improves clarity/testability.
- Product naming: **OpenClaw** product/docs; `openclaw` CLI/package/path/config.
- Written English: American spelling.
## Release / Advisory Workflows
## Tests
- Use `$openclaw-release-maintainer` at `.agents/skills/openclaw-release-maintainer/SKILL.md` for release naming, version coordination, release auth, and changelog-backed release-note workflows.
- Use `$openclaw-ghsa-maintainer` at `.agents/skills/openclaw-ghsa-maintainer/SKILL.md` for GHSA advisory inspection, patch/publish flow, private-fork checks, and GHSA API validation.
- Release and publish remain explicit-approval actions even when using the skill.
- Vitest. Tests colocated `*.test.ts`; e2e `*.e2e.test.ts`.
- Example models in tests: `sonnet-4.6`, `gpt-5.4`.
- Clean up timers/env/globals/mocks/sockets/temp dirs/module state; `--isolate=false` must stay safe.
- Hot tests: avoid per-test `vi.resetModules()` + fresh heavy imports; prefer static or `beforeAll` imports and reset state directly.
- Measure first: `pnpm test:perf:imports <file>` for import drag; `pnpm test:perf:hotspots --limit N` for suite targets.
- Keep tests at seam depth: unit-test pure helpers/contracts; one integration smoke per boundary, not per branch.
- Mock expensive runtime seams directly: scanners, manifests, package registries, filesystem crawls, provider SDKs, network/process launch.
- Prefer injected deps over module mocks; if mocking modules, mock narrow local `*.runtime.ts` seams, not broad barrels.
- Share fixtures/builders; do not recreate temp dirs, package manifests, or plugin workspaces in every case unless state isolation needs it.
- Delete duplicate assertions when another test owns the boundary; assert only the behavior that can regress here.
- Avoid broad `importOriginal()` / broad `openclaw/plugin-sdk/*` partial mocks in hot tests. Add narrow local `*.runtime.ts` seam and mock it.
- Use existing deps/callback/runtime injection seams before module mocks.
- Import-dominated test time is a boundary smell; shrink import surface before adding cases.
- Replacing slow integration coverage: extract production composition into a named helper and test that helper.
- Do not modify baseline/inventory/ignore/snapshot/expected-failure files to silence checks without explicit approval.
- Do not set test workers above 16. For memory pressure: `OPENCLAW_VITEST_MAX_WORKERS=1 pnpm test`.
- Live: `OPENCLAW_LIVE_TEST=1 pnpm test:live`; full logs `OPENCLAW_LIVE_TEST_QUIET=0`.
- Full testing guide: `docs/help/testing.md`.
## Testing Guidelines
## Docs / Changelog
- Framework: Vitest with V8 coverage thresholds (70% lines/branches/functions/statements).
- Naming: match source names with `*.test.ts`; e2e in `*.e2e.test.ts`.
- When tests need example Anthropic/OpenAI model constants, prefer `sonnet-4.6` and `gpt-5.4`; update older Anthropic/GPT examples when you touch those tests.
- Run `pnpm test` (or `pnpm test:coverage`) before pushing when you touch logic.
- Write tests to clean up timers, env, globals, mocks, sockets, temp dirs, and module state so `--isolate=false` stays green.
- Test performance guardrail: do not put `vi.resetModules()` plus `await import(...)` in `beforeEach`/per-test loops for heavy modules unless module state truly requires it. Prefer static imports or one-time `beforeAll` imports, then reset mocks/runtime state directly.
- Test performance guardrail: if a test file uses stable `vi.mock(...)` hoists or other static module mocks, do not pair them with `vi.resetModules()` and a fresh `await import(...)` in every `beforeEach`. Import the heavy module once in `beforeAll`, then reset/prime mocks in `beforeEach` so Browser/Matrix-style hotspot tests do not pay the module graph cost per case.
- Test performance guardrail: inside an extension package, prefer a thin local seam (`./api.ts`, `./runtime-api.ts`, or a narrower local `*.runtime-api.ts`) over direct `openclaw/plugin-sdk/*` imports for internal production code. Keep local seams curated and lightweight; only reach for direct `plugin-sdk/*` imports when you are crossing a real package boundary or when no suitable local seam exists yet.
- Test performance guardrail: keep expensive runtime fallback work such as snapshotting, migration, installs, or bootstrap behind dedicated `*.runtime.ts` boundaries so tests can mock the seam instead of accidentally invoking real work.
- Test performance guardrail: for import-only/runtime-wrapper tests, keep the wrapper lazy. Do not eagerly load heavy verification/bootstrap/runtime modules at module top level if the exported function can import them on demand.
- Test performance guardrail: prefer explicit mock factories over `importOriginal()` for broad modules. Reserve `importOriginal()` for narrow modules where partial-real behavior is genuinely needed.
- Test performance guardrail: do not partial-mock broad `openclaw/plugin-sdk/*` barrels in hot tests. Add a plugin-local `*.runtime.ts` seam and mock that seam instead.
- Test performance guardrail: when production code already accepts `deps`, callbacks, or runtime injection, use that seam in tests before adding module-level mocks.
- Test performance guardrail: prefer narrow public SDK subpaths such as `models-provider-runtime`, `skill-commands-runtime`, and `reply-dispatch-runtime` over older broad helper barrels when both expose the needed helper.
- Test performance guardrail: treat import-dominated test time as a boundary bug. Refactor the import surface before adding more cases to the slow file.
- Test performance guardrail: when replacing a slow integration test with helper-level coverage, extract the exact production composition into a named helper and test that helper. Do not trade coverage shape for speed without preserving the behavior proof somewhere cheaper.
- Test performance guardrail: for plugin-owned static descriptors used by core tests or cold paths, prefer lightweight public artifacts with full-runtime fallback over loading broad bundled plugin barrels.
- Agents MUST NOT modify baseline, inventory, ignore, snapshot, or expected-failure files to silence failing checks without explicit approval in this chat.
- For targeted/local debugging, use the native root-project entrypoint: `pnpm test <path-or-filter> [vitest args...]` (for example `pnpm test src/commands/onboard-search.test.ts -t "shows registered plugin providers"`); do not default to raw `pnpm vitest run ...` because it bypasses the repo's default config/profile/pool routing.
- Do not set test workers above 16; tried already.
- Vitest now defaults to native root-project `threads`, with hard `forks` exceptions for `gateway`, `agents`, and `commands`. Keep new pool changes explicit and justified; use `OPENCLAW_VITEST_POOL=forks` for full local fork debugging.
- If local Vitest runs cause memory pressure, the default worker budget now derives from host capabilities (CPU, memory band, current load). For a conservative explicit override during land/gate runs, use `OPENCLAW_VITEST_MAX_WORKERS=1 pnpm test`.
- Live tests (real keys): `OPENCLAW_LIVE_TEST=1 pnpm test:live` (OpenClaw-only) or `LIVE=1 pnpm test:live` (includes provider live tests). Docker: `pnpm test:docker:live-models`, `pnpm test:docker:live-gateway`. Onboarding Docker E2E: `pnpm test:docker:onboard`.
- `pnpm test:live` defaults quiet now. Keep `[live]` progress; suppress profile/gateway chatter. Full logs: `OPENCLAW_LIVE_TEST_QUIET=0 pnpm test:live`.
- Full kit + whats covered: `docs/help/testing.md`.
- Changelog: user-facing changes only; no internal/meta notes (version alignment, appcast reminders, release process).
- Changelog placement: in the active version block, append new entries to the end of the target section (`### Changes` or `### Fixes`); do not insert new entries at the top of a section.
- Changelog attribution: use at most one contributor mention per line; prefer `Thanks @author` and do not also add `by @author` on the same entry.
- Pure test additions/fixes generally do **not** need a changelog entry unless they alter user-facing behavior or the user asks for one.
- Mobile: before using a simulator, check for connected real devices (iOS + Android) and prefer them when available.
- Update docs when behavior/API changes. Use docs list/read_when hints.
- Docs links: see `docs/AGENTS.md`.
- Changelog: user-facing only. Pure test/internal changes usually no entry.
- Changelog placement: append to active version `### Changes`/`### Fixes`; at most one contributor mention, prefer `Thanks @user`.
## Commit & Pull Request Guidelines
## Git
- Use `$openclaw-pr-maintainer` at `.agents/skills/openclaw-pr-maintainer/SKILL.md` for maintainer PR triage, review, close, search, and landing workflows.
- This includes auto-close labels, bug-fix evidence gates, GitHub comment/search footguns, and maintainer PR decision flow.
- For the repo's end-to-end maintainer PR workflow, use `$openclaw-pr-maintainer` at `.agents/skills/openclaw-pr-maintainer/SKILL.md`.
- Use `scripts/committer "<msg>" <file...>`; stage only intended files.
- Commits: conventional-ish, concise/action-oriented. Group related changes.
- No manual stash/autostash unless explicitly requested. No branch/worktree changes unless requested.
- No merge commits on `main`; rebase on latest `origin/main` before push.
- User says "commit": commit your changes only. "commit all": commit everything in grouped chunks. "push": may `git pull --rebase` first.
- Do not delete/rename unexpected files; ask if it blocks. Otherwise ignore unrelated WIP.
- If bulk PR close/reopen affects >5 PRs, ask with exact count/scope.
- PR/issue workflows: use `$openclaw-pr-maintainer`.
- `/landpr`: use `~/.codex/prompts/landpr.md`.
- `/landpr` lives in the global Codex prompts (`~/.codex/prompts/landpr.md`); when landing or merging any PR, always follow that `/landpr` process.
- Create commits with `scripts/committer "<msg>" <file...>`; avoid manual `git add`/`git commit` so staging stays scoped.
- Follow concise, action-oriented commit messages (e.g., `CLI: add verbose flag to send`).
- Group related changes; avoid bundling unrelated refactors.
- PR submission template (canonical): `.github/pull_request_template.md`
- Issue submission templates (canonical): `.github/ISSUE_TEMPLATE/`
## Security / Release
## Git Notes
- Never commit real phone numbers, videos, credentials, live config.
- Secrets: channel/provider credentials under `~/.openclaw/credentials/`; model auth profiles under `~/.openclaw/agents/<agentId>/agent/auth-profiles.json`.
- Env keys: check `~/.profile`.
- Dependency patches/overrides/vendor changes require explicit approval. `pnpm.patchedDependencies` must use exact versions.
- Carbon pins owner-only: do not change `@buape/carbon` versions unless Shadow (`@thewilloftheshadow`, verified by `gh`) asks.
- Releases/publish/version bumps require explicit approval.
- Release docs: `docs/reference/RELEASING.md`; use `$openclaw-release-maintainer`.
- GHSA/advisories: use `$openclaw-ghsa-maintainer`.
- Beta tag/version must match, e.g. `vYYYY.M.D-beta.N` => npm `YYYY.M.D-beta.N --tag beta`.
- If `git branch -d/-D <branch>` is policy-blocked, delete the local ref directly: `git update-ref -d refs/heads/<branch>`.
- Agents MUST NOT create or push merge commits on `main`. If `main` has advanced, rebase local commits onto the latest `origin/main` before pushing.
- Bulk PR close/reopen safety: if a close action would affect more than 5 PRs, first ask for explicit user confirmation with the exact PR count and target scope/query.
## Apps / Platform
## Security & Configuration Tips
- Before simulator/emulator testing, check connected real iOS/Android devices first.
- "restart iOS/Android apps" = rebuild/reinstall/relaunch, not kill/launch.
- SwiftUI: prefer Observation (`@Observable`, `@Bindable`) over new `ObservableObject`.
- mac gateway: use app or `openclaw gateway restart/status --deep`; avoid ad-hoc tmux gateway sessions. Rebuild mac app locally, not over SSH.
- mac logs: `./scripts/clawlog.sh`.
- Version bump touches: `package.json`, `apps/android/app/build.gradle.kts`, `apps/ios/version.json` then `pnpm ios:version:sync`, `apps/macos/.../Info.plist`, `docs/install/updating.md`. Appcast only for Sparkle release.
- iOS Team ID: `security find-identity -p codesigning -v`; fallback `defaults read com.apple.dt.Xcode IDEProvisioningTeamIdentifiers`.
- Mobile LAN pairing: plaintext `ws://` is loopback-only by default. Trusted private-network `ws://` needs `OPENCLAW_ALLOW_INSECURE_PRIVATE_WS=1`; Tailscale/public use `wss://` or a tunnel.
- A2UI hash `src/canvas-host/a2ui/.bundle.hash`: generated; ignore unless running `pnpm canvas:a2ui:bundle`; commit separately.
- Channel/provider state lives under `~/.openclaw/credentials/`; rerun `openclaw channels login` if logged out. Model auth profiles live under `~/.openclaw/agents/<agentId>/agent/auth-profiles.json`; legacy OAuth import still reads `~/.openclaw/credentials/oauth.json`.
- Pi sessions live under `~/.openclaw/agents/<agentId>/sessions/` by default; `session.store` can override the session store path.
- Environment variables: see `~/.profile`.
- Never commit or publish real phone numbers, videos, or live configuration values. Use obviously fake placeholders in docs, tests, and examples.
- Release flow: use the private [maintainer release docs](https://github.com/openclaw/maintainers/blob/main/release/README.md) for the actual runbook, `docs/reference/RELEASING.md` for the public release policy, and `$openclaw-release-maintainer` for the maintainership workflow.
## External Ops
## Local Runtime / Platform Notes
- Remote install docs: `docs/install/exe-dev.md`, `docs/install/fly.md`, `docs/install/hetzner.md`.
- Parallels smoke: `$openclaw-parallels-smoke`; Discord roundtrip: `parallels-discord-roundtrip`.
- Vocabulary: "makeup" = "mac app".
- Rebrand/migration issues or legacy config/service warnings: run `openclaw doctor` (see `docs/gateway/doctor.md`).
- Use `$openclaw-parallels-smoke` at `.agents/skills/openclaw-parallels-smoke/SKILL.md` for Parallels smoke, rerun, upgrade, debug, and result-interpretation workflows across macOS, Windows, and Linux guests.
- For the macOS Discord roundtrip deep dive, use the narrower `.agents/skills/parallels-discord-roundtrip/SKILL.md` companion skill.
- Never edit `node_modules` (global/Homebrew/npm/git installs too). Updates overwrite. Skill notes go in `tools.md` or `AGENTS.md`.
- If you need local-only `.agents` ignores, use `.git/info/exclude` instead of repo `.gitignore`.
- When adding a new `AGENTS.md` anywhere in the repo, also add a `CLAUDE.md` symlink pointing to it (example: `ln -s AGENTS.md CLAUDE.md`).
- Signal: "update fly" => `fly ssh console -a flawd-bot -C "bash -lc 'cd /data/clawd/openclaw && git pull --rebase origin main'"` then `fly machines restart e825232f34d058 -a flawd-bot`.
- CLI progress: use `src/cli/progress.ts` (`osc-progress` + `@clack/prompts` spinner); dont hand-roll spinners/bars.
- Status output: keep tables + ANSI-safe wrapping (`src/terminal/table.ts`); `status --all` = read-only/pasteable, `status --deep` = probes.
- Gateway may run as an app-managed launchd job. Restart the gateway via the app or `openclaw gateway restart`; inspect with `openclaw gateway status --deep` or, for the default profile, `launchctl print gui/$UID/ai.openclaw.gateway`. Use `scripts/restart-mac.sh` when you need to rebuild/relaunch the local macOS app itself. The app LaunchAgent uses `ai.openclaw.mac`. **When debugging on macOS, start/stop the gateway via the app or gateway CLI, not ad-hoc tmux sessions; kill any temporary tunnels before handoff.**
- macOS logs: use `./scripts/clawlog.sh` to query unified logs for the OpenClaw subsystem; it supports follow/tail/category filters and expects passwordless sudo for `/usr/bin/log`.
- If shared guardrails are available locally, review them; otherwise follow this repo's guidance.
- SwiftUI state management (iOS/macOS): prefer the `Observation` framework (`@Observable`, `@Bindable`) over `ObservableObject`/`@StateObject`; dont introduce new `ObservableObject` unless required for compatibility, and migrate existing usages when touching related code.
- Connection providers: when adding a new connection, update every UI surface and docs (macOS app, web UI, mobile if applicable, onboarding/overview docs) and add matching status + configuration forms so provider lists and settings stay in sync.
- Version locations: `package.json` (CLI), `apps/android/app/build.gradle.kts` (versionName/versionCode), `apps/ios/version.json` (source for generated iOS config and Fastlane metadata), `apps/macos/Sources/OpenClaw/Resources/Info.plist` (CFBundleShortVersionString/CFBundleVersion), and `docs/install/updating.md` (pinned npm version).
- "Bump version everywhere" means all version locations above, then run `pnpm ios:version:sync` for iOS generated outputs. Only touch appcast metadata when cutting a new macOS Sparkle release.
- **Restart apps:** “restart iOS/Android apps” means rebuild (recompile/install) and relaunch, not just kill/launch.
- **Device checks:** before testing, verify connected real devices (iOS/Android) before reaching for simulators/emulators.
- Mobile pairing: `ws://` (cleartext) is allowed for private LAN addresses (RFC 1918, link-local, mDNS `.local`) and loopback. Private LAN hosts typically lack PKI-backed identity, so requiring TLS there adds complexity without meaningful security gain. `wss://` is required for Tailscale and public endpoints.
- Security report scope: reports that treat cleartext `ws://` mobile pairing over private LAN as a vulnerability are out of scope unless they demonstrate a trust-boundary bypass beyond passive network observation on the same LAN.
- iOS Team ID lookup: `security find-identity -p codesigning -v` → use Apple Development (…) TEAMID. Fallback: `defaults read com.apple.dt.Xcode IDEProvisioningTeamIdentifiers`.
- A2UI bundle hash: `src/canvas-host/a2ui/.bundle.hash` is auto-generated; ignore unexpected changes, and only regenerate via `pnpm canvas:a2ui:bundle` (or `scripts/bundle-a2ui.sh`) when needed. Commit the hash as a separate commit.
- Release signing/notary credentials are managed outside the repo; maintainers keep that setup in the private [maintainer release docs](https://github.com/openclaw/maintainers/tree/main/release).
- Lobster palette: use the shared CLI palette in `src/terminal/palette.ts` (no hardcoded colors); apply palette to onboarding/config prompts and other TTY UI output as needed.
- When asked to open a “session” file, open the Pi session logs under `~/.openclaw/agents/<agentId>/sessions/*.jsonl` (use the `agent=<id>` value in the Runtime line of the system prompt; newest unless a specific ID is given), not the default `sessions.json`. If logs are needed from another machine, SSH via Tailscale and read the same path there.
- Do not rebuild the macOS app over SSH; rebuilds must be run directly on the Mac.
- Voice wake forwarding tips:
- Command template should stay `openclaw-mac agent --message "${text}" --thinking low`; `VoiceWakeForwarder` already shell-escapes `${text}`. Dont add extra quotes.
- launchd PATH is minimal; ensure the apps launch agent PATH includes standard system paths plus your pnpm bin (typically `$HOME/Library/pnpm`) so `pnpm`/`openclaw` binaries resolve when invoked via `openclaw-mac`.
## Misc Footguns
## Collaboration / Safety Notes
- When working on a GitHub Issue or PR, print the full URL at the end of the task.
- When answering questions, respond with high-confidence answers only: verify in code; do not guess.
- Carbon version edits are owner-only: do not change `@buape/carbon` version pins unless you are Shadow (@thewilloftheshadow) as verified by gh.
- Any dependency with `pnpm.patchedDependencies` must use an exact version (no `^`/`~`).
- Patching dependencies (pnpm patches, overrides, or vendored changes) requires explicit approval; do not do this by default.
- **Multi-agent safety:** do **not** create/apply/drop `git stash` entries unless explicitly requested (this includes `git pull --rebase --autostash`). Assume other agents may be working; keep unrelated WIP untouched and avoid cross-cutting state changes.
- **Multi-agent safety:** when the user says "push", you may `git pull --rebase` to integrate latest changes (never discard other agents' work). When the user says "commit", scope to your changes only. When the user says "commit all", commit everything in grouped chunks.
- **Multi-agent safety:** prefer grouped `commit` / `pull --rebase` / `push` cycles for related work instead of many tiny syncs.
- **Multi-agent safety:** do **not** create/remove/modify `git worktree` checkouts (or edit `.worktrees/*`) unless explicitly requested.
- **Multi-agent safety:** do **not** switch branches / check out a different branch unless explicitly requested.
- **Multi-agent safety:** running multiple agents is OK as long as each agent has its own session.
- **Multi-agent safety:** when you see unrecognized files, keep going; focus on your changes and commit only those.
- Lint/format churn:
- If staged+unstaged diffs are formatting-only, auto-resolve without asking.
- If commit/push already requested, auto-stage and include formatting-only follow-ups in the same commit (or a tiny follow-up commit if needed), no extra confirmation.
- Only ask when changes are semantic (logic/data/behavior).
- **Multi-agent safety:** focus reports on your edits; avoid guard-rail disclaimers unless truly blocked; when multiple agents touch the same file, continue if safe; end with a brief “other files present” note only if relevant.
- Bug investigations: read source code of relevant npm dependencies and all related local code before concluding; aim for high-confidence root cause.
- Code style: add brief comments for tricky logic; keep files under ~700 LOC when feasible (split/refactor as needed).
- Tool schema guardrails (google-antigravity): avoid `Type.Union` in tool input schemas; no `anyOf`/`oneOf`/`allOf`. Use `stringEnum`/`optionalStringEnum` (Type.Unsafe enum) for string lists, and `Type.Optional(...)` instead of `... | null`. Keep top-level tool schema as `type: "object"` with `properties`.
- Tool schema guardrails: avoid raw `format` property names in tool schemas; some validators treat `format` as a reserved keyword and reject the schema.
- Never send streaming/partial replies to external messaging surfaces (WhatsApp, Telegram); only final replies should be delivered there. Streaming/tool events may still go to internal UIs/control channel.
- For manual `openclaw message send` messages that include `!`, use the heredoc pattern noted below to avoid the Bash tools escaping.
- Release guardrails: do not change version numbers without operators explicit consent; always ask permission before running any npm publish/release step.
- Beta release guardrail: when using a beta Git tag (for example `vYYYY.M.D-beta.N`), publish npm with a matching beta version suffix (for example `YYYY.M.D-beta.N`) rather than a plain version on `--tag beta`; otherwise the plain version name gets consumed/blocked.
- Rebrand/migration/config warnings: run `openclaw doctor`.
- Never edit `node_modules`.
- Local-only `.agents` ignores: use `.git/info/exclude`, not repo `.gitignore`.
- CLI progress: use `src/cli/progress.ts`; status tables: `src/terminal/table.ts`.
- Connection/provider additions: update all UI surfaces + docs + status/config forms.
- Provider-facing tool schemas: prefer flat string enum helpers over `Type.Union([Type.Literal(...)])`; some providers reject generated `anyOf`. Do not treat this as a repo-wide protocol/schema ban.
- External messaging surfaces: no token-delta channel messages. Follow `docs/concepts/streaming.md`; preview/block streaming uses message edits/chunks and must preserve final/fallback delivery.

View File

@@ -2,33 +2,171 @@
Docs: https://docs.openclaw.ai
## Unreleased
## 2026.4.20
### Changes
- Onboard/wizard: restyle the setup security disclaimer with a single yellow warning banner, section headings and bulleted checklists, and un-dim the note body so key guidance is easy to scan; add a loading spinner during the initial model catalog load so the wizard no longer goes blank while it runs; add an "API key" placeholder to provider API key prompts. (#69553) Thanks @Patrick-Erichsen.
- Agents/prompts: strengthen the default system prompt and OpenAI GPT-5 overlay with clearer completion bias, live-state checks, weak-result recovery, and verification-before-final guidance.
- Models/costs: support tiered model pricing from cached catalogs and configured models, and include bundled Moonshot Kimi K2.6/K2.5 cost estimates for token-usage reports. (#67605) Thanks @sliverp.
- Sessions/Maintenance: enforce the built-in entry cap and age prune by default, and prune oversized stores at load time so accumulated cron/executor session backlogs cannot OOM the gateway before the write path runs. (#69404) Thanks @bobrenze-bot.
- Plugins/tests: reuse plugin loader alias and Jiti config resolution across repeated same-context loads, reducing import-heavy test overhead. (#69316) Thanks @amknight.
- Cron: split runtime execution state into `jobs-state.json` so `jobs.json` stays stable for git-tracked job definitions. (#63105) Thanks @Feelw00.
- Agents/compaction: send opt-in start and completion notices during context compaction. (#67830) Thanks @feniix.
- Moonshot/Kimi: default bundled Moonshot setup, web search, and media-understanding surfaces to `kimi-k2.6` while keeping `kimi-k2.5` available for compatibility. (#69477) Thanks @scoootscooob.
- Moonshot/Kimi: allow `thinking.keep = "all"` on `moonshot/kimi-k2.6`, and strip it for other Moonshot models or requests where pinned `tool_choice` disables thinking. (#68816) Thanks @aniaan.
- BlueBubbles/groups: forward per-group `systemPrompt` config into inbound context `GroupSystemPrompt` so configured group-specific behavioral instructions (for example threaded-reply and tapback conventions) are injected on every turn. Supports `"*"` wildcard fallback matching the existing `requireMention` pattern. Closes #60665. (#69198) Thanks @omarshahine.
- Plugins/tasks: add a detached runtime registration contract so plugin executors can own detached task lifecycle and cancellation without reaching into core task internals. (#68915) Thanks @mbelinky.
- Terminal/logging: optimize `sanitizeForLog()` by replacing the iterative control-character stripping loop with a single regex pass while preserving the existing ANSI-first sanitization behavior. (#67205) Thanks @bulutmuf.
- QA/CI: make `openclaw qa suite` and `openclaw qa telegram` fail by default when scenarios fail, add `--allow-failures` for artifact-only runs, and tighten live-lane defaults for CI automation. (#69122) Thanks @joshavant.
- Mattermost: stream thinking, tool activity, and partial reply text into a single draft preview post that finalizes in place when safe. (#47838) thanks @ninjaa.
### Fixes
- Exec/YOLO: stop rejecting gateway-host exec in `security=full` plus `ask=off` mode via the Python/Node script preflight hardening path, so promptless YOLO exec once again runs direct interpreter stdin and heredoc forms such as `node <<'NODE' ... NODE`.
- OpenAI Codex: normalize legacy `openai-completions` transport overrides on default OpenAI/Codex and GitHub Copilot-compatible hosts back to the native Codex Responses transport while leaving custom proxies untouched. (#45304, #42194) Thanks @dyss1992 and @DeadlySilent.
- Anthropic/plugins: scope Anthropic `api: "anthropic-messages"` defaulting to Anthropic-owned providers, so `openai-codex` and other providers without an explicit `api` no longer get rewritten to the wrong transport. Fixes #64534.
- fix(qqbot): add SSRF guard to direct-upload URL paths in uploadC2CMedia and uploadGroupMedia [AI-assisted]. (#69595) Thanks @pgondhi987.
- fix(gateway): enforce allowRequestSessionKey gate on template-rendered mapping sessionKeys. (#69381) Thanks @pgondhi987.
- Browser/Chrome MCP: surface `DevToolsActivePort` attach failures as browser-connectivity errors instead of a generic "waiting for tabs" timeout, and point signed-out fallbacks toward the managed `openclaw` profile.
- Webchat/images: treat inline image attachments as media for empty-turn gating while still ignoring metadata-only blank turns. (#69474) Thanks @Jaswir.
- Discord/think: only show `adaptive` in `/think` autocomplete for provider/model pairs that actually support provider-managed adaptive thinking, so GPT/OpenAI models no longer advertise an Anthropic-only option.
- Thinking: only expose `max` for models that explicitly support provider max reasoning, and remap stored `max` settings to the largest supported thinking mode when users switch to another model.
- Thinking/UI: drive `/think` options and chat/Sessions pickers from provider-owned thinking profiles, so custom model level sets such as binary `on/off`, Gemini 3 Pro `off/low/high`, Anthropic `adaptive/max`, and OpenAI `xhigh` stay in one runtime contract.
- Gateway/usage: bound the cost usage cache with FIFO eviction so date/range lookups cannot grow unbounded. (#68842) Thanks @Feelw00.
- OpenAI/Responses: resolve `/think` levels against each GPT model's supported reasoning efforts so `/think off` no longer becomes high reasoning or sends unsupported `reasoning.effort: "none"` payloads.
- Lobster/TaskFlow: allow managed approval resumes to use `approvalId` without a resume token, and persist that id in approval wait state. (#69559) Thanks @kirkluokun.
- Plugins/startup: install bundled runtime dependencies into each plugin's own runtime directory, reuse source-checkout repair caches after rebuilds, and log only packages that were actually installed so repeated Gateway starts stay quiet once deps are present.
- Plugins/startup: ignore pnpm's `npm_execpath` when repairing bundled plugin runtime dependencies and skip workspace-only package specs so npm-only install flags or local workspace links do not break packaged plugin startup.
- MCP: block interpreter-startup env keys such as `NODE_OPTIONS` for stdio servers while preserving ordinary credential and proxy env vars. (#69540) Thanks @drobison00.
- Agents/shell: ignore non-interactive placeholder shells like `/usr/bin/false` and `/sbin/nologin`, falling back to `sh` so service-user exec runs no longer exit immediately. (#69308) Thanks @sk7n4k3d.
- Setup/TUI: relaunch the setup hatch TUI in a fresh process while preserving the configured gateway target and auth source, so onboarding recovers terminal state cleanly without exposing gateway secrets on command-line args. (#69524) Thanks @shakkernerd.
- Codex: avoid re-exposing the image-generation tool on native vision turns with inbound images, and keep bare image-model overrides on the configured image provider. (#65061) Thanks @zhulijin1991.
- Sessions/reset: clear auto-sourced model, provider, and auth-profile overrides on `/new` and `/reset` while preserving explicit user selections, so channel sessions stop staying pinned to runtime fallback choices. (#69419) Thanks @sk7n4k3d.
- Sessions/costs: snapshot `estimatedCostUsd` like token counters so repeated persist paths no longer compound the same run cost by up to dozens of times. (#69403) Thanks @MrMiaigi.
- OpenAI Codex: route ChatGPT/Codex OAuth Responses requests through the `/backend-api/codex` endpoint so `openai-codex/gpt-5.4` no longer hits the removed `/backend-api/responses` alias. (#69336) Thanks @mzogithub.
- OpenAI/Responses: omit disabled reasoning payloads when `/think off` is active, so GPT reasoning models no longer receive unsupported `reasoning.effort: "none"` requests. (#61982) Thanks @a-tokyo.
- Gateway/pairing: treat loopback shared-secret node-host, TUI, and gateway clients as local for pairing decisions, so trusted local tools no longer reconnect as remote clients and fail with `pairing required`. (#69431) Thanks @SARAMALI15792.
- Active Memory: degrade gracefully when memory recall fails during prompt building, logging a warning and letting the reply continue without memory context instead of failing the whole turn. (#69485) Thanks @Magicray1217.
- Ollama: add provider-policy defaults for `baseUrl` and `models` so implicit local discovery can run before config validation rejects a minimal Ollama provider config. (#69370) Thanks @PratikRai0101.
- Agents/model selection: clear transient auto-failover session overrides before each turn so recovered primary models are retried immediately without emitting user-override reset warnings. (#69365) Thanks @hitesh-github99.
- Auto-reply: apply silent `NO_REPLY` policy per conversation type, so direct chats get a helpful rewritten reply while groups and internal deliveries can remain quiet. (#68644) Thanks @Takhoffman.
- Telegram/status reactions: honor `messages.removeAckAfterReply` when lifecycle status reactions are enabled, clearing or restoring the reaction after success/error using the configured hold timings. (#68067) Thanks @poiskgit.
- Web search/plugins: resolve plugin-scoped SecretRef API keys for bundled Exa, Firecrawl, Gemini, Kimi, Perplexity, Tavily, and Grok web-search providers when they are selected through the shared web-search config. (#68424) Thanks @afurm.
- Telegram/polling: raise the default polling watchdog threshold from 90s to 120s and add configurable `channels.telegram.pollingStallThresholdMs` (also per-account) so long-running Telegram work gets more room before polling is treated as stalled. (#57737) Thanks @Vitalcheffe.
- Telegram/polling: bound the persisted-offset confirmation `getUpdates` probe with a client-side timeout so a zombie socket cannot hang polling recovery before the runner watchdog starts. (#50368) Thanks @boticlaw.
- Agents/Pi runner: retry silent `stopReason=error` turns with no output when no side effects ran, so non-frontier providers that briefly return empty error turns get another chance instead of ending the session early. (#68310) Thanks @Chased1k.
- Plugins/memory: preserve the active memory capability when read-only snapshot plugin loads run, so status and provider discovery paths no longer wipe memory public artifacts. (#69219) Thanks @zeroaltitude.
- Plugins: keep only the highest-precedence manifest when distinct discovered plugins share an id, so lower-precedence global or workspace duplicates no longer load beside bundled or config-selected plugins. (#41626) Thanks @Tortes.
- fix(security): block MINIMAX_API_HOST workspace env injection and remove env-driven URL routing [AI-assisted]. (#67300) Thanks @pgondhi987.
- Cron/delivery: treat explicit `delivery.mode: "none"` runs as not requested even if the runner reports `delivered: false`, so no-delivery cron jobs no longer persist false delivery failures or errors. (#69285) Thanks @matsuri1987.
- Plugins/install: repair active and default-enabled bundled plugin runtime dependencies before import in packaged installs, so bundled Discord, WhatsApp, Slack, Telegram, and provider plugins work without putting their dependency trees in core.
- BlueBubbles: raise the outbound `/api/v1/message/text` send timeout default from 10s to 30s, and add a configurable `channels.bluebubbles.sendTimeoutMs` (also per-account) so macOS 26 setups where Private API iMessage sends stall for 60+ seconds no longer silently lose messages at the 10s abort. Probes, chat lookups, and health checks keep the shorter 10s default. Fixes #67486. (#69193) Thanks @omarshahine.
- Agents/bootstrap: budget truncation markers against per-file caps, preserve source content instead of silently wasting bootstrap bytes, and avoid marker-only output in tiny-budget truncation cases. (#69114) Thanks @BKF-Gitty.
- Context engine/plugins: stop rejecting third-party context engines whose `info.id` differs from the registered plugin slot id. The strict-match contract added in 2026.4.14 broke `lossless-claw` and other plugins whose internal engine id does not equal the slot id they are registered under, producing repeated `info.id must match registered id` lane failures on every turn. Fixes #66601. (#66678) Thanks @GodsBoy.
- Agents/compaction: rename embedded Pi compaction lifecycle events to `compaction_start` / `compaction_end` so OpenClaw stays aligned with `pi-coding-agent` 0.66.1 event naming. (#67713) Thanks @mpz4life.
- Security/dotenv: block all `OPENCLAW_*` keys from untrusted workspace `.env` files so workspace-local env loading fails closed for new runtime-control variables instead of silently inheriting them. (#473)
- Gateway/device pairing: restrict non-admin paired-device sessions (device-token auth) to their own pairing list, approve, and reject actions so a paired device cannot enumerate other devices or approve/reject pairing requests authored by another device. Admin and shared-secret operator sessions retain full visibility. (#69375) Thanks @eleqtrizit.
- Agents/gateway tool: extend the agent-facing `gateway` tool's config mutation guard so model-driven `config.patch` and `config.apply` cannot rewrite operator-trusted paths (sandbox, plugin trust, gateway auth/TLS, hook routing and tokens, SSRF policy, MCP servers, workspace filesystem hardening) and cannot bypass the guard by editing per-agent sandbox, tools, or embedded-Pi overrides in place under `agents.list[]`. (#69377) Thanks @eleqtrizit.
- Gateway/websocket broadcasts: require `operator.read` (or higher) for chat, agent, and tool-result event frames so pairing-scoped and node-role sessions no longer passively receive session chat content, and scope-gate unknown broadcast events by default. Plugin-defined `plugin.*` broadcasts are scoped to operator.write/admin, and status/transport events (`heartbeat`, `presence`, `tick`, etc.) remain unrestricted. Per-client sequence numbers preserve per-connection monotonicity. (#69373) Thanks @eleqtrizit.
- Agents/compaction: always reload embedded Pi resources through an explicit loader and reapply reserve-token overrides so runs without extension factories no longer silently lose compaction settings before session start. (#67146) Thanks @ly85206559.
- Memory-core/dreaming: normalize sweep timestamps and reuse hashed narrative session keys for fallback cleanup so Dreaming narrative sub-sessions stop leaking. (#67023) Thanks @chiyouYCH.
- Gateway/startup: delay HTTP bind until websocket handlers are attached, so immediate post-startup websocket health/connect probes no longer hit the startup race window. (#43392) Thanks @dalefrieswthat.
- Codex/app-server: release the session lane when a downstream consumer throws while draining the `turn/completed` notification, so follow-up messages after a Codex plugin reply stop queueing behind a stale lane lock. Fixes #67996. (#69072) Thanks @ayeshakhalid192007-dev.
- Codex/app-server: default approval handling to `on-request` so Codex harness sessions do not start with overly permissive tool approvals. (#68721) Thanks @Lucenx9.
- Cron/delivery: keep isolated cron chat delivery tools available, resolve `channel: "last"` targets from the gateway, show delivery previews in `cron list/show`, and avoid duplicate fallback sends after direct message-tool delivery. (#69587) Thanks @obviyus.
- BlueBubbles: add opt-in `channels.bluebubbles.coalesceSameSenderDms` so a single composed message with text + pasted URL (which Apple splits into two webhooks ~0.8-2.0 s apart) arrives as one agent turn instead of two. When enabled, DM messages that are not linked via `associatedMessageGuid` hash to `dm:<chat>:<sender>` so the inbound debounce window merges them into a single merged turn — including URL-preview balloon events, DM control-command sends (which normally bypass debouncing), and rapid same-sender follow-ups. The default inbound debounce window widens from 500 ms to 2500 ms when the flag is set without an explicit `messages.inbound.byChannel.bluebubbles`, covering the observed Apple split-send cadence. Every source `messageId` folded into the merged view is committed to the inbound dedupe store after processing, so a later MessagePoller replay of any individual source event is recognized as a duplicate. Merged output is bounded (≤4000 chars text with an explicit `…[truncated]` marker, ≤20 attachments, first-plus-latest sampling beyond 10 source entries) so a rapid-fire flood inside the window cannot amplify the downstream prompt. Group chats and existing text+balloon follow-ups continue to key per-message. See [Coalescing split-send DMs](https://docs.openclaw.ai/channels/bluebubbles#coalescing-split-send-dms-command--url-in-one-composition) for scenarios, tuning, and troubleshooting. (#69258) Thanks @omarshahine.
- Cron/Telegram: key isolated direct-delivery dedupe to each cron execution instead of the reused session id, so recurring Telegram announce runs no longer report delivered while silently skipping later sends. (#69000) Thanks @obviyus.
- Models/Kimi: default bundled Kimi thinking to off and normalize Anthropic-compatible `thinking` payloads so stale session `/think` state no longer silently re-enables reasoning on Kimi runs. (#68907) Thanks @frankekn.
- Control UI/cron: keep the runtime-only `last` delivery sentinel from being materialized into persisted cron delivery and failure-alert channel configs when jobs are created or edited. (#68829) Thanks @tianhaocui.
- OpenAI/Responses: strip orphaned reasoning blocks before outbound Responses API calls so compacted or restored histories no longer fail on standalone reasoning items. (#55787) Thanks @suboss87.
- Cron/CLI: parse PowerShell-style `--tools` allow-lists the same way as comma-separated input, so `cron add` and `cron edit` no longer persist `exec read write` as one combined tool entry on Windows. (#68858) Thanks @chen-zhang-cs-code.
- Browser/user-profile: let existing-session `profile="user"` tool calls auto-route to a connected browser node or use explicit `target="node"`, while still honoring explicit `target="host"` pinning. (#48677)
- Discord/slash commands: tolerate partial Discord channel metadata in slash-command and model-picker flows so partial channel objects no longer crash when channel names, topics, or thread parent metadata are unavailable. (#68953) Thanks @dutifulbob.
- BlueBubbles: consolidate outbound HTTP through a typed `BlueBubblesClient` that resolves the SSRF policy once at construction so image attachments stop getting blocked on localhost and reactions stop getting blocked on private-IP BB deployments. Fixes #34749 and #59722. (#68234) Thanks @omarshahine.
- Cron/gateway: reject ambiguous announce delivery config at add/update time so invalid multi-channel or target-id provider settings fail early instead of persisting broken cron jobs. (#69015) Thanks @obviyus.
- Cron/main-session delivery: preserve `heartbeat.target="last"` through deferred wake queuing, gateway wake forwarding, and same-target wake coalescing so queued cron replies still return to the last active chat. (#69021) Thanks @obviyus.
- Cron/gateway: ignore disabled channels when announce delivery ambiguity is checked, and validate main-session delivery patches against the live cron service default agent so hot-reloaded agent config does not falsely reject valid updates. (#69040) Thanks @obviyus.
- Matrix/allowlists: hot-reload `dm.allowFrom` and `groupAllowFrom` entries on inbound messages while keeping config removals authoritative, so Matrix allowlist changes no longer require a channel restart to add or revoke a sender. (#68546) Thanks @johnlanni.
- BlueBubbles: always set `method` explicitly on outbound text sends (`"private-api"` when available, `"apple-script"` otherwise), and prefer Private API on macOS 26 even for plain text. Fixes silent delivery failure on macOS setups without Private API where an omitted `method` let BB Server fall back to version-dependent default behavior that silently drops the message (#64480), and the AppleScript `-1700` error on macOS 26 Tahoe plain text sends (#53159). (#69070) Thanks @xqing3.
- Matrix/commands: recognize slash commands that are prefixed with the bot's Matrix mention, so room messages like `@bot:server /new` trigger the command path without requiring custom mention regexes. (#68570) Thanks @nightq and @johnlanni.
- Gateway/pairing: return reason-specific `PAIRING_REQUIRED` details, remediation hints, and request ids so unapproved-device and scope-upgrade failures surface actionable recovery guidance in the CLI and Control UI. (#69227) Thanks @obviyus.
- Agents/subagents: include requested role and runtime timing on subagent failure payloads so parent agents can correlate failed or timed-out child work. (#68726) Thanks @BKF-Gitty.
- Gateway/sessions: reject stale agent-scoped sessions after an agent is removed from config while preserving legacy default-agent main-session aliases. (#65986) Thanks @bittoby.
- Doctor/gateway: surface pending device pairing requests, scope-upgrade approval drift, and stale device-token mismatch repair steps so `openclaw doctor --fix` no longer leaves pairing/auth setup failures unexplained. (#69210) Thanks @obviyus.
- Cron/isolated-agent: preserve explicit `delivery.mode: "none"` message targets for isolated runs without inheriting implicit `last` routing, so agent-initiated Telegram sends keep their authored destination while bare `mode:none` jobs stay targetless. (#69153) Thanks @obviyus.
- Cron/isolated-agent: keep `delivery.mode: "none"` account-only or thread-only configs from inheriting a stale implicit recipient, so isolated runs only resolve message routing when the job authored an explicit `to` target. (#69163) Thanks @obviyus.
- Gateway/TUI: retry session history while the local gateway is still finishing startup, so `openclaw tui` reconnects no longer fail on transient `chat.history unavailable during gateway startup` errors. (#69164) Thanks @shakkernerd.
- BlueBubbles/reactions: fall back to `love` when an agent reacts with an emoji outside the iMessage tapback set (`love`/`like`/`dislike`/`laugh`/`emphasize`/`question`), so wider-vocabulary model reactions like `👀` still produce a visible tapback instead of failing the whole reaction request. Configured ack reactions still validate strictly via the new `normalizeBlueBubblesReactionInputStrict` path. (#64693) Thanks @zqchris.
- BlueBubbles: prefer iMessage over SMS when both chats exist for the same handle, honor explicit `sms:` targets, and never silently downgrade iMessage-available recipients. (#61781) Thanks @rmartin.
- Telegram/setup: require numeric `allowFrom` user IDs during setup instead of offering unsupported `@username` DM resolution, and point operators to `from.id`/`getUpdates` for discovery. (#69191) Thanks @obviyus.
- GitHub Copilot/onboarding: default GitHub Copilot setup to `claude-opus-4.6` and keep the bundled default model list aligned, so new Copilot setups no longer start on the older `gpt-4o` default. (#69207) Thanks @obviyus.
- Gateway/status: separate reachability, capability, and read-probe reporting so connect-only or scope-limited sessions no longer look fully healthy, and normalize SSH targets entered as `ssh user@host`. (#69215) Thanks @obviyus.
- Slack: fix outbound replies failing with "unresolved SecretRef" for accounts configured via `file` or `exec` secret sources; the send path now tolerates the runtime snapshot retaining an unresolved channel SecretRef when a boot-resolved token override is already available. (#68954) Thanks @openperf.
- Control UI/device pairing: explain scope and role approval upgrades during reconnects, and show requested versus approved access in the Control UI and `openclaw devices` so broader reconnects no longer look like lost pairings. (#69221) Thanks @obviyus.
- Gateway/Control UI: surface pending scope, role, and device-metadata pairing approvals in auth errors and Control UI hints so broader reconnects no longer look like random auth breakage. (#69226) Thanks @obviyus.
- Telegram/media: parse lowercase media directives in block replies and preserve outbound attachment filenames, so generated files send once with their original names. (#69641) Thanks @obviyus.
- Agents/Anthropic: honor explicit `cacheRetention: "long"` for custom `anthropic-messages` endpoints by applying the 1-hour ephemeral cache TTL independently of the Anthropic/Vertex hostname allowlist. Implicit and env-driven long retention still require an allowlisted host. (#67800) Thanks @MonkeyLeeT.
## 2026.4.19-beta.2
### Fixes
- Agents/openai-completions: always send `stream_options.include_usage` on streaming requests, so local and custom OpenAI-compatible backends report real context usage instead of showing 0%. (#68746) Thanks @kagura-agent.
- Agents/nested lanes: scope nested agent work per target session so a long-running nested run on one session no longer head-of-line blocks unrelated sessions across the gateway. (#67785) Thanks @stainlu.
- Agents/status: preserve carried-forward session token totals for providers that omit usage metadata, so `/status` and `openclaw sessions` keep showing the last known context usage instead of dropping back to unknown/0%. (#67695) Thanks @stainlu.
- Install/update: keep legacy update verification compatible with the QA Lab runtime shim, so updating older global installs to beta no longer fails after npm installs the package successfully.
## 2026.4.19-beta.1
### Fixes
- Agents/channels: route cross-agent subagent spawns through the target agent's bound channel account while preserving peer and workspace/role-scoped bindings, so child sessions no longer inherit the caller's account in shared rooms, workspaces, or multi-account setups. (#67508) Thanks @lukeboyett and @gumadeiras.
- Telegram/callbacks: treat permanent callback edit errors as completed updates so stale command pagination buttons no longer wedge the update watermark and block newer Telegram updates. (#68588) Thanks @Lucenx9.
- Browser/CDP: allow the selected remote CDP profile host for CDP health and control checks without widening browser navigation SSRF policy, so WSL-to-Windows Chrome endpoints no longer appear offline under strict defaults. Fixes #68108. (#68207) Thanks @Mlightsnow.
- Codex: stop cumulative app-server token totals from being treated as fresh context usage, so session status no longer reports inflated context percentages after long Codex threads. (#64669) Thanks @cyrusaf.
- Browser/CDP: add phase-specific CDP readiness diagnostics and normalize loopback WebSocket host aliases, so Windows browser startup failures surface whether HTTP discovery, WebSocket discovery, SSRF validation, or the `Browser.getVersion` health check failed.
- Browser/CDP: discover Chromes real DevTools websocket from bare `ws://host:port` attach-only roots before declaring the profile down, while still falling back to direct websocket providers that do not expose `/json/version`. Fixes #68027. (#68715) Thanks @visionik.
## 2026.4.18
### Changes
- Anthropic/models: add Claude Opus 4.7 `xhigh` reasoning effort support and keep it separate from adaptive thinking.
- Control UI/settings: overhaul the settings and slash-command experience with faster presets, quick-create flows, and refreshed command discovery. (#67819) Thanks @BunsDev.
- macOS/gateway: add `screen.snapshot` support for macOS app nodes, including runtime plumbing, default macOS allowlisting, and docs for monitor preview flows. (#67954) Thanks @BunsDev.
### Fixes
- Codex/gateway: fix gateway crashes when the codex-acp subprocess terminates abruptly; pending requests now shut down gracefully instead of propagating an uncaught EPIPE through the gateway daemon and connected channels. Fixes #67886. (#67947) Thanks @openperf.
- Agents/bootstrap: resolve bootstrap from workspace truth instead of stale session transcript markers, keep embedded bootstrap instructions on a hidden user-context prelude, suppress normal `/new` and `/reset` greetings while `BOOTSTRAP.md` is still pending, and make the embedded runner read the bootstrap ritual before replying normally.
- Agents/bootstrap: dedupe repeated bootstrap-truncation warnings so startup logs stay actionable. (#67906) Thanks @rubencu.
- WhatsApp/multi-account: centralize named-account inbound policy, isolate per-account group activation and scoped session keys, preserve legacy activation backfill, and keep `accounts.default` shared defaults aligned across runtime, setup, and compat migration paths. Thanks @mcaxtr.
- Cron/delivery: clean up isolated sessions after direct deliveries when `deleteAfterRun` is enabled, covering structured and threaded branches that previously bypassed cleanup. (#67807) Thanks @MonkeyLeeT.
- Gateway/hello-ok: always report negotiated auth metadata for successful shared-auth handshakes, including control-ui bypass coverage when no device token is issued. (#67810) Thanks @BunsDev.
- Gateway/hello-ok: always report negotiated auth metadata and preserve scopes for reused device tokens on successful shared-auth handshakes, including control-ui bypass coverage when no device token is issued. (#67810, #68039) Thanks @BunsDev.
- Onboarding/non-interactive: preserve existing gateway auth tokens during re-onboard so active local gateway clients are not disconnected by an implicit token rotation. (#67821) Thanks @BKF-Gitty.
- OpenAI Codex/Responses: unify native Responses API capability detection so Codex OAuth requests emit the required `store: false` field on the native Responses path. (#67918) Thanks @obviyus.
- WhatsApp/setup: guard personal-phone and allowlist prompt values so setup fails with clear validation errors instead of crashing on undefined prompt text. (#67895) Thanks @lawrence3699.
- Models/config: preserve an existing `models.json` provider `baseUrl` during merge-mode regeneration so custom endpoints do not get reset on restart. (#67893) Thanks @lawrence3699.
- Plugin SDK: preserve `secret-input-runtime` function exports in published builds so provider plugins can read SecretRef-backed setup inputs.
- Plugins/discovery: reuse bundled and global plugin discovery results across workspace cache misses so Windows multi-workspace startup stops redoing the shared synchronous scan. (#67940) Thanks @obviyus.
- Bundled plugins/install: keep staged bundled plugin runtime imports resolving through the packaged Plugin SDK while omitting checkout-only aliases from the dist inventory, so published installs do not fail on repo-local paths.
- Plugins/webhooks: enforce synchronous plugin registration with full rollback of failed plugin side effects, and cache SecretRef-backed webhook auth per route so plugin startup and inbound webhook auth stay deterministic. (#67941) Thanks @obviyus.
- Telegram/polling transport: give the Telegram undici dispatcher pool bounded keep-alive defaults and an explicit lifecycle. Previously every recoverable network error and stall watchdog trip silently replaced the transport, abandoning the old dispatcher pool and its sockets; long-running gateway processes accumulated hundreds of ESTABLISHED connections to `api.telegram.org`, saturating per-IP upstream proxy quotas and causing the actively-used outbound proxy node to time out while every other node still tested healthy. Transports now expose `close()`, `TelegramPollingTransportState` destroys the stale transport on dirty-rebuild, and `TelegramPollingSession` disposes the transport when polling exits — backed by a strict per-origin pool cap on every constructed `Agent`, `ProxyAgent`, and `EnvHttpProxyAgent` as defence in depth.
- Telegram/polling: publish successful `getUpdates` calls as account health liveness, avoid false stall restarts after recoverable `getUpdates` errors, and force Telegram API dispatchers to HTTP/1.1 so stalled polling recovers instead of sitting connected-but-dead.
- Telegram/ACP bindings: drop persisted DM bindings that still point at missing or failed ACP sessions on restart, while preserving plugin-owned bindings and uncertain store reads. (#67822) Thanks @chinar-amrutkar.
- Telegram/streaming: keep a transient preview on the same Telegram message when auto-compaction retries an in-flight answer, so streamed replies no longer appear duplicated after compaction. (#66939) Thanks @rubencu.
- Memory/sqlite-vec: emit the degraded sqlite-vec warning once per degraded episode instead of repeating it for every file write, while preserving the latch across safe-reindex rollback and resetting it when vector state is genuinely rebuilt. (#67898) Thanks @rubencu.
- Memory-core: preserve stored vector dimensions during read-only recovery so memory indexes do not lose vector metadata while repairing read-only state.
- Reply/block streaming: preserve post-stream incomplete-turn error payloads after block streaming already emitted content, so users get the warning instead of silence. (#67991) Thanks @obviyus.
- Telegram/streaming: clear the compaction replay guard after visible non-final boundaries so a post-tool assistant reply rotates to a fresh preview instead of editing the pre-compaction message. (#67993) Thanks @obviyus.
- Matrix: fix `sessions_spawn --thread` subagent session spawning — thread binding creation, cleanup on session end, and completion-message delivery target resolution now work end-to-end. (#67643) Thanks @eejohnso-ops and @gumadeiras.
- Slack/streaming: resolve native streaming recipient teams from the inbound user when available, with a monitor-team fallback, so DM and shared-workspace streams target the right recipient more reliably.
- macOS/webchat: enable Undo and Redo in the composer text input by turning on the native `NSTextView` undo manager. (#34962) Thanks @tylerbittner.
- macOS/remote SSH: require an already-trusted host key on the macOS remote command, gateway probe, port tunnel, and pairing probe paths by switching `StrictHostKeyChecking=accept-new` to `StrictHostKeyChecking=yes` and centralizing the shared SSH option fragments in `CommandResolver`, so first-time macOS remote connections no longer silently accept an unknown host key and must be trusted ahead of time via `~/.ssh/known_hosts`. (#68199)
- CLI/configure: show the channel picker before probing statuses and let remove mode delete configured channel blocks directly from config. (#68007) Thanks @gumadeiras.
- Control UI/settings: reset scroll position when switching settings pages and align details headers. (#68150) Thanks @BunsDev.
- WhatsApp/gateway: harden WhatsApp auth persistence and backup recovery, model unstable auth state explicitly in setup/status/health, recover backup-backed login without forcing a fresh QR, and keep local gateway handoff and channel restarts truthful after login. Thanks @mcaxtr.
- OpenAI Codex/OAuth: keep OpenClaw as the canonical owner for imported Codex CLI OAuth sessions, stop writing refreshed credentials back into `.codex`, and prefer fresher OpenClaw credentials over stale imported CLI state so refresh recovery stays stable. Thanks @vincentkoc.
- OpenAI Codex/OAuth: treat the OpenAI TLS prerequisites probe as advisory instead of a hard blocker, so Codex sign-in can still proceed when the speculative Node/OpenSSL precheck fails but the real OAuth flow still works. Thanks @vincentkoc.
- Models status/OAuth health: align OAuth health reporting with the same effective credential view runtime uses, so expired refreshable sessions stop showing healthy by default and fresher imported Codex CLI credentials surface correctly in `models status`, doctor, and gateway auth status. Thanks @vincentkoc.
@@ -43,9 +181,11 @@ Docs: https://docs.openclaw.ai
- Failover/google: only treat `INTERNAL` status payloads as retryable timeouts when they also carry a `500` code, so malformed non-500 payloads do not enter the retry path. (#68238) Thanks @altaywtf and @Openbling.
- Agents/tools: filter bundled MCP/LSP tools through the final owner-only and tool-policy pipeline after merging them into the effective tool list, so existing allowlists, deny rules, sandbox policy, subagent policy, and owner-only restrictions apply to bundled tools the same way they apply to core tools. (#68195)
- Gateway/assistant media: require `operator.read` scope for assistant-media file and metadata requests on identity-bearing HTTP auth paths so callers without a read scope can no longer access assistant media. (#68175) Thanks @eleqtrizit.
- Gateway/web: allow same-origin microphone access in the Permissions-Policy header so browser voice capture can work from the Control UI and webchat origin. (#68368)
- Exec approvals/display: escape raw control characters (including newline and carriage return) in the shared and macOS approval-prompt command sanitizers, so trailing command payloads no longer render on hidden extra lines in the approval UI. (#68198)
- Telegram/streaming: fence same-session stale preview and finalization work after aborts so Telegram no longer replays an older reply or flushes a hidden short preview after the abort confirmation lands. (#68100) Thanks @rubencu.
- OpenAI Codex/OAuth + Pi: keep imported Codex CLI OAuth bootstrap, Pi auth export, and runtime overlay handling aligned so Codex sessions survive refresh and health checks without leaking transient CLI state into saved auth files. Thanks @vincentkoc.
- OpenAI Codex/OAuth: keep Codex-specific auth bridging inside the owning plugins, preserve canonical imported CLI profiles, and allow legacy identity-less main-store OAuth sessions to upgrade during refresh mirroring. (#68284) Thanks @vincentkoc.
- Config/redact: add `browser.cdpUrl` and `browser.profiles.*.cdpUrl` to sensitive URL config paths so embedded credentials (query tokens and HTTP Basic auth) are properly redacted in `config.get` API responses and availability error messages. (#67679) Thanks @Ziy1-Tan.
- Agents/TTS: report failed speech synthesis as a real tool error so unconfigured providers no longer feed successful TTS failure output back into agent loops. (#67980) Thanks @lawrence3699.
- Gateway/wake: allow unknown properties on wake payloads so external senders like Paperclip can attach opaque metadata without failing schema validation. (#68355) Thanks @kagura-agent.
@@ -54,6 +194,16 @@ Docs: https://docs.openclaw.ai
- Agents/failover: avoid treating bare leading `402 ...` prose as billing errors while still recognizing proxy subscription failures. (#45827) Thanks @junyuc25.
- Config/$schema: preserve root-authored `$schema` during partial config rewrites without injecting include-only schema URLs into the root config. (#47322) Thanks @EfeDurmaz16.
- Agents/CLI delivery: run the same reply-media path normalizer the auto-reply flow uses before shipping `openclaw agent --deliver` payloads, so relative `MEDIA:./out/photo.png` tokens resolve against the agent workspace instead of being rejected downstream with `LocalMediaAccessError: Local media path is not under an allowed directory`. Thanks @frankekn.
- Agents/Google: strip `thinkingBudget=0` for the thinking-required `gemini-2.5-pro` model in embedded-runner and native Google payloads, so requests no longer fail with `Budget 0 is invalid. This model only works in thinking mode.` and the API uses its default thinking behavior instead. (#68607) Thanks @josmithiii.
- Slack/threads: log failed thread starter and history fetches at verbose level while preserving best-effort fallback behavior, so missing Slack thread context is diagnosable without interrupting inbound handling. (#68594) Thanks @martingarramon.
- Gateway/restart: keep stale-gateway cleanup from terminating the current process's parent or ancestors, so plugin sidecars like WeChat no longer kill the active gateway and trigger an infinite supervisor restart loop. Fixes #68451. (#68517) Thanks @openperf.
- Gateway/auth: reject gateway auth credentials that match published example placeholders at startup and secret reload, and keep cloud install snippets from publishing copy-paste gateway/keyring secrets. (#68404) Thanks @coygeek.
- CLI/update: preserve macOS restart helper launchctl failures in the update restart log without letting log setup block the restart path. (#68492) Thanks @hclsys.
- Slack/threads: keep file-only root messages as starter context so first thread replies can still hydrate starter media. (#68594) Thanks @martingarramon.
- Google/Antigravity: resolve forward-compatible Gemini 3.1 Pro custom-tools and Flash variants from the bundled Google plugin templates, so `google-antigravity/gemini-3.1-pro-preview-customtools` no longer falls through to an unknown-model error. Fixes #35512.
- Active Memory: raise the blocking recall timeout ceiling to 120 seconds and reject larger config values during plugin schema validation. Fixes #68410. (#68480) Thanks @Bartok9.
- Control UI/chat: keep history-backed user image uploads visible after chat reload while filtering blocked or non-image transcript media paths. (#68415) Thanks @mraleko.
- Matrix/plugins: keep remaining Matrix event helpers on the canonical `matrix-js-sdk` subpath so build and plugin-load entrypoint checks stay consistent. (#68498) Thanks @masatohoshino.
## 2026.4.15
@@ -162,6 +312,9 @@ Docs: https://docs.openclaw.ai
- Node-host/tools.exec: let approval binding distinguish known native binaries from mutable shell payload files, while still fail-closing unknown or racy file probes so absolute-path node-host commands like `/usr/bin/whoami` no longer get rejected as unsafe interpreter/runtime commands. (#66731) Thanks @tmimmanuel.
- Codex/gateway: fix gateway crash when the codex-acp subprocess terminates abruptly; an unhandled EPIPE on the child stdin stream now routes through graceful client shutdown, rejecting pending requests instead of propagating as an uncaught exception that crashes the entire gateway daemon and all connected channels. Fixes #67886. (#67947) thanks @openperf
- Slack/streaming: resolve native streaming recipient teams from the inbound user when available, with a monitor-team fallback, so DM and shared-workspace streams target the right recipient more reliably.
- OpenRouter/streaming: treat `reasoning_details.response.output_text` and `reasoning_details.response.text` as visible assistant output on OpenRouter-compatible completions streams, while keeping `reasoning.text` hidden and refusing to surface ambiguous bare `text` items by default so visible replies, thinking blocks, and tool calls can coexist in the same chunk. (#67410) Thanks @neeravmakwana.
- Models/OpenRouter aliases: resolve `openrouter:auto` to the canonical `openrouter/auto` model and map `openrouter:free` to the first configured concrete `openrouter/...:free` model instead of mis-resolving these compatibility aliases under the default provider. (#57066) Thanks @sumiisiaran.
- OpenRouter/Arcee: canonicalize stale OpenRouter `https://openrouter.ai/v1` base URLs during provider config normalization and runtime model/transport resolution, so fresh `models.json` writes and previously discovered rows self-heal back to `https://openrouter.ai/api/v1` instead of breaking OpenRouter-routed requests. (#67295) Thanks @achalkov.
## 2026.4.14

View File

@@ -1,6 +1,6 @@
# syntax=docker/dockerfile:1.7
FROM debian:bookworm-slim@sha256:98f4b71de414932439ac6ac690d7060df1f27161073c5036a7553723881bffbe
FROM debian:bookworm-slim@sha256:4724b8cc51e33e398f0e2e15e18d5ec2851ff0c2280647e1310bc1642182655d
ENV DEBIAN_FRONTEND=noninteractive

View File

@@ -1,6 +1,6 @@
# syntax=docker/dockerfile:1.7
FROM debian:bookworm-slim@sha256:98f4b71de414932439ac6ac690d7060df1f27161073c5036a7553723881bffbe
FROM debian:bookworm-slim@sha256:4724b8cc51e33e398f0e2e15e18d5ec2851ff0c2280647e1310bc1642182655d
ENV DEBIAN_FRONTEND=noninteractive

View File

@@ -157,9 +157,9 @@ Run `openclaw doctor` to surface risky/misconfigured DM policies.
## Security model (important)
- Default: tools run on the host for the `main` session, so the agent has full access when it is just you.
- Group/channel safety: set `agents.defaults.sandbox.mode: "non-main"` to run non-`main` sessions inside per-session Docker sandboxes.
- Group/channel safety: set `agents.defaults.sandbox.mode: "non-main"` to run non-`main` sessions inside sandboxes. Docker is the default sandbox backend; SSH and OpenShell backends are also available.
- Typical sandbox default: allow `bash`, `process`, `read`, `write`, `edit`, `sessions_list`, `sessions_history`, `sessions_send`, `sessions_spawn`; deny `browser`, `canvas`, `nodes`, `cron`, `discord`, `gateway`.
- Before exposing anything remotely, read [Security](https://docs.openclaw.ai/gateway/security), [Docker sandboxing](https://docs.openclaw.ai/install/docker), and [Configuration](https://docs.openclaw.ai/gateway/configuration).
- Before exposing anything remotely, read [Security](https://docs.openclaw.ai/gateway/security), [Sandboxing](https://docs.openclaw.ai/gateway/sandboxing), and [Configuration](https://docs.openclaw.ai/gateway/configuration).
## Operator quick refs
@@ -173,7 +173,7 @@ Run `openclaw doctor` to surface risky/misconfigured DM policies.
- New here: [Getting started](https://docs.openclaw.ai/start/getting-started), [Onboarding](https://docs.openclaw.ai/start/wizard), [Updating](https://docs.openclaw.ai/install/updating)
- Channel setup: [Channels index](https://docs.openclaw.ai/channels), [WhatsApp](https://docs.openclaw.ai/channels/whatsapp), [Telegram](https://docs.openclaw.ai/channels/telegram), [Discord](https://docs.openclaw.ai/channels/discord), [Slack](https://docs.openclaw.ai/channels/slack)
- Apps + nodes: [macOS](https://docs.openclaw.ai/platforms/macos), [iOS](https://docs.openclaw.ai/platforms/ios), [Android](https://docs.openclaw.ai/platforms/android), [Nodes](https://docs.openclaw.ai/nodes)
- Config + security: [Configuration](https://docs.openclaw.ai/gateway/configuration), [Security](https://docs.openclaw.ai/gateway/security), [Docker sandboxing](https://docs.openclaw.ai/install/docker)
- Config + security: [Configuration](https://docs.openclaw.ai/gateway/configuration), [Security](https://docs.openclaw.ai/gateway/security), [Sandboxing](https://docs.openclaw.ai/gateway/sandboxing)
- Remote + web: [Gateway](https://docs.openclaw.ai/gateway), [Remote access](https://docs.openclaw.ai/gateway/remote), [Tailscale](https://docs.openclaw.ai/gateway/tailscale), [Web surfaces](https://docs.openclaw.ai/web)
- Tools + automation: [Tools](https://docs.openclaw.ai/tools), [Skills](https://docs.openclaw.ai/tools/skills), [Cron jobs](https://docs.openclaw.ai/automation/cron-jobs), [Webhooks](https://docs.openclaw.ai/automation/webhook), [Gmail Pub/Sub](https://docs.openclaw.ai/automation/gmail-pubsub)
- Internals: [Architecture](https://docs.openclaw.ai/concepts/architecture), [Agent](https://docs.openclaw.ai/concepts/agent), [Session model](https://docs.openclaw.ai/concepts/session), [Gateway protocol](https://docs.openclaw.ai/reference/rpc)
@@ -212,21 +212,34 @@ Runbook: [iOS connect](https://docs.openclaw.ai/platforms/ios).
Prefer `pnpm` for builds from source. Bun is optional for running TypeScript directly.
For the dev loop:
```bash
git clone https://github.com/openclaw/openclaw.git
cd openclaw
pnpm install
pnpm ui:build # auto-installs UI deps on first run
pnpm build
pnpm openclaw onboard --install-daemon
# First run only (or after resetting local OpenClaw config/workspace)
pnpm openclaw setup
# Optional: prebuild Control UI before first startup
pnpm ui:build
# Dev loop (auto-reload on source/config changes)
pnpm gateway:watch
```
Note: `pnpm openclaw ...` runs TypeScript directly (via `tsx`). `pnpm build` produces `dist/` for running via Node / the packaged `openclaw` binary.
If you need a built `dist/` from the checkout (for Node, packaging, or release validation), run:
```bash
pnpm build
pnpm ui:build
```
`pnpm openclaw setup` writes the local config/workspace needed for `pnpm gateway:watch`. It is safe to re-run, but you normally only need it on first setup or after resetting local state. `pnpm gateway:watch` does not rebuild `dist/control-ui`, so rerun `pnpm ui:build` after `ui/` changes or use `pnpm ui:dev` when iterating on the Control UI. If you want this checkout to run onboarding directly, use `pnpm openclaw onboard --install-daemon`.
Note: `pnpm openclaw ...` runs TypeScript directly (via `tsx`). `pnpm build` produces `dist/` for running via Node / the packaged `openclaw` binary, while `pnpm gateway:watch` rebuilds the runtime on demand during the dev loop.
## Development channels
@@ -285,133 +298,69 @@ Thanks to all clawtributors:
<!-- clawtributors:start -->
[![steipete](https://avatars.githubusercontent.com/u/58493?v=4&s=48)](https://github.com/steipete) [![vincentkoc](https://avatars.githubusercontent.com/u/25068?v=4&s=48)](https://github.com/vincentkoc) [![Takhoffman](https://avatars.githubusercontent.com/u/781889?v=4&s=48)](https://github.com/Takhoffman) [![obviyus](https://avatars.githubusercontent.com/u/22031114?v=4&s=48)](https://github.com/obviyus) [![gumadeiras](https://avatars.githubusercontent.com/u/5599352?v=4&s=48)](https://github.com/gumadeiras) [![Mariano Belinky](https://avatars.githubusercontent.com/u/132747814?v=4&s=48)](https://github.com/mbelinky) [![vignesh07](https://avatars.githubusercontent.com/u/1436853?v=4&s=48)](https://github.com/vignesh07) [![joshavant](https://avatars.githubusercontent.com/u/830519?v=4&s=48)](https://github.com/joshavant) [![scoootscooob](https://avatars.githubusercontent.com/u/167050519?v=4&s=48)](https://github.com/scoootscooob) [![jacobtomlinson](https://avatars.githubusercontent.com/u/1610850?v=4&s=48)](https://github.com/jacobtomlinson)
[![shakkernerd](https://avatars.githubusercontent.com/u/165377636?v=4&s=48)](https://github.com/shakkernerd) [![sebslight](https://avatars.githubusercontent.com/u/19554889?v=4&s=48)](https://github.com/sebslight) [![tyler6204](https://avatars.githubusercontent.com/u/64381258?v=4&s=48)](https://github.com/tyler6204) [![ngutman](https://avatars.githubusercontent.com/u/1540134?v=4&s=48)](https://github.com/ngutman) [![thewilloftheshadow](https://avatars.githubusercontent.com/u/35580099?v=4&s=48)](https://github.com/thewilloftheshadow) [![Sid-Qin](https://avatars.githubusercontent.com/u/201593046?v=4&s=48)](https://github.com/Sid-Qin) [![mcaxtr](https://avatars.githubusercontent.com/u/7562095?v=4&s=48)](https://github.com/mcaxtr) [![eleqtrizit](https://avatars.githubusercontent.com/u/31522568?v=4&s=48)](https://github.com/eleqtrizit) [![BunsDev](https://avatars.githubusercontent.com/u/68980965?v=4&s=48)](https://github.com/BunsDev) [![cpojer](https://avatars.githubusercontent.com/u/13352?v=4&s=48)](https://github.com/cpojer)
[![Glucksberg](https://avatars.githubusercontent.com/u/80581902?v=4&s=48)](https://github.com/Glucksberg) [![osolmaz](https://avatars.githubusercontent.com/u/2453968?v=4&s=48)](https://github.com/osolmaz) [![bmendonca3](https://avatars.githubusercontent.com/u/208517100?v=4&s=48)](https://github.com/bmendonca3) [![jalehman](https://avatars.githubusercontent.com/u/550978?v=4&s=48)](https://github.com/jalehman) [![huntharo](https://avatars.githubusercontent.com/u/5617868?v=4&s=48)](https://github.com/huntharo) [![neeravmakwana](https://avatars.githubusercontent.com/u/261249544?v=4&s=48)](https://github.com/neeravmakwana) [![openperf](https://avatars.githubusercontent.com/u/80630709?v=4&s=48)](https://github.com/openperf) [![joshp123](https://avatars.githubusercontent.com/u/1497361?v=4&s=48)](https://github.com/joshp123) [![pgondhi987](https://avatars.githubusercontent.com/u/270720687?v=4&s=48)](https://github.com/pgondhi987) [![altaywtf](https://avatars.githubusercontent.com/u/9790196?v=4&s=48)](https://github.com/altaywtf)
[![quotentiroler](https://avatars.githubusercontent.com/u/40643627?v=4&s=48)](https://github.com/quotentiroler) [![liuxiaopai-ai](https://avatars.githubusercontent.com/u/73659136?v=4&s=48)](https://github.com/liuxiaopai-ai) [![rodrigouroz](https://avatars.githubusercontent.com/u/384037?v=4&s=48)](https://github.com/rodrigouroz) [![frankekn](https://avatars.githubusercontent.com/u/4488090?v=4&s=48)](https://github.com/frankekn) [![drobison00](https://avatars.githubusercontent.com/u/5256797?v=4&s=48)](https://github.com/drobison00) [![zerone0x](https://avatars.githubusercontent.com/u/39543393?v=4&s=48)](https://github.com/zerone0x) [![onutc](https://avatars.githubusercontent.com/u/152018508?v=4&s=48)](https://github.com/onutc) [![ademczuk](https://avatars.githubusercontent.com/u/5212682?v=4&s=48)](https://github.com/ademczuk) [![ImLukeF](https://avatars.githubusercontent.com/u/92253590?v=4&s=48)](https://github.com/ImLukeF) [![hydro13](https://avatars.githubusercontent.com/u/6640526?v=4&s=48)](https://github.com/hydro13)
[![hxy91819](https://avatars.githubusercontent.com/u/8814856?v=4&s=48)](https://github.com/hxy91819) [![coygeek](https://avatars.githubusercontent.com/u/65363919?v=4&s=48)](https://github.com/coygeek) [![dutifulbob](https://avatars.githubusercontent.com/u/261991368?v=4&s=48)](https://github.com/dutifulbob) [![sliverp](https://avatars.githubusercontent.com/u/38134380?v=4&s=48)](https://github.com/sliverp) [![Elonito](https://avatars.githubusercontent.com/u/190923101?v=4&s=48)](https://github.com/0xRaini) [![robbyczgw-cla](https://avatars.githubusercontent.com/u/239660374?v=4&s=48)](https://github.com/robbyczgw-cla) [![joelnishanth](https://avatars.githubusercontent.com/u/140015627?v=4&s=48)](https://github.com/joelnishanth) [![echoVic](https://avatars.githubusercontent.com/u/16428813?v=4&s=48)](https://github.com/echoVic) [![sallyom](https://avatars.githubusercontent.com/u/11166065?v=4&s=48)](https://github.com/sallyom) [![yinghaosang](https://avatars.githubusercontent.com/u/261132136?v=4&s=48)](https://github.com/yinghaosang)
[![BradGroux](https://avatars.githubusercontent.com/u/3053586?v=4&s=48)](https://github.com/BradGroux) [![christianklotz](https://avatars.githubusercontent.com/u/69443?v=4&s=48)](https://github.com/christianklotz) [![odysseus0](https://avatars.githubusercontent.com/u/8635094?v=4&s=48)](https://github.com/odysseus0) [![hclsys](https://avatars.githubusercontent.com/u/7755017?v=4&s=48)](https://github.com/hclsys) [![byungsker](https://avatars.githubusercontent.com/u/72309817?v=4&s=48)](https://github.com/byungsker) [![pashpashpash](https://avatars.githubusercontent.com/u/20898225?v=4&s=48)](https://github.com/pashpashpash) [![stakeswky](https://avatars.githubusercontent.com/u/64798754?v=4&s=48)](https://github.com/stakeswky) [![github-actions[bot]](https://avatars.githubusercontent.com/in/15368?v=4&s=48)](https://github.com/apps/github-actions) [![xinhuagu](https://avatars.githubusercontent.com/u/562450?v=4&s=48)](https://github.com/xinhuagu) [![MonkeyLeeT](https://avatars.githubusercontent.com/u/6754057?v=4&s=48)](https://github.com/MonkeyLeeT)
[![100yenadmin](https://avatars.githubusercontent.com/u/239388517?v=4&s=48)](https://github.com/100yenadmin) [![mcinteerj](https://avatars.githubusercontent.com/u/3613653?v=4&s=48)](https://github.com/mcinteerj) [![samzong](https://avatars.githubusercontent.com/u/13782141?v=4&s=48)](https://github.com/samzong) [![chilu18](https://avatars.githubusercontent.com/u/7957943?v=4&s=48)](https://github.com/chilu18) [![darkamenosa](https://avatars.githubusercontent.com/u/6668014?v=4&s=48)](https://github.com/darkamenosa) [![widingmarcus-cyber](https://avatars.githubusercontent.com/u/245375637?v=4&s=48)](https://github.com/widingmarcus-cyber) [![cgdusek](https://avatars.githubusercontent.com/u/38732970?v=4&s=48)](https://github.com/cgdusek) [![Lukavyi](https://avatars.githubusercontent.com/u/1013690?v=4&s=48)](https://github.com/Lukavyi) [![davidrudduck](https://avatars.githubusercontent.com/u/47308254?v=4&s=48)](https://github.com/davidrudduck) [![VACInc](https://avatars.githubusercontent.com/u/3279061?v=4&s=48)](https://github.com/VACInc)
[![MoerAI](https://avatars.githubusercontent.com/u/26067127?v=4&s=48)](https://github.com/MoerAI) [![velvet-shark](https://avatars.githubusercontent.com/u/126378?v=4&s=48)](https://github.com/velvet-shark) [![HenryLoenwind](https://avatars.githubusercontent.com/u/1485873?v=4&s=48)](https://github.com/HenryLoenwind) [![omarshahine](https://avatars.githubusercontent.com/u/10343873?v=4&s=48)](https://github.com/omarshahine) [![bohdanpodvirnyi](https://avatars.githubusercontent.com/u/31819391?v=4&s=48)](https://github.com/bohdanpodvirnyi) [![Verite Igiraneza](https://avatars.githubusercontent.com/u/69280208?v=4&s=48)](https://github.com/VeriteIgiraneza) [![akramcodez](https://avatars.githubusercontent.com/u/179671552?v=4&s=48)](https://github.com/akramcodez) [![Kaneki-x](https://avatars.githubusercontent.com/u/6857108?v=4&s=48)](https://github.com/Kaneki-x) [![aether-ai-agent](https://avatars.githubusercontent.com/u/261339948?v=4&s=48)](https://github.com/aether-ai-agent) [![joaohlisboa](https://avatars.githubusercontent.com/u/8200873?v=4&s=48)](https://github.com/joaohlisboa)
[![MaudeBot](https://avatars.githubusercontent.com/u/255777700?v=4&s=48)](https://github.com/MaudeBot) [![davidguttman](https://avatars.githubusercontent.com/u/431696?v=4&s=48)](https://github.com/davidguttman) [![justinhuangcode](https://avatars.githubusercontent.com/u/252443740?v=4&s=48)](https://github.com/justinhuangcode) [![lml2468](https://avatars.githubusercontent.com/u/39320777?v=4&s=48)](https://github.com/lml2468) [![wirjo](https://avatars.githubusercontent.com/u/165846?v=4&s=48)](https://github.com/wirjo) [![iHildy](https://avatars.githubusercontent.com/u/25069719?v=4&s=48)](https://github.com/iHildy) [![mudrii](https://avatars.githubusercontent.com/u/220262?v=4&s=48)](https://github.com/mudrii) [![advaitpaliwal](https://avatars.githubusercontent.com/u/66044327?v=4&s=48)](https://github.com/advaitpaliwal) [![czekaj](https://avatars.githubusercontent.com/u/1464539?v=4&s=48)](https://github.com/czekaj) [![dlauer](https://avatars.githubusercontent.com/u/757041?v=4&s=48)](https://github.com/dlauer)
[![Solvely-Colin](https://avatars.githubusercontent.com/u/211764741?v=4&s=48)](https://github.com/Solvely-Colin) [![feiskyer](https://avatars.githubusercontent.com/u/676637?v=4&s=48)](https://github.com/feiskyer) [![brandonwise](https://avatars.githubusercontent.com/u/21148772?v=4&s=48)](https://github.com/brandonwise) [![conroywhitney](https://avatars.githubusercontent.com/u/249891?v=4&s=48)](https://github.com/conroywhitney) [![mneves75](https://avatars.githubusercontent.com/u/2423436?v=4&s=48)](https://github.com/mneves75) [![jaydenfyi](https://avatars.githubusercontent.com/u/213395523?v=4&s=48)](https://github.com/jaydenfyi) [![davemorin](https://avatars.githubusercontent.com/u/78139?v=4&s=48)](https://github.com/davemorin) [![joeykrug](https://avatars.githubusercontent.com/u/5925937?v=4&s=48)](https://github.com/joeykrug) [![kevinWangSheng](https://avatars.githubusercontent.com/u/118158941?v=4&s=48)](https://github.com/kevinWangSheng) [![pejmanjohn](https://avatars.githubusercontent.com/u/481729?v=4&s=48)](https://github.com/pejmanjohn)
[![Lanfei](https://avatars.githubusercontent.com/u/2156642?v=4&s=48)](https://github.com/Lanfei) [![liuy](https://avatars.githubusercontent.com/u/1192888?v=4&s=48)](https://github.com/liuy) [![lc0rp](https://avatars.githubusercontent.com/u/2609441?v=4&s=48)](https://github.com/lc0rp) [![teconomix](https://avatars.githubusercontent.com/u/6959299?v=4&s=48)](https://github.com/teconomix) [![omair445](https://avatars.githubusercontent.com/u/32237905?v=4&s=48)](https://github.com/omair445) [![dorukardahan](https://avatars.githubusercontent.com/u/35905596?v=4&s=48)](https://github.com/dorukardahan) [![mmaps](https://avatars.githubusercontent.com/u/3399869?v=4&s=48)](https://github.com/mmaps) [![Tobias Bischoff](https://avatars.githubusercontent.com/u/711564?v=4&s=48)](https://github.com/tobiasbischoff) [![adhitShet](https://avatars.githubusercontent.com/u/131381638?v=4&s=48)](https://github.com/adhitShet) [![pandego](https://avatars.githubusercontent.com/u/7780875?v=4&s=48)](https://github.com/pandego)
[![bradleypriest](https://avatars.githubusercontent.com/u/167215?v=4&s=48)](https://github.com/bradleypriest) [![bjesuiter](https://avatars.githubusercontent.com/u/2365676?v=4&s=48)](https://github.com/bjesuiter) [![grp06](https://avatars.githubusercontent.com/u/1573959?v=4&s=48)](https://github.com/grp06) [![shadril238](https://avatars.githubusercontent.com/u/63901551?v=4&s=48)](https://github.com/shadril238) [![kesku](https://avatars.githubusercontent.com/u/62210496?v=4&s=48)](https://github.com/kesku) [![YuriNachos](https://avatars.githubusercontent.com/u/19365375?v=4&s=48)](https://github.com/YuriNachos) [![vrknetha](https://avatars.githubusercontent.com/u/20596261?v=4&s=48)](https://github.com/vrknetha) [![smartprogrammer93](https://avatars.githubusercontent.com/u/33181301?v=4&s=48)](https://github.com/smartprogrammer93) [![nachx639](https://avatars.githubusercontent.com/u/71144023?v=4&s=48)](https://github.com/Nachx639) [![jnMetaCode](https://avatars.githubusercontent.com/u/12096460?v=4&s=48)](https://github.com/jnMetaCode)
[![Phineas1500](https://avatars.githubusercontent.com/u/41450967?v=4&s=48)](https://github.com/Phineas1500) [![dingn42](https://avatars.githubusercontent.com/u/17723822?v=4&s=48)](https://github.com/dingn42) [![geekhuashan](https://avatars.githubusercontent.com/u/47098938?v=4&s=48)](https://github.com/geekhuashan) [![Nanako0129](https://avatars.githubusercontent.com/u/44753291?v=4&s=48)](https://github.com/Nanako0129) [![AytuncYildizli](https://avatars.githubusercontent.com/u/47717026?v=4&s=48)](https://github.com/AytuncYildizli) [![BruceMacD](https://avatars.githubusercontent.com/u/5853428?v=4&s=48)](https://github.com/BruceMacD) [![jjjojoj](https://avatars.githubusercontent.com/u/88077783?v=4&s=48)](https://github.com/jjjojoj) [![mvanhorn](https://avatars.githubusercontent.com/u/455140?v=4&s=48)](https://github.com/mvanhorn) [![bugkill3r](https://avatars.githubusercontent.com/u/2924124?v=4&s=48)](https://github.com/bugkill3r) [![rahthakor](https://avatars.githubusercontent.com/u/8470553?v=4&s=48)](https://github.com/rahthakor)
[![GodsBoy](https://avatars.githubusercontent.com/u/5792287?v=4&s=48)](https://github.com/GodsBoy) [![SARAMALI15792](https://avatars.githubusercontent.com/u/140950904?v=4&s=48)](https://github.com/SARAMALI15792) [![Radek Paclt](https://avatars.githubusercontent.com/u/50451445?v=4&s=48)](https://github.com/radek-paclt) [![Elarwei001](https://avatars.githubusercontent.com/u/168552401?v=4&s=48)](https://github.com/Elarwei001) [![ingyukoh](https://avatars.githubusercontent.com/u/6015960?v=4&s=48)](https://github.com/ingyukoh) [![SnowSky1](https://avatars.githubusercontent.com/u/126348592?v=4&s=48)](https://github.com/SnowSky1) [![lewiswigmore](https://avatars.githubusercontent.com/u/58551848?v=4&s=48)](https://github.com/lewiswigmore) [![Hiroshi Tanaka](https://avatars.githubusercontent.com/u/145330217?v=4&s=48)](https://github.com/solavrc) [![aldoeliacim](https://avatars.githubusercontent.com/u/17973757?v=4&s=48)](https://github.com/aldoeliacim) [![Jakub Rusz](https://avatars.githubusercontent.com/u/55534579?v=4&s=48)](https://github.com/jrusz)
[![Tony Dehnke](https://avatars.githubusercontent.com/u/36720180?v=4&s=48)](https://github.com/tonydehnke) [![roshanasingh4](https://avatars.githubusercontent.com/u/88576930?v=4&s=48)](https://github.com/roshanasingh4) [![zssggle-rgb](https://avatars.githubusercontent.com/u/226775494?v=4&s=48)](https://github.com/zssggle-rgb) [![adam91holt](https://avatars.githubusercontent.com/u/9592417?v=4&s=48)](https://github.com/adam91holt) [![graysurf](https://avatars.githubusercontent.com/u/10785178?v=4&s=48)](https://github.com/graysurf) [![xadenryan](https://avatars.githubusercontent.com/u/165437834?v=4&s=48)](https://github.com/xadenryan) [![sfo2001](https://avatars.githubusercontent.com/u/103369858?v=4&s=48)](https://github.com/sfo2001) [![Jamieson O'Reilly](https://avatars.githubusercontent.com/u/6668807?v=4&s=48)](https://github.com/orlyjamie) [![hsrvc](https://avatars.githubusercontent.com/u/129702169?v=4&s=48)](https://github.com/hsrvc) [![tomsun28](https://avatars.githubusercontent.com/u/24788200?v=4&s=48)](https://github.com/tomsun28)
[![BillChirico](https://avatars.githubusercontent.com/u/13951316?v=4&s=48)](https://github.com/BillChirico) [![carrotRakko](https://avatars.githubusercontent.com/u/24588751?v=4&s=48)](https://github.com/carrotRakko) [![ranausmanai](https://avatars.githubusercontent.com/u/257128159?v=4&s=48)](https://github.com/ranausmanai) [![arkyu2077](https://avatars.githubusercontent.com/u/42494191?v=4&s=48)](https://github.com/arkyu2077) [![hoyyeva](https://avatars.githubusercontent.com/u/63033505?v=4&s=48)](https://github.com/hoyyeva) [![luoyanglang](https://avatars.githubusercontent.com/u/238804951?v=4&s=48)](https://github.com/luoyanglang) [![sibbl](https://avatars.githubusercontent.com/u/866535?v=4&s=48)](https://github.com/sibbl) [![gregmousseau](https://avatars.githubusercontent.com/u/5036458?v=4&s=48)](https://github.com/gregmousseau) [![sahilsatralkar](https://avatars.githubusercontent.com/u/62758655?v=4&s=48)](https://github.com/sahilsatralkar) [![akoscz](https://avatars.githubusercontent.com/u/1360047?v=4&s=48)](https://github.com/akoscz)
[![rrenamed](https://avatars.githubusercontent.com/u/87486610?v=4&s=48)](https://github.com/rrenamed) [![YuzuruS](https://avatars.githubusercontent.com/u/1485195?v=4&s=48)](https://github.com/YuzuruS) [![Hongwei Ma](https://avatars.githubusercontent.com/u/11957602?v=4&s=48)](https://github.com/Marvae) [![mitchmcalister](https://avatars.githubusercontent.com/u/209334?v=4&s=48)](https://github.com/mitchmcalister) [![juanpablodlc](https://avatars.githubusercontent.com/u/92012363?v=4&s=48)](https://github.com/juanpablodlc) [![shtse8](https://avatars.githubusercontent.com/u/8020099?v=4&s=48)](https://github.com/shtse8) [![thebenignhacker](https://avatars.githubusercontent.com/u/32418586?v=4&s=48)](https://github.com/thebenignhacker) [![nimbleenigma](https://avatars.githubusercontent.com/u/129692390?v=4&s=48)](https://github.com/nimbleenigma) [![Linux2010](https://avatars.githubusercontent.com/u/35169750?v=4&s=48)](https://github.com/Linux2010) [![shichangs](https://avatars.githubusercontent.com/u/46870204?v=4&s=48)](https://github.com/shichangs)
[![efe-arv](https://avatars.githubusercontent.com/u/259833796?v=4&s=48)](https://github.com/efe-arv) [![Hsiao A](https://avatars.githubusercontent.com/u/70124331?v=4&s=48)](https://github.com/hsiaoa) [![nabbilkhan](https://avatars.githubusercontent.com/u/203121263?v=4&s=48)](https://github.com/nabbilkhan) [![ayanesakura](https://avatars.githubusercontent.com/u/40628300?v=4&s=48)](https://github.com/ayanesakura) [![lupuletic](https://avatars.githubusercontent.com/u/105351510?v=4&s=48)](https://github.com/lupuletic) [![polooooo](https://avatars.githubusercontent.com/u/50262693?v=4&s=48)](https://github.com/polooooo) [![xaeon2026](https://avatars.githubusercontent.com/u/264572156?v=4&s=48)](https://github.com/xaeon2026) [![shrey150](https://avatars.githubusercontent.com/u/3813908?v=4&s=48)](https://github.com/shrey150) [![taw0002](https://avatars.githubusercontent.com/u/42811278?v=4&s=48)](https://github.com/taw0002) [![dinakars777](https://avatars.githubusercontent.com/u/250428393?v=4&s=48)](https://github.com/dinakars777)
[![giulio-leone](https://avatars.githubusercontent.com/u/6887247?v=4&s=48)](https://github.com/giulio-leone) [![nyanjou](https://avatars.githubusercontent.com/u/258645604?v=4&s=48)](https://github.com/nyanjou) [![meaningfool](https://avatars.githubusercontent.com/u/2862331?v=4&s=48)](https://github.com/meaningfool) [![kunalk16](https://avatars.githubusercontent.com/u/5303824?v=4&s=48)](https://github.com/kunalk16) [![ide-rea](https://avatars.githubusercontent.com/u/30512600?v=4&s=48)](https://github.com/ide-rea) [![Jonathan Jing](https://avatars.githubusercontent.com/u/17068507?v=4&s=48)](https://github.com/JonathanJing) [![yelog](https://avatars.githubusercontent.com/u/14227866?v=4&s=48)](https://github.com/yelog) [![markmusson](https://avatars.githubusercontent.com/u/4801649?v=4&s=48)](https://github.com/markmusson) [![kiranvk-2011](https://avatars.githubusercontent.com/u/91108465?v=4&s=48)](https://github.com/kiranvk-2011) [![Sathvik Veerapaneni](https://avatars.githubusercontent.com/u/98241593?v=4&s=48)](https://github.com/Sathvik-Chowdary-Veerapaneni)
[![rogerdigital](https://avatars.githubusercontent.com/u/13251150?v=4&s=48)](https://github.com/rogerdigital) [![artwalker](https://avatars.githubusercontent.com/u/44759507?v=4&s=48)](https://github.com/artwalker) [![azade-c](https://avatars.githubusercontent.com/u/252790079?v=4&s=48)](https://github.com/azade-c) [![chinar-amrutkar](https://avatars.githubusercontent.com/u/22189135?v=4&s=48)](https://github.com/chinar-amrutkar) [![maxsumrall](https://avatars.githubusercontent.com/u/628843?v=4&s=48)](https://github.com/maxsumrall) [![Minidoracat](https://avatars.githubusercontent.com/u/11269639?v=4&s=48)](https://github.com/Minidoracat) [![unisone](https://avatars.githubusercontent.com/u/32521398?v=4&s=48)](https://github.com/unisone) [![ly85206559](https://avatars.githubusercontent.com/u/12526624?v=4&s=48)](https://github.com/ly85206559) [![Sam Padilla](https://avatars.githubusercontent.com/u/35386211?v=4&s=48)](https://github.com/theSamPadilla) [![AnonO6](https://avatars.githubusercontent.com/u/124311066?v=4&s=48)](https://github.com/AnonO6)
[![afurm](https://avatars.githubusercontent.com/u/6375192?v=4&s=48)](https://github.com/afurm) [![황재원](https://avatars.githubusercontent.com/u/91544407?v=4&s=48)](https://github.com/jwchmodx) [![Leszek Szpunar](https://avatars.githubusercontent.com/u/13106764?v=4&s=48)](https://github.com/leszekszpunar) [![Mrseenz](https://avatars.githubusercontent.com/u/101962919?v=4&s=48)](https://github.com/Mrseenz) [![Yida-Dev](https://avatars.githubusercontent.com/u/92713555?v=4&s=48)](https://github.com/Yida-Dev) [![kesor](https://avatars.githubusercontent.com/u/7056?v=4&s=48)](https://github.com/kesor) [![mazhe-nerd](https://avatars.githubusercontent.com/u/106217973?v=4&s=48)](https://github.com/mazhe-nerd) [![Harald Buerbaumer](https://avatars.githubusercontent.com/u/44548809?v=4&s=48)](https://github.com/buerbaumer) [![magimetal](https://avatars.githubusercontent.com/u/36491250?v=4&s=48)](https://github.com/magimetal) [![Hiren Patel](https://avatars.githubusercontent.com/u/172098?v=4&s=48)](https://github.com/patelhiren)
[![BinHPdev](https://avatars.githubusercontent.com/u/219093083?v=4&s=48)](https://github.com/BinHPdev) [![RyanLee-Dev](https://avatars.githubusercontent.com/u/33855278?v=4&s=48)](https://github.com/RyanLee-Dev) [![cathrynlavery](https://avatars.githubusercontent.com/u/50469282?v=4&s=48)](https://github.com/cathrynlavery) [![al3mart](https://avatars.githubusercontent.com/u/11448715?v=4&s=48)](https://github.com/al3mart) [![JustYannicc](https://avatars.githubusercontent.com/u/52761674?v=4&s=48)](https://github.com/JustYannicc) [![abhisekbasu1](https://avatars.githubusercontent.com/u/40645221?v=4&s=48)](https://github.com/AbhisekBasu1) [![dbhurley](https://avatars.githubusercontent.com/u/5251425?v=4&s=48)](https://github.com/dbhurley) [![Kris Wu](https://avatars.githubusercontent.com/u/32388289?v=4&s=48)](https://github.com/mpz4life) [![tmimmanuel](https://avatars.githubusercontent.com/u/14046872?v=4&s=48)](https://github.com/tmimmanuel) [![JustasM](https://avatars.githubusercontent.com/u/59362982?v=4&s=48)](https://github.com/JustasMonkev)
[![Simantak Dabhade](https://avatars.githubusercontent.com/u/67303107?v=4&s=48)](https://github.com/simantak-dabhade) [![NicholasSpisak](https://avatars.githubusercontent.com/u/129075147?v=4&s=48)](https://github.com/NicholasSpisak) [![natefikru](https://avatars.githubusercontent.com/u/10344644?v=4&s=48)](https://github.com/natefikru) [![dunamismax](https://avatars.githubusercontent.com/u/65822992?v=4&s=48)](https://github.com/dunamismax) [![Simone Macario](https://avatars.githubusercontent.com/u/2116609?v=4&s=48)](https://github.com/simonemacario) [![ENCHIGO](https://avatars.githubusercontent.com/u/38551565?v=4&s=48)](https://github.com/ENCHIGO) [![xingsy97](https://avatars.githubusercontent.com/u/87063252?v=4&s=48)](https://github.com/xingsy97) [![emonty](https://avatars.githubusercontent.com/u/95156?v=4&s=48)](https://github.com/emonty) [![jadilson12](https://avatars.githubusercontent.com/u/36805474?v=4&s=48)](https://github.com/jadilson12) [![Yi-Cheng Wang](https://avatars.githubusercontent.com/u/80525895?v=4&s=48)](https://github.com/kirisame-wang)
[![Mathias Nagler](https://avatars.githubusercontent.com/u/9951231?v=4&s=48)](https://github.com/mathiasnagler) [![Sean McLellan](https://avatars.githubusercontent.com/u/760674?v=4&s=48)](https://github.com/Oceanswave) [![gumclaw](https://avatars.githubusercontent.com/u/265388744?v=4&s=48)](https://github.com/gumclaw) [![RichardCao](https://avatars.githubusercontent.com/u/4612401?v=4&s=48)](https://github.com/RichardCao) [![MKV21](https://avatars.githubusercontent.com/u/4974411?v=4&s=48)](https://github.com/MKV21) [![petter-b](https://avatars.githubusercontent.com/u/62076402?v=4&s=48)](https://github.com/petter-b) [![CodeForgeNet](https://avatars.githubusercontent.com/u/166907114?v=4&s=48)](https://github.com/CodeForgeNet) [![Johnson Shi](https://avatars.githubusercontent.com/u/13926417?v=4&s=48)](https://github.com/johnsonshi) [![durenzidu](https://avatars.githubusercontent.com/u/38130340?v=4&s=48)](https://github.com/durenzidu) [![dougvk](https://avatars.githubusercontent.com/u/401660?v=4&s=48)](https://github.com/dougvk)
[![Whoaa512](https://avatars.githubusercontent.com/u/1581943?v=4&s=48)](https://github.com/Whoaa512) [![zimeg](https://avatars.githubusercontent.com/u/18134219?v=4&s=48)](https://github.com/zimeg) [![Tseka Luk](https://avatars.githubusercontent.com/u/79151285?v=4&s=48)](https://github.com/TsekaLuk) [![Ryan Haines](https://avatars.githubusercontent.com/u/1855752?v=4&s=48)](https://github.com/Ryan-Haines) [![ufhy](https://avatars.githubusercontent.com/u/41638541?v=4&s=48)](https://github.com/uf-hy) [![Daan van der Plas](https://avatars.githubusercontent.com/u/93204684?v=4&s=48)](https://github.com/Daanvdplas) [![bittoby](https://avatars.githubusercontent.com/u/218712309?v=4&s=48)](https://github.com/bittoby) [![XuHao](https://avatars.githubusercontent.com/u/5087930?v=4&s=48)](https://github.com/xuhao1) [![Lucenx9](https://avatars.githubusercontent.com/u/185146821?v=4&s=48)](https://github.com/Lucenx9) [![HeMuling](https://avatars.githubusercontent.com/u/74801533?v=4&s=48)](https://github.com/HeMuling)
[![AaronLuo00](https://avatars.githubusercontent.com/u/112882500?v=4&s=48)](https://github.com/AaronLuo00) [![YUJIE2002](https://avatars.githubusercontent.com/u/123847463?v=4&s=48)](https://github.com/YUJIE2002) [![DhruvBhatia0](https://avatars.githubusercontent.com/u/69252327?v=4&s=48)](https://github.com/DhruvBhatia0) [![Divanoli Mydeen Pitchai](https://avatars.githubusercontent.com/u/12023205?v=4&s=48)](https://github.com/divanoli) [![Bronko](https://avatars.githubusercontent.com/u/2217509?v=4&s=48)](https://github.com/derbronko) [![rubyrunsstuff](https://avatars.githubusercontent.com/u/246602379?v=4&s=48)](https://github.com/rubyrunsstuff) [![rabsef-bicrym](https://avatars.githubusercontent.com/u/52549148?v=4&s=48)](https://github.com/rabsef-bicrym) [![IVY-AI-gif](https://avatars.githubusercontent.com/u/62232838?v=4&s=48)](https://github.com/IVY-AI-gif) [![pvtclawn](https://avatars.githubusercontent.com/u/258811507?v=4&s=48)](https://github.com/pvtclawn) [![stephenschoettler](https://avatars.githubusercontent.com/u/7587303?v=4&s=48)](https://github.com/stephenschoettler)
[![Dale Babiy](https://avatars.githubusercontent.com/u/42547246?v=4&s=48)](https://github.com/minupla) [![LeftX](https://avatars.githubusercontent.com/u/53989315?v=4&s=48)](https://github.com/xzq-xu) [![David Gelberg](https://avatars.githubusercontent.com/u/57605064?v=4&s=48)](https://github.com/mousberg) [![Engr. Arif Ahmed Joy](https://avatars.githubusercontent.com/u/4543396?v=4&s=48)](https://github.com/arifahmedjoy) [![Masataka Shinohara](https://avatars.githubusercontent.com/u/11906529?v=4&s=48)](https://github.com/harhogefoo) [![2233admin](https://avatars.githubusercontent.com/u/57929895?v=4&s=48)](https://github.com/2233admin) [![ameno-](https://avatars.githubusercontent.com/u/2416135?v=4&s=48)](https://github.com/ameno-) [![battman21](https://avatars.githubusercontent.com/u/2656916?v=4&s=48)](https://github.com/battman21) [![bcherny](https://avatars.githubusercontent.com/u/1761758?v=4&s=48)](https://github.com/bcherny) [![bobashopcashier](https://avatars.githubusercontent.com/u/77253505?v=4&s=48)](https://github.com/bobashopcashier)
[![dguido](https://avatars.githubusercontent.com/u/294844?v=4&s=48)](https://github.com/dguido) [![druide67](https://avatars.githubusercontent.com/u/212749853?v=4&s=48)](https://github.com/druide67) [![guirguispierre](https://avatars.githubusercontent.com/u/22091706?v=4&s=48)](https://github.com/guirguispierre) [![jzakirov](https://avatars.githubusercontent.com/u/15848838?v=4&s=48)](https://github.com/jzakirov) [![loganprit](https://avatars.githubusercontent.com/u/72722788?v=4&s=48)](https://github.com/loganprit) [![martinfrancois](https://avatars.githubusercontent.com/u/14319020?v=4&s=48)](https://github.com/martinfrancois) [![neo1027144-creator](https://avatars.githubusercontent.com/u/267440006?v=4&s=48)](https://github.com/neo1027144-creator) [![RealKai42](https://avatars.githubusercontent.com/u/44634134?v=4&s=48)](https://github.com/RealKai42) [![schumilin](https://avatars.githubusercontent.com/u/2003498?v=4&s=48)](https://github.com/schumilin) [![shuofengzhang](https://avatars.githubusercontent.com/u/24763026?v=4&s=48)](https://github.com/shuofengzhang)
[![solstead](https://avatars.githubusercontent.com/u/168413654?v=4&s=48)](https://github.com/solstead) [![hengm3467](https://avatars.githubusercontent.com/u/100685635?v=4&s=48)](https://github.com/hengm3467) [![chziyue](https://avatars.githubusercontent.com/u/62380760?v=4&s=48)](https://github.com/chziyue) [![James L. Cowan Jr.](https://avatars.githubusercontent.com/u/112015792?v=4&s=48)](https://github.com/jameslcowan) [![scifantastic](https://avatars.githubusercontent.com/u/150712374?v=4&s=48)](https://github.com/scifantastic) [![ryan-crabbe](https://avatars.githubusercontent.com/u/128659760?v=4&s=48)](https://github.com/ryan-crabbe) [![alexfilatov](https://avatars.githubusercontent.com/u/138589?v=4&s=48)](https://github.com/alexfilatov) [![Luckymingxuan](https://avatars.githubusercontent.com/u/159552597?v=4&s=48)](https://github.com/Luckymingxuan) [![HollyChou](https://avatars.githubusercontent.com/u/128659251?v=4&s=48)](https://github.com/Hollychou924) [![badlogic](https://avatars.githubusercontent.com/u/514052?v=4&s=48)](https://github.com/badlogic)
[![Daniel Hnyk](https://avatars.githubusercontent.com/u/2741256?v=4&s=48)](https://github.com/hnykda) [![dan bachelder](https://avatars.githubusercontent.com/u/325706?v=4&s=48)](https://github.com/dbachelder) [![heavenlost](https://avatars.githubusercontent.com/u/70937055?v=4&s=48)](https://github.com/heavenlost) [![shad0wca7](https://avatars.githubusercontent.com/u/9969843?v=4&s=48)](https://github.com/shad0wca7) [![Jared](https://avatars.githubusercontent.com/u/37019497?v=4&s=48)](https://github.com/jared596) [![kiranjd](https://avatars.githubusercontent.com/u/25822851?v=4&s=48)](https://github.com/kiranjd) [![Mars](https://avatars.githubusercontent.com/u/40958792?v=4&s=48)](https://github.com/Mellowambience) [![Kim](https://avatars.githubusercontent.com/u/150593189?v=4&s=48)](https://github.com/KimGLee) [![seheepeak](https://avatars.githubusercontent.com/u/134766597?v=4&s=48)](https://github.com/seheepeak) [![tsavo](https://avatars.githubusercontent.com/u/877990?v=4&s=48)](https://github.com/TSavo)
[![McRolly NWANGWU](https://avatars.githubusercontent.com/u/60803337?v=4&s=48)](https://github.com/mcrolly) [![dashed](https://avatars.githubusercontent.com/u/139499?v=4&s=48)](https://github.com/dashed) [![Shuai-DaiDai](https://avatars.githubusercontent.com/u/134567396?v=4&s=48)](https://github.com/Shuai-DaiDai) [![Subash Natarajan](https://avatars.githubusercontent.com/u/11032439?v=4&s=48)](https://github.com/suboss87) [![emanuelst](https://avatars.githubusercontent.com/u/9994339?v=4&s=48)](https://github.com/emanuelst) [![magendary](https://avatars.githubusercontent.com/u/30611068?v=4&s=48)](https://github.com/magendary) [![LI SHANXIN](https://avatars.githubusercontent.com/u/128674037?v=4&s=48)](https://github.com/PeterShanxin) [![j2h4u](https://avatars.githubusercontent.com/u/39818683?v=4&s=48)](https://github.com/j2h4u) [![bsormagec](https://avatars.githubusercontent.com/u/965219?v=4&s=48)](https://github.com/bsormagec) [![mjamiv](https://avatars.githubusercontent.com/u/142179942?v=4&s=48)](https://github.com/mjamiv)
[![Lalit Singh](https://avatars.githubusercontent.com/u/17166039?v=4&s=48)](https://github.com/aerolalit) [![Jessy LANGE](https://avatars.githubusercontent.com/u/89694096?v=4&s=48)](https://github.com/jessy2027) [![buddyh](https://avatars.githubusercontent.com/u/31752869?v=4&s=48)](https://github.com/buddyh) [![Aaron Zhu](https://avatars.githubusercontent.com/u/139607425?v=4&s=48)](https://github.com/aaron-he-zhu) [![F_ool](https://avatars.githubusercontent.com/u/112874572?v=4&s=48)](https://github.com/hhhhao28) [![Ben Stein](https://avatars.githubusercontent.com/u/31802821?v=4&s=48)](https://github.com/benostein) [![Lyle](https://avatars.githubusercontent.com/u/31182860?v=4&s=48)](https://github.com/LyleLiu666) [![Ping](https://avatars.githubusercontent.com/u/5123601?v=4&s=48)](https://github.com/pingren) [![popomore](https://avatars.githubusercontent.com/u/360661?v=4&s=48)](https://github.com/popomore) [![Dithilli](https://avatars.githubusercontent.com/u/41286037?v=4&s=48)](https://github.com/Dithilli)
[![fal3](https://avatars.githubusercontent.com/u/6484295?v=4&s=48)](https://github.com/fal3) [![mkbehr](https://avatars.githubusercontent.com/u/1285?v=4&s=48)](https://github.com/mkbehr) [![mteam88](https://avatars.githubusercontent.com/u/84196639?v=4&s=48)](https://github.com/mteam88) [![gupsammy](https://avatars.githubusercontent.com/u/20296019?v=4&s=48)](https://github.com/gupsammy) [![Shailesh](https://avatars.githubusercontent.com/u/75851986?v=4&s=48)](https://github.com/gut-puncture) [![Garnet Liu](https://avatars.githubusercontent.com/u/12513503?v=4&s=48)](https://github.com/garnetlyx) [![Thorfinn](https://avatars.githubusercontent.com/u/136994453?v=4&s=48)](https://github.com/miloudbelarebia) [![Protocol-zero-0](https://avatars.githubusercontent.com/u/257158451?v=4&s=48)](https://github.com/Protocol-zero-0) [![Paul van Oorschot](https://avatars.githubusercontent.com/u/20116814?v=4&s=48)](https://github.com/pvoo) [![Patrick Yingxi Pan](https://avatars.githubusercontent.com/u/5210631?v=4&s=48)](https://github.com/patrick-yingxi-pan)
[![Ptah.ai](https://avatars.githubusercontent.com/u/11701?v=4&s=48)](https://github.com/ptahdunbar) [![정우용](https://avatars.githubusercontent.com/u/71975659?v=4&s=48)](https://github.com/keepitmello) [![artuskg](https://avatars.githubusercontent.com/u/11966157?v=4&s=48)](https://github.com/artuskg) [![Anandesh-Sharma](https://avatars.githubusercontent.com/u/30695364?v=4&s=48)](https://github.com/Anandesh-Sharma) [![zidongdesign](https://avatars.githubusercontent.com/u/81469543?v=4&s=48)](https://github.com/zidongdesign) [![innocent-children](https://avatars.githubusercontent.com/u/55626758?v=4&s=48)](https://github.com/Innocent-children) [![El-Fitz](https://avatars.githubusercontent.com/u/8971906?v=4&s=48)](https://github.com/El-Fitz) [![arthurbr11](https://avatars.githubusercontent.com/u/99079981?v=4&s=48)](https://github.com/arthurbr11) [![jackheuberger](https://avatars.githubusercontent.com/u/7830838?v=4&s=48)](https://github.com/jackheuberger) [![Sergiusz](https://avatars.githubusercontent.com/u/6172067?v=4&s=48)](https://github.com/serkonyc)
[![Xu Gu](https://avatars.githubusercontent.com/u/53551744?v=4&s=48)](https://github.com/guxu11) [![hyojin](https://avatars.githubusercontent.com/u/3413183?v=4&s=48)](https://github.com/hyojin) [![jeann2013](https://avatars.githubusercontent.com/u/3299025?v=4&s=48)](https://github.com/jeann2013) [![jogelin](https://avatars.githubusercontent.com/u/954509?v=4&s=48)](https://github.com/jogelin) [![rmorse](https://avatars.githubusercontent.com/u/853547?v=4&s=48)](https://github.com/rmorse) [![scz2011](https://avatars.githubusercontent.com/u/9337506?v=4&s=48)](https://github.com/scz2011) [![Andyliu](https://avatars.githubusercontent.com/u/2377291?v=4&s=48)](https://github.com/andyliu) [![benithors](https://avatars.githubusercontent.com/u/20652882?v=4&s=48)](https://github.com/benithors) [![xiwuqi](https://avatars.githubusercontent.com/u/64734786?v=4&s=48)](https://github.com/xiwuqi) [![Alvin](https://avatars.githubusercontent.com/u/48358093?v=4&s=48)](https://github.com/TigerInYourDream)
[![AARON AGENT](https://avatars.githubusercontent.com/u/78432083?v=4&s=48)](https://github.com/aaronagent) [![Derek YU](https://avatars.githubusercontent.com/u/154693526?v=4&s=48)](https://github.com/TonyDerek-dot) [![Marvin](https://avatars.githubusercontent.com/u/43185740?v=4&s=48)](https://github.com/Zitzak) [![Andrew Jeon](https://avatars.githubusercontent.com/u/46941315?v=4&s=48)](https://github.com/ruypang) [![stain lu](https://avatars.githubusercontent.com/u/109842185?v=4&s=48)](https://github.com/stainlu) [![OpenCils](https://avatars.githubusercontent.com/u/114985039?v=4&s=48)](https://github.com/OpenCils) [![Stefan Galescu](https://avatars.githubusercontent.com/u/52995748?v=4&s=48)](https://github.com/stefangalescu) [![SP](https://avatars.githubusercontent.com/u/8068616?v=4&s=48)](https://github.com/sp-hk2ldn) [![Michael Flanagan](https://avatars.githubusercontent.com/u/39276573?v=4&s=48)](https://github.com/MikeORed) [![Gracie Gould](https://avatars.githubusercontent.com/u/66045258?v=4&s=48)](https://github.com/graciegould)
[![cash-echo-bot](https://avatars.githubusercontent.com/u/252747386?v=4&s=48)](https://github.com/cash-echo-bot) [![visionik](https://avatars.githubusercontent.com/u/52174?v=4&s=48)](https://github.com/visionik) [![WalterSumbon](https://avatars.githubusercontent.com/u/45062253?v=4&s=48)](https://github.com/WalterSumbon) [![huangcj](https://avatars.githubusercontent.com/u/43933609?v=4&s=48)](https://github.com/SubtleSpark) [![krizpoon](https://avatars.githubusercontent.com/u/1977532?v=4&s=48)](https://github.com/krizpoon) [![rodbland2021](https://avatars.githubusercontent.com/u/86267410?v=4&s=48)](https://github.com/rodbland2021) [![Thomas M](https://avatars.githubusercontent.com/u/44269971?v=4&s=48)](https://github.com/thomasxm) [![sar618](https://avatars.githubusercontent.com/u/214745104?v=4&s=48)](https://github.com/sar618) [![fagemx](https://avatars.githubusercontent.com/u/117356295?v=4&s=48)](https://github.com/fagemx) [![daymade](https://avatars.githubusercontent.com/u/4291901?v=4&s=48)](https://github.com/daymade)
[![Tyson Cung](https://avatars.githubusercontent.com/u/45380903?v=4&s=48)](https://github.com/tysoncung) [![Igor Markelov](https://avatars.githubusercontent.com/u/1489583?v=4&s=48)](https://github.com/pycckuu) [![Eng. Juan Combetto](https://avatars.githubusercontent.com/u/322761?v=4&s=48)](https://github.com/omniwired) [![connorshea](https://avatars.githubusercontent.com/u/2977353?v=4&s=48)](https://github.com/connorshea) [![bonald](https://avatars.githubusercontent.com/u/12394874?v=4&s=48)](https://github.com/bonald) [![Keenan](https://avatars.githubusercontent.com/u/85285887?v=4&s=48)](https://github.com/BeeSting50) [![nachoiacovino](https://avatars.githubusercontent.com/u/50103937?v=4&s=48)](https://github.com/nachoiacovino) [![zhumengzhu](https://avatars.githubusercontent.com/u/4508623?v=4&s=48)](https://github.com/zhumengzhu) [![Amine Harch el korane](https://avatars.githubusercontent.com/u/95189778?v=4&s=48)](https://github.com/Vitalcheffe) [![zhoulc777](https://avatars.githubusercontent.com/u/65058500?v=4&s=48)](https://github.com/zhoulongchao77)
[![Alex Navarro](https://avatars.githubusercontent.com/u/78754189?v=4&s=48)](https://github.com/navarrotech) [![Tanwa Arpornthip](https://avatars.githubusercontent.com/u/72845369?v=4&s=48)](https://github.com/CommanderCrowCode) [![TIHU](https://avatars.githubusercontent.com/u/44923937?v=4&s=48)](https://github.com/paceyw) [![Aftabbs](https://avatars.githubusercontent.com/u/112916888?v=4&s=48)](https://github.com/Aftabbs) [![Alex-Alaniz](https://avatars.githubusercontent.com/u/88956822?v=4&s=48)](https://github.com/Alex-Alaniz) [![jarvis-medmatic](https://avatars.githubusercontent.com/u/252428873?v=4&s=48)](https://github.com/jarvis-medmatic) [![Tom Ron](https://avatars.githubusercontent.com/u/126325152?v=4&s=48)](https://github.com/tomron87) [![day253](https://avatars.githubusercontent.com/u/9634619?v=4&s=48)](https://github.com/day253) [![Jaaneek](https://avatars.githubusercontent.com/u/25470423?v=4&s=48)](https://github.com/Jaaneek) [![Justin Song](https://avatars.githubusercontent.com/u/32268203?v=4&s=48)](https://github.com/AnCoSONG)
[![ziomancer](https://avatars.githubusercontent.com/u/262232137?v=4&s=48)](https://github.com/ziomancer) [![shayan919293](https://avatars.githubusercontent.com/u/60409704?v=4&s=48)](https://github.com/shayan919293) [![Edward](https://avatars.githubusercontent.com/u/53964601?v=4&s=48)](https://github.com/edwluo) [![Roger Chien](https://avatars.githubusercontent.com/u/20276663?v=4&s=48)](https://github.com/rjchien728) [![Michael Lee](https://avatars.githubusercontent.com/u/5957298?v=4&s=48)](https://github.com/TinyTb) [![Tomáš Dinh](https://avatars.githubusercontent.com/u/82420070?v=4&s=48)](https://github.com/No898) [![Ian Derrington](https://avatars.githubusercontent.com/u/76016868?v=4&s=48)](https://github.com/ianderrington) [![Lucky](https://avatars.githubusercontent.com/u/14868134?v=4&s=48)](https://github.com/L-U-C-K-Y) [![peschee](https://avatars.githubusercontent.com/u/63866?v=4&s=48)](https://github.com/peschee) [![Harry Cui Kepler](https://avatars.githubusercontent.com/u/166882517?v=4&s=48)](https://github.com/Kepler2024)
[![julianengel](https://avatars.githubusercontent.com/u/10634231?v=4&s=48)](https://github.com/julianengel) [![markfietje](https://avatars.githubusercontent.com/u/4325889?v=4&s=48)](https://github.com/markfietje) [![Dakshay Mehta](https://avatars.githubusercontent.com/u/50276213?v=4&s=48)](https://github.com/dakshaymehta) [![TheRipper](https://avatars.githubusercontent.com/u/144421782?v=4&s=48)](https://github.com/DavidNitZ) [![Dominic](https://avatars.githubusercontent.com/u/43616264?v=4&s=48)](https://github.com/dominicnunez) [![danielwanwx](https://avatars.githubusercontent.com/u/144515713?v=4&s=48)](https://github.com/danielwanwx) [![Seungwoo hong](https://avatars.githubusercontent.com/u/1100974?v=4&s=48)](https://github.com/hongsw) [![Youyou972](https://avatars.githubusercontent.com/u/50808411?v=4&s=48)](https://github.com/Youyou972) [![boris721](https://avatars.githubusercontent.com/u/257853888?v=4&s=48)](https://github.com/boris721) [![damoahdominic](https://avatars.githubusercontent.com/u/4623434?v=4&s=48)](https://github.com/damoahdominic)
[![dan-dr](https://avatars.githubusercontent.com/u/6669808?v=4&s=48)](https://github.com/dan-dr) [![doodlewind](https://avatars.githubusercontent.com/u/7312949?v=4&s=48)](https://github.com/doodlewind) [![kkarimi](https://avatars.githubusercontent.com/u/875218?v=4&s=48)](https://github.com/kkarimi) [![brokemac79](https://avatars.githubusercontent.com/u/255583030?v=4&s=48)](https://github.com/brokemac79) [![ozbillwang](https://avatars.githubusercontent.com/u/8954908?v=4&s=48)](https://github.com/ozbillwang) [![Ravish Gupta](https://avatars.githubusercontent.com/u/1249023?v=4&s=48)](https://github.com/ravyg) [![Jason Hargrove](https://avatars.githubusercontent.com/u/285708?v=4&s=48)](https://github.com/jasonhargrove) [![BrianWang1990](https://avatars.githubusercontent.com/u/20699847?v=4&s=48)](https://github.com/BrianWang1990) [![Joshua McKiddy](https://avatars.githubusercontent.com/u/43189238?v=4&s=48)](https://github.com/hackersifu) [![Fologan](https://avatars.githubusercontent.com/u/164580328?v=4&s=48)](https://github.com/Fologan)
[![Anonymous Amit](https://avatars.githubusercontent.com/u/134582556?v=4&s=48)](https://github.com/AnonAmit) [![v1p0r](https://avatars.githubusercontent.com/u/25909990?v=4&s=48)](https://github.com/v1p0r) [![Ajay Elika](https://avatars.githubusercontent.com/u/73169130?v=4&s=48)](https://github.com/ajay99511) [![Iranb](https://avatars.githubusercontent.com/u/49674669?v=4&s=48)](https://github.com/Iranb) [![Yonatan](https://avatars.githubusercontent.com/u/10474956?v=4&s=48)](https://github.com/yhyatt) [![codexGW](https://avatars.githubusercontent.com/u/9350182?v=4&s=48)](https://github.com/codexGW) [![Shaun Tsai](https://avatars.githubusercontent.com/u/13811075?v=4&s=48)](https://github.com/ShaunTsai) [![TideFinder](https://avatars.githubusercontent.com/u/68721273?v=4&s=48)](https://github.com/papago2355) [![Chase Dorsey](https://avatars.githubusercontent.com/u/12650570?v=4&s=48)](https://github.com/cdorsey) [![tda](https://avatars.githubusercontent.com/u/95275462?v=4&s=48)](https://github.com/tda1017)
[![0xJonHoldsCrypto](https://avatars.githubusercontent.com/u/81202085?v=4&s=48)](https://github.com/0xJonHoldsCrypto) [![akyourowngames](https://avatars.githubusercontent.com/u/123736861?v=4&s=48)](https://github.com/akyourowngames) [![clawdinator[bot]](https://avatars.githubusercontent.com/in/2607181?v=4&s=48)](https://github.com/apps/clawdinator) [![koala73](https://avatars.githubusercontent.com/u/996596?v=4&s=48)](https://github.com/koala73) [![sircrumpet](https://avatars.githubusercontent.com/u/4436535?v=4&s=48)](https://github.com/sircrumpet) [![thesomewhatyou](https://avatars.githubusercontent.com/u/162917831?v=4&s=48)](https://github.com/thesomewhatyou) [![zats](https://avatars.githubusercontent.com/u/2688806?v=4&s=48)](https://github.com/zats) [![Accunza](https://avatars.githubusercontent.com/u/12242811?v=4&s=48)](https://github.com/duqaXxX) [![Joly0](https://avatars.githubusercontent.com/u/13993216?v=4&s=48)](https://github.com/Joly0) [![Hanna](https://avatars.githubusercontent.com/u/4538260?v=4&s=48)](https://github.com/hannasdev)
[![Jeremiah Lowin](https://avatars.githubusercontent.com/u/153965?v=4&s=48)](https://github.com/jlowin) [![peetzweg/](https://avatars.githubusercontent.com/u/839848?v=4&s=48)](https://github.com/peetzweg) [![Skyler Miao](https://avatars.githubusercontent.com/u/153898832?v=4&s=48)](https://github.com/adao-max) [![tumf](https://avatars.githubusercontent.com/u/69994?v=4&s=48)](https://github.com/tumf) [![Hiago Silva](https://avatars.githubusercontent.com/u/97215740?v=4&s=48)](https://github.com/Huntterxx) [![Nate](https://avatars.githubusercontent.com/u/12980165?v=4&s=48)](https://github.com/nk1tz) [![lidamao633](https://avatars.githubusercontent.com/u/94925404?v=4&s=48)](https://github.com/lidamao633) [![Cklee](https://avatars.githubusercontent.com/u/99405438?v=4&s=48)](https://github.com/liebertar) [![CornBrother0x](https://avatars.githubusercontent.com/u/101160087?v=4&s=48)](https://github.com/CornBrother0x) [![DukeDeSouth](https://avatars.githubusercontent.com/u/51200688?v=4&s=48)](https://github.com/DukeDeSouth)
[![Sahan](https://avatars.githubusercontent.com/u/57447079?v=4&s=48)](https://github.com/sahancava) [![CashWilliams](https://avatars.githubusercontent.com/u/613573?v=4&s=48)](https://github.com/CashWilliams) [![Felix Lu](https://avatars.githubusercontent.com/u/58391009?v=4&s=48)](https://github.com/lumpinif) [![AdeboyeDN](https://avatars.githubusercontent.com/u/65312338?v=4&s=48)](https://github.com/AdeboyeDN) [![Rohan Santhosh Kumar](https://avatars.githubusercontent.com/u/181558744?v=4&s=48)](https://github.com/Rohan5commit) [![Srinivas Pavan](https://avatars.githubusercontent.com/u/34889400?v=4&s=48)](https://github.com/srinivaspavan9) [![h0tp](https://avatars.githubusercontent.com/u/141889580?v=4&s=48)](https://github.com/h0tp-ftw) [![Neo](https://avatars.githubusercontent.com/u/54811660?v=4&s=48)](https://github.com/neooriginal) [![Tianworld](https://avatars.githubusercontent.com/u/40754565?v=4&s=48)](https://github.com/Tianworld) [![neverland](https://avatars.githubusercontent.com/u/10937319?v=4&s=48)](https://github.com/Bermudarat)
[![asklee-klawd](https://avatars.githubusercontent.com/u/105007315?v=4&s=48)](https://github.com/asklee-klawd) [![Yuting Lin](https://avatars.githubusercontent.com/u/32728916?v=4&s=48)](https://github.com/yuting0624) [![constansino](https://avatars.githubusercontent.com/u/65108260?v=4&s=48)](https://github.com/constansino) [![ghsmc](https://avatars.githubusercontent.com/u/68118719?v=4&s=48)](https://github.com/ghsmc) [![ibrahimq21](https://avatars.githubusercontent.com/u/8392472?v=4&s=48)](https://github.com/ibrahimq21) [![irtiq7](https://avatars.githubusercontent.com/u/3823029?v=4&s=48)](https://github.com/irtiq7) [![kelvinCB](https://avatars.githubusercontent.com/u/50544379?v=4&s=48)](https://github.com/kelvinCB) [![mitsuhiko](https://avatars.githubusercontent.com/u/7396?v=4&s=48)](https://github.com/mitsuhiko) [![nohat](https://avatars.githubusercontent.com/u/838027?v=4&s=48)](https://github.com/nohat) [![santiagomed](https://avatars.githubusercontent.com/u/30184543?v=4&s=48)](https://github.com/santiagomed)
[![suminhthanh](https://avatars.githubusercontent.com/u/2907636?v=4&s=48)](https://github.com/suminhthanh) [![svkozak](https://avatars.githubusercontent.com/u/31941359?v=4&s=48)](https://github.com/svkozak) [![张哲芳](https://avatars.githubusercontent.com/u/34058239?v=4&s=48)](https://github.com/zhangzhefang-github) [![Ho Lim](https://avatars.githubusercontent.com/u/166576253?v=4&s=48)](https://github.com/HOYALIM) [![Toven](https://avatars.githubusercontent.com/u/69218856?v=4&s=48)](https://github.com/ping-Toven) [![R. Desmond](https://avatars.githubusercontent.com/u/134018026?v=4&s=48)](https://github.com/0-CYBERDYNE-SYSTEMS-0) [![游乐场](https://avatars.githubusercontent.com/u/79438767?v=4&s=48)](https://github.com/ylc0919) [![Reed](https://avatars.githubusercontent.com/u/129141816?v=4&s=48)](https://github.com/reed1898) [![Aditya Chaudhary](https://avatars.githubusercontent.com/u/55331140?v=4&s=48)](https://github.com/ItsAditya-xyz) [![Sam](https://avatars.githubusercontent.com/u/14844597?v=4&s=48)](https://github.com/samrusani)
[![Andy](https://avatars.githubusercontent.com/u/91510251?v=4&s=48)](https://github.com/andyk-ms) [![Rajat Joshi](https://avatars.githubusercontent.com/u/78920780?v=4&s=48)](https://github.com/18-RAJAT) [![cyb1278588254](https://avatars.githubusercontent.com/u/48212932?v=4&s=48)](https://github.com/cyb1278588254) [![Zoher Ghadyali](https://avatars.githubusercontent.com/u/34316555?v=4&s=48)](https://github.com/zoherghadyali) [![Manik Vahsith](https://avatars.githubusercontent.com/u/49544491?v=4&s=48)](https://github.com/manikv12) [![tarouca](https://avatars.githubusercontent.com/u/36767065?v=4&s=48)](https://github.com/manueltarouca) [![MrBrain](https://avatars.githubusercontent.com/u/176294248?v=4&s=48)](https://github.com/GaosCode) [![Daniel Zou](https://avatars.githubusercontent.com/u/12799392?v=4&s=48)](https://github.com/pahdo) [![Lilo](https://avatars.githubusercontent.com/u/1622461?v=4&s=48)](https://github.com/detecti1) [![Jason](https://avatars.githubusercontent.com/u/101583541?v=4&s=48)](https://github.com/JasonOA888)
[![SUMUKH](https://avatars.githubusercontent.com/u/130692934?v=4&s=48)](https://github.com/sumukhj1219) [![Bakhtier Sizhaev](https://avatars.githubusercontent.com/u/108124494?v=4&s=48)](https://github.com/bakhtiersizhaev) [![Ganghyun Kim](https://avatars.githubusercontent.com/u/58307870?v=4&s=48)](https://github.com/kyleok) [![AkashKobal](https://avatars.githubusercontent.com/u/98216083?v=4&s=48)](https://github.com/AkashKobal) [![Brian](https://avatars.githubusercontent.com/u/95547369?v=4&s=48)](https://github.com/zhuisDEV) [![wu-tian807](https://avatars.githubusercontent.com/u/61640083?v=4&s=48)](https://github.com/wu-tian807) [![Vasanth Rao Naik Sabavat](https://avatars.githubusercontent.com/u/50385532?v=4&s=48)](https://github.com/vsabavat) [![Kinfey](https://avatars.githubusercontent.com/u/93169410?v=4&s=48)](https://github.com/kinfey) [![Artemii](https://avatars.githubusercontent.com/u/35071559?v=4&s=48)](https://github.com/crimeacs) [![VibhorGautam](https://avatars.githubusercontent.com/u/55019395?v=4&s=48)](https://github.com/VibhorGautam)
[![John Rood](https://avatars.githubusercontent.com/u/62669593?v=4&s=48)](https://github.com/John-Rood) [![velamints2](https://avatars.githubusercontent.com/u/93711796?v=4&s=48)](https://github.com/velamints2) [![Benji Peng](https://avatars.githubusercontent.com/u/11394934?v=4&s=48)](https://github.com/benjipeng) [![JINNYEONG KIM](https://avatars.githubusercontent.com/u/41609506?v=4&s=48)](https://github.com/divisonofficer) [![Rahul kumar Pal](https://avatars.githubusercontent.com/u/151990777?v=4&s=48)](https://github.com/Rahulkumar070) [![Rockcent](https://avatars.githubusercontent.com/u/128210877?v=4&s=48)](https://github.com/rockcent) [![Limitless](https://avatars.githubusercontent.com/u/127183162?v=4&s=48)](https://github.com/Limitless2023) [![24601](https://avatars.githubusercontent.com/u/1157207?v=4&s=48)](https://github.com/24601) [![awkoy](https://avatars.githubusercontent.com/u/13995636?v=4&s=48)](https://github.com/awkoy) [![dawondyifraw](https://avatars.githubusercontent.com/u/9797257?v=4&s=48)](https://github.com/dawondyifraw)
[![google-labs-jules[bot]](https://avatars.githubusercontent.com/in/842251?v=4&s=48)](https://github.com/apps/google-labs-jules) [![henrino3](https://avatars.githubusercontent.com/u/4260288?v=4&s=48)](https://github.com/henrino3) [![Kansodata](https://avatars.githubusercontent.com/u/225288021?v=4&s=48)](https://github.com/Kansodata) [![kaonash](https://avatars.githubusercontent.com/u/7535663?v=4&s=48)](https://github.com/kaonash) [![p6l-richard](https://avatars.githubusercontent.com/u/18185649?v=4&s=48)](https://github.com/p6l-richard) [![pi0](https://avatars.githubusercontent.com/u/5158436?v=4&s=48)](https://github.com/pi0) [![skainguyen1412](https://avatars.githubusercontent.com/u/14249881?v=4&s=48)](https://github.com/skainguyen1412) [![Starhappysh](https://avatars.githubusercontent.com/u/221244539?v=4&s=48)](https://github.com/Starhappysh) [![xdanger](https://avatars.githubusercontent.com/u/7087?v=4&s=48)](https://github.com/xdanger) [![Penchan](https://avatars.githubusercontent.com/u/5032148?v=4&s=48)](https://github.com/p3nchan)
[![scald](https://avatars.githubusercontent.com/u/1215913?v=4&s=48)](https://github.com/scald) [![Serhii](https://avatars.githubusercontent.com/u/151471784?v=4&s=48)](https://github.com/kashevk0) [![a](https://avatars.githubusercontent.com/u/33371662?v=4&s=48)](https://github.com/Yuandiaodiaodiao) [![Doğu Abaris](https://avatars.githubusercontent.com/u/135986694?v=4&s=48)](https://github.com/doguabaris) [![ysqander](https://avatars.githubusercontent.com/u/80843820?v=4&s=48)](https://github.com/ysqander) [![andranik-sahakyan](https://avatars.githubusercontent.com/u/8908029?v=4&s=48)](https://github.com/andranik-sahakyan) [![Wangnov](https://avatars.githubusercontent.com/u/48670012?v=4&s=48)](https://github.com/Wangnov) [![Austin](https://avatars.githubusercontent.com/u/112558420?v=4&s=48)](https://github.com/rixau) [![lisitan](https://avatars.githubusercontent.com/u/50470712?v=4&s=48)](https://github.com/lisitan) [![Rishi Vhavle](https://avatars.githubusercontent.com/u/134706404?v=4&s=48)](https://github.com/kaizen403)
[![Frank Harris](https://avatars.githubusercontent.com/u/183158?v=4&s=48)](https://github.com/hirefrank) [![Kenny Lee](https://avatars.githubusercontent.com/u/1432489?v=4&s=48)](https://github.com/kennyklee) [![Alice Losasso](https://avatars.githubusercontent.com/u/104875499?v=4&s=48)](https://github.com/dddabtc) [![edincampara](https://avatars.githubusercontent.com/u/142477787?v=4&s=48)](https://github.com/edincampara) [![Felix Hellström](https://avatars.githubusercontent.com/u/30758862?v=4&s=48)](https://github.com/fellanH) [![Varun Chopra](https://avatars.githubusercontent.com/u/113368492?v=4&s=48)](https://github.com/VarunChopra11) [![wangai-studio](https://avatars.githubusercontent.com/u/256938352?v=4&s=48)](https://github.com/wangai-studio) [![sleontenko](https://avatars.githubusercontent.com/u/7135949?v=4&s=48)](https://github.com/sleontenko) [![Yassine Amjad](https://avatars.githubusercontent.com/u/59234686?v=4&s=48)](https://github.com/yassine20011) [![Anton Eicher](https://avatars.githubusercontent.com/u/54324760?v=4&s=48)](https://github.com/ant1eicher)
[![Drake Thomsen](https://avatars.githubusercontent.com/u/120344051?v=4&s=48)](https://github.com/ThomsenDrake) [![Hinata Kaga (samon)](https://avatars.githubusercontent.com/u/61647657?v=4&s=48)](https://github.com/kakuteki) [![andreabadesso](https://avatars.githubusercontent.com/u/3586068?v=4&s=48)](https://github.com/andreabadesso) [![chenxin-yan](https://avatars.githubusercontent.com/u/71162231?v=4&s=48)](https://github.com/chenxin-yan) [![cordx56](https://avatars.githubusercontent.com/u/23298744?v=4&s=48)](https://github.com/cordx56) [![dvrshil](https://avatars.githubusercontent.com/u/81693876?v=4&s=48)](https://github.com/dvrshil) [![MarvinCui](https://avatars.githubusercontent.com/u/130876763?v=4&s=48)](https://github.com/MarvinCui) [![Yeom-JinHo](https://avatars.githubusercontent.com/u/81306489?v=4&s=48)](https://github.com/Yeom-JinHo) [![Jeremy Mumford](https://avatars.githubusercontent.com/u/36290330?v=4&s=48)](https://github.com/17jmumford) [![Charlie Niño](https://avatars.githubusercontent.com/u/2346724?v=4&s=48)](https://github.com/KnHack)
[![Sharoon Sharif](https://avatars.githubusercontent.com/u/150296639?v=4&s=48)](https://github.com/SharoonSharif) [![Oren](https://avatars.githubusercontent.com/u/168856?v=4&s=48)](https://github.com/orenyomtov) [![MattQ](https://avatars.githubusercontent.com/u/115874885?v=4&s=48)](https://github.com/mattqdev) [![Parker Todd Brooks](https://avatars.githubusercontent.com/u/585456?v=4&s=48)](https://github.com/parkertoddbrooks) [![Yufeng He](https://avatars.githubusercontent.com/u/40085740?v=4&s=48)](https://github.com/he-yufeng) [![Milofax](https://avatars.githubusercontent.com/u/2537423?v=4&s=48)](https://github.com/Milofax) [![Steve (OpenClaw)](https://avatars.githubusercontent.com/u/261149299?v=4&s=48)](https://github.com/stevebot-alive) [![zhoulf1006](https://avatars.githubusercontent.com/u/35586967?v=4&s=48)](https://github.com/zhoulf1006) [![Jonatan](https://avatars.githubusercontent.com/u/19454127?v=4&s=48)](https://github.com/jrrcdev) [![Sebastian B Otaegui](https://avatars.githubusercontent.com/u/91633?v=4&s=48)](https://github.com/feniix)
[![Matthew](https://avatars.githubusercontent.com/u/76985631?v=4&s=48)](https://github.com/ZetiMente) [![ABFS Tech](https://avatars.githubusercontent.com/u/82096803?v=4&s=48)](https://github.com/QuantDeveloperUSA) [![alexstyl](https://avatars.githubusercontent.com/u/1665273?v=4&s=48)](https://github.com/alexstyl) [![Ethan Palm](https://avatars.githubusercontent.com/u/56270045?v=4&s=48)](https://github.com/ethanpalm) [![Qkal](https://avatars.githubusercontent.com/u/77361240?v=4&s=48)](https://github.com/qkal) [![cygaar](https://avatars.githubusercontent.com/u/97691933?v=4&s=48)](https://github.com/cygaar) [![Umut CAN](https://avatars.githubusercontent.com/u/78921017?v=4&s=48)](https://github.com/U-C4N) [![Jakob](https://avatars.githubusercontent.com/u/38699060?v=4&s=48)](https://github.com/jakobdylanc) [![antons](https://avatars.githubusercontent.com/u/129705?v=4&s=48)](https://github.com/antons) [![austinm911](https://avatars.githubusercontent.com/u/31991302?v=4&s=48)](https://github.com/austinm911)
[![mahmoudashraf93](https://avatars.githubusercontent.com/u/9130129?v=4&s=48)](https://github.com/mahmoudashraf93) [![philipp-spiess](https://avatars.githubusercontent.com/u/458591?v=4&s=48)](https://github.com/philipp-spiess) [![pkrmf](https://avatars.githubusercontent.com/u/1714267?v=4&s=48)](https://github.com/pkrmf) [![joshrad-dev](https://avatars.githubusercontent.com/u/62785552?v=4&s=48)](https://github.com/joshrad-dev) [![factnest365-ops](https://avatars.githubusercontent.com/u/236534360?v=4&s=48)](https://github.com/factnest365-ops) [![yingchunbai](https://avatars.githubusercontent.com/u/33477283?v=4&s=48)](https://github.com/yingchunbai) [![AJ (@techfren)](https://avatars.githubusercontent.com/u/8023513?v=4&s=48)](https://github.com/aj47) [![Marchel Fahrezi](https://avatars.githubusercontent.com/u/53804949?v=4&s=48)](https://github.com/Alg0rix) [![futhgar](https://avatars.githubusercontent.com/u/51002668?v=4&s=48)](https://github.com/futhgar) [![Zhang](https://avatars.githubusercontent.com/u/56248212?v=4&s=48)](https://github.com/YonganZhang)
[![Rémi](https://avatars.githubusercontent.com/u/1299873?v=4&s=48)](https://github.com/remusao) [![Dan Ballance](https://avatars.githubusercontent.com/u/13839912?v=4&s=48)](https://github.com/danballance) [![Eric Su](https://avatars.githubusercontent.com/u/60202455?v=4&s=48)](https://github.com/GHesericsu) [![Kimitaka Watanabe](https://avatars.githubusercontent.com/u/167225?v=4&s=48)](https://github.com/kimitaka) [![Justin Ling](https://avatars.githubusercontent.com/u/2521993?v=4&s=48)](https://github.com/itsjling) [![Raymond Berger](https://avatars.githubusercontent.com/u/921217?v=4&s=48)](https://github.com/RayBB) [![lutr0](https://avatars.githubusercontent.com/u/76906369?v=4&s=48)](https://github.com/lutr0) [![claude](https://avatars.githubusercontent.com/u/81847?v=4&s=48)](https://github.com/claude) [![AngryBird](https://avatars.githubusercontent.com/u/48046333?v=4&s=48)](https://github.com/angrybirddd) [![Fabian Williams](https://avatars.githubusercontent.com/u/92543063?v=4&s=48)](https://github.com/fabianwilliams)
[![0x4C33](https://avatars.githubusercontent.com/u/60883781?v=4&s=48)](https://github.com/haoruilee) [![8BlT](https://avatars.githubusercontent.com/u/162764392?v=4&s=48)](https://github.com/8BlT) [![atalovesyou](https://avatars.githubusercontent.com/u/3534502?v=4&s=48)](https://github.com/atalovesyou) [![erikpr1994](https://avatars.githubusercontent.com/u/6299331?v=4&s=48)](https://github.com/erikpr1994) [![jonasjancarik](https://avatars.githubusercontent.com/u/2459191?v=4&s=48)](https://github.com/jonasjancarik) [![longmaba](https://avatars.githubusercontent.com/u/9361500?v=4&s=48)](https://github.com/longmaba) [![mitschabaude-bot](https://avatars.githubusercontent.com/u/247582884?v=4&s=48)](https://github.com/mitschabaude-bot) [![thesash](https://avatars.githubusercontent.com/u/1166151?v=4&s=48)](https://github.com/thesash) [![Max](https://avatars.githubusercontent.com/u/8418866?v=4&s=48)](https://github.com/rdev) [![easternbloc](https://avatars.githubusercontent.com/u/92585?v=4&s=48)](https://github.com/easternbloc)
[![chrisrodz](https://avatars.githubusercontent.com/u/2967620?v=4&s=48)](https://github.com/chrisrodz) [![gabriel-trigo](https://avatars.githubusercontent.com/u/38991125?v=4&s=48)](https://github.com/gabriel-trigo) [![manmal](https://avatars.githubusercontent.com/u/142797?v=4&s=48)](https://github.com/manmal) [![neist](https://avatars.githubusercontent.com/u/1029724?v=4&s=48)](https://github.com/neist) [![wes-davis](https://avatars.githubusercontent.com/u/16506720?v=4&s=48)](https://github.com/wes-davis) [![manuelhettich](https://avatars.githubusercontent.com/u/17690367?v=4&s=48)](https://github.com/ManuelHettich) [![sktbrd](https://avatars.githubusercontent.com/u/116202536?v=4&s=48)](https://github.com/sktbrd) [![larlyssa](https://avatars.githubusercontent.com/u/13128869?v=4&s=48)](https://github.com/larlyssa) [![pcty-nextgen-service-account](https://avatars.githubusercontent.com/u/112553441?v=4&s=48)](https://github.com/pcty-nextgen-service-account) [![Syhids](https://avatars.githubusercontent.com/u/671202?v=4&s=48)](https://github.com/Syhids)
[![tmchow](https://avatars.githubusercontent.com/u/517103?v=4&s=48)](https://github.com/tmchow) [![Marc Gratch](https://avatars.githubusercontent.com/u/2238658?v=4&s=48)](https://github.com/mgratch) [![xtao](https://avatars.githubusercontent.com/u/1050163?v=4&s=48)](https://github.com/xtao) [![JackyWay](https://avatars.githubusercontent.com/u/53031570?v=4&s=48)](https://github.com/JackyWay) [![Josh Phillips](https://avatars.githubusercontent.com/u/3744255?v=4&s=48)](https://github.com/j1philli) [![T5-AndyML](https://avatars.githubusercontent.com/u/22801233?v=4&s=48)](https://github.com/T5-AndyML) [![huohua-dev](https://avatars.githubusercontent.com/u/258873123?v=4&s=48)](https://github.com/huohua-dev) [![imfing](https://avatars.githubusercontent.com/u/5097752?v=4&s=48)](https://github.com/imfing) [![Randy Torres](https://avatars.githubusercontent.com/u/149904821?v=4&s=48)](https://github.com/RandyVentures) [![Marco Di Dionisio](https://avatars.githubusercontent.com/u/3519682?v=4&s=48)](https://github.com/marcodd23)
[![iamadig](https://avatars.githubusercontent.com/u/102129234?v=4&s=48)](https://github.com/Iamadig) [![humanwritten](https://avatars.githubusercontent.com/u/206531610?v=4&s=48)](https://github.com/humanwritten) [![Rob Axelsen](https://avatars.githubusercontent.com/u/13132899?v=4&s=48)](https://github.com/robaxelsen) [![Pratham Dubey](https://avatars.githubusercontent.com/u/134331217?v=4&s=48)](https://github.com/prathamdby) [![0oAstro](https://avatars.githubusercontent.com/u/79555780?v=4&s=48)](https://github.com/0oAstro) [![aaronn](https://avatars.githubusercontent.com/u/1653630?v=4&s=48)](https://github.com/aaronn) [![Arturo](https://avatars.githubusercontent.com/u/34192856?v=4&s=48)](https://github.com/afern247) [![Asleep123](https://avatars.githubusercontent.com/u/122379135?v=4&s=48)](https://github.com/Asleep123) [![dantelex](https://avatars.githubusercontent.com/u/631543?v=4&s=48)](https://github.com/dantelex) [![fcatuhe](https://avatars.githubusercontent.com/u/17382215?v=4&s=48)](https://github.com/fcatuhe)
[![gtsifrikas](https://avatars.githubusercontent.com/u/8904378?v=4&s=48)](https://github.com/gtsifrikas) [![hrdwdmrbl](https://avatars.githubusercontent.com/u/554881?v=4&s=48)](https://github.com/hrdwdmrbl) [![hugobarauna](https://avatars.githubusercontent.com/u/2719?v=4&s=48)](https://github.com/hugobarauna) [![jayhickey](https://avatars.githubusercontent.com/u/1676460?v=4&s=48)](https://github.com/jayhickey) [![jiulingyun](https://avatars.githubusercontent.com/u/126459548?v=4&s=48)](https://github.com/jiulingyun) [![Jonathan D. Rhyne (DJ-D)](https://avatars.githubusercontent.com/u/7828464?v=4&s=48)](https://github.com/jdrhyne) [![jverdi](https://avatars.githubusercontent.com/u/345050?v=4&s=48)](https://github.com/jverdi) [![kitze](https://avatars.githubusercontent.com/u/1160594?v=4&s=48)](https://github.com/kitze) [![loukotal](https://avatars.githubusercontent.com/u/18210858?v=4&s=48)](https://github.com/loukotal) [![minghinmatthewlam](https://avatars.githubusercontent.com/u/14224566?v=4&s=48)](https://github.com/minghinmatthewlam)
[![MSch](https://avatars.githubusercontent.com/u/7475?v=4&s=48)](https://github.com/MSch) [![odrobnik](https://avatars.githubusercontent.com/u/333270?v=4&s=48)](https://github.com/odrobnik) [![oswalpalash](https://avatars.githubusercontent.com/u/6431196?v=4&s=48)](https://github.com/oswalpalash) [![ratulsarna](https://avatars.githubusercontent.com/u/105903728?v=4&s=48)](https://github.com/ratulsarna) [![reeltimeapps](https://avatars.githubusercontent.com/u/637338?v=4&s=48)](https://github.com/reeltimeapps) [![snopoke](https://avatars.githubusercontent.com/u/249606?v=4&s=48)](https://github.com/snopoke) [![sreekaransrinath](https://avatars.githubusercontent.com/u/50989977?v=4&s=48)](https://github.com/sreekaransrinath) [![timkrase](https://avatars.githubusercontent.com/u/38947626?v=4&s=48)](https://github.com/timkrase)
<!-- clawtributors:end -->

View File

@@ -38,6 +38,7 @@ For fastest triage, include all of the following:
- Tested version details (OpenClaw version and/or commit SHA).
- Reproducible PoC against latest `main` or latest released version.
- If the claim targets a released version, evidence from the shipped tag and published artifact/package for that exact version (not only `main`).
- For dependency CVE reports, evidence that the shipped dependency version is actually affected, plus a PoC that reproduces impact through OpenClaw. Showing that OpenClaw can reach a native parser is not enough by itself.
- Demonstrated impact tied to OpenClaw's documented trust boundaries.
- For exposed-secret reports: proof the credential is OpenClaw-owned (or grants access to OpenClaw-operated infrastructure/services).
- Explicit statement that the report does not rely on adversarial operators sharing one gateway host/config.
@@ -62,6 +63,7 @@ These are frequently reported but are typically closed with no code change:
- Reports that treat `POST /tools/invoke` under shared-secret bearer auth (`gateway.auth.mode="token"` or `"password"`) as a narrower per-request/per-scope authorization surface. That endpoint is designed as the same trusted-operator HTTP boundary: shared-secret bearer auth is full operator access there, narrower `x-openclaw-scopes` values do not reduce that path, and owner-only tool policy follows the shared-secret operator contract.
- Reports that only show differences in heuristic detection/parity (for example obfuscation-pattern detection on one exec path but not another, such as `node.invoke -> system.run` parity gaps) without demonstrating bypass of auth, approvals, allowlist enforcement, sandboxing, or other documented trust boundaries.
- Reports that only show an ACP tool can indirectly execute, mutate, orchestrate sessions, or reach another tool/runtime without demonstrating bypass of ACP prompt/approval, allowlist enforcement, sandboxing, or another documented trust boundary. ACP silent approval is intentionally limited to narrow readonly classes; parity-only indirect-command findings are hardening, not vulnerabilities.
- Reports that only show untrusted media bytes reaching a maintained native decoder dependency (for example Sharp/libvips/libheif) without proving the shipped dependency version is vulnerable and demonstrating crash, memory corruption, data exposure, or a boundary bypass through OpenClaw. JavaScript header sniffing and image dimension fast-paths are preflight/UX checks, not the security boundary for native decoder correctness.
- ReDoS/DoS claims that require trusted operator configuration input (for example catastrophic regex in `sessionFilter` or `logging.redactPatterns`) without a trust-boundary bypass.
- Archive/install extraction claims that require pre-existing local filesystem priming in trusted state (for example planting symlink/hardlink aliases under destination directories such as skills/tools paths) without showing an untrusted path that can create/control that primitive.
- Reports that depend on replacing or rewriting an already-approved executable path on a trusted host (same-path inode/content swap) without showing an untrusted path to perform that write.
@@ -145,6 +147,7 @@ Plugins/extensions are part of OpenClaw's trusted computing base for a gateway.
- Reports whose only claim is heuristic/parity drift in command-risk detection (for example obfuscation-pattern checks) across exec surfaces, without a demonstrated trust-boundary bypass. These are hardening-only findings and are not vulnerabilities; triage may close them as `invalid`/`no-action` or track them separately as low/informational hardening.
- Reports whose only claim is that an ACP-exposed tool can indirectly execute commands, mutate host state, or reach another privileged tool/runtime without demonstrating a bypass of ACP prompt/approval, allowlist enforcement, sandboxing, or another documented trust boundary. These are hardening-only findings, not vulnerabilities.
- Reports whose only claim is that exec approvals do not semantically model every interpreter/runtime loader form, subcommand, flag combination, package script, or transitive module/config import. Exec approvals bind exact request context and best-effort direct local file operands; they are not a complete semantic model of everything a runtime may load.
- Reports whose only claim is parser reachability in an up-to-date maintained dependency without showing that the exact shipped dependency build is vulnerable. We keep native media dependencies current; dependency exposure alone is not a vulnerability.
- Exposed secrets that are third-party/user-controlled credentials (not OpenClaw-owned and not granting access to OpenClaw-operated infrastructure/services) without demonstrated OpenClaw impact
- Reports whose only claim is host-side exec when sandbox runtime is disabled/unavailable (documented default behavior in the trusted-operator model), without a boundary bypass.
- Reports whose only claim is that a platform-provided upload destination URL is untrusted (for example Microsoft Teams `fileConsent/invoke` `uploadInfo.uploadUrl`) without proving attacker control in an authenticated production flow.

View File

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

View File

@@ -1,6 +1,14 @@
# OpenClaw iOS Changelog
## 2026.4.16 - 2026-04-17
## 2026.4.20 - 2026-04-20
Maintenance update for the current OpenClaw release.
## 2026.4.19 - 2026-04-19
Maintenance update for the current OpenClaw beta release.
## 2026.4.18 - 2026-04-18
Maintenance update for the current OpenClaw release.

View File

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

View File

@@ -1060,7 +1060,8 @@ private final class GatewayTLSFingerprintProbe: NSObject, URLSessionDelegate, @u
}
private func finish(_ fingerprint: String?) {
let (shouldComplete, taskToCancel, sessionToInvalidate) = self.state.withLock { s -> (Bool, URLSessionWebSocketTask?, URLSession?) in
typealias FinishState = (Bool, URLSessionWebSocketTask?, URLSession?)
let (shouldComplete, taskToCancel, sessionToInvalidate) = self.state.withLock { s -> FinishState in
guard !s.didFinish else { return (false, nil, nil) }
s.didFinish = true
let task = s.task

View File

@@ -292,7 +292,9 @@ enum GatewaySettingsStore {
let port = defaults.object(forKey: self.lastGatewayPortDefaultsKey) as? Int
let payload = LastGatewayConnectionData(
kind: kind, stableID: stableID, useTLS: useTLS,
kind: kind,
stableID: stableID,
useTLS: useTLS,
host: kind == .manual ? host : nil,
port: kind == .manual ? port : nil)
guard self.saveLastGatewayConnectionData(payload) else { return }

View File

@@ -136,7 +136,10 @@ private struct HomeToolbarStatusButton: View {
.buttonStyle(.plain)
.accessibilityLabel("Connection Status")
.accessibilityValue(self.accessibilityValue)
.accessibilityHint(self.gateway == .connected ? "Double tap for gateway actions" : "Double tap to open settings")
.accessibilityHint(
self.gateway == .connected
? "Double tap for gateway actions"
: "Double tap to open settings")
.onAppear { self.updatePulse(for: self.gateway, scenePhase: self.scenePhase, reduceMotion: self.reduceMotion) }
.onDisappear { self.pulse = false }
.onChange(of: self.gateway) { _, newValue in

View File

@@ -234,7 +234,9 @@ final class NodeAppModel {
self.watchMessagingService.setStatusHandler { [weak self] status in
Task { @MainActor in
GatewayDiagnostics.log(
"node app model: watch status callback reachable=\(status.reachable) activation=\(status.activationState) backgrounded=\(self?.isBackgrounded ?? false)")
"node app model: watch status callback "
+ "reachable=\(status.reachable) activation=\(status.activationState) "
+ "backgrounded=\(self?.isBackgrounded ?? false)")
await self?.handleWatchMessagingStatusChanged(status)
}
}
@@ -924,7 +926,9 @@ final class NodeAppModel {
self.screen.showDefaultCanvas()
} else {
let trustedA2UIURL = await self.resolveA2UIHostURL()
self.screen.navigate(to: url, trustA2UIActions: trustedA2UIURL == Self.normalizeURLForTrustComparison(url))
self.screen.navigate(
to: url,
trustA2UIActions: trustedA2UIURL == Self.normalizeURLForTrustComparison(url))
}
return BridgeInvokeResponse(id: req.id, ok: true)
case OpenClawCanvasCommand.hide.rawValue:
@@ -934,7 +938,9 @@ final class NodeAppModel {
let params = try Self.decodeParams(OpenClawCanvasNavigateParams.self, from: req.paramsJSON)
let trimmedURL = params.url.trimmingCharacters(in: .whitespacesAndNewlines)
let trustedA2UIURL = await self.resolveA2UIHostURL()
self.screen.navigate(to: trimmedURL, trustA2UIActions: trustedA2UIURL == Self.normalizeURLForTrustComparison(trimmedURL))
self.screen.navigate(
to: trimmedURL,
trustA2UIActions: trustedA2UIURL == Self.normalizeURLForTrustComparison(trimmedURL))
return BridgeInvokeResponse(id: req.id, ok: true)
case OpenClawCanvasCommand.evalJS.rawValue:
let params = try Self.decodeParams(OpenClawCanvasEvalParams.self, from: req.paramsJSON)
@@ -2562,8 +2568,8 @@ extension NodeAppModel {
PendingForegroundNodeActionsResponse.self,
from: payload)
guard !decoded.actions.isEmpty else { return }
self.pendingActionLogger.info(
"Pending actions pulled trigger=\(trigger, privacy: .public) count=\(decoded.actions.count, privacy: .public)")
// swiftlint:disable:next line_length
self.pendingActionLogger.info("Pending actions pulled trigger=\(trigger, privacy: .public) count=\(decoded.actions.count, privacy: .public)")
await self.applyPendingForegroundNodeActions(decoded.actions, trigger: trigger)
} catch {
// Best-effort only.
@@ -2585,8 +2591,8 @@ extension NodeAppModel {
command: action.command,
paramsJSON: action.paramsJSON)
let result = await self.handleInvoke(req)
self.pendingActionLogger.info(
"Pending action replay trigger=\(trigger, privacy: .public) id=\(action.id, privacy: .public) command=\(action.command, privacy: .public) ok=\(result.ok, privacy: .public)")
// swiftlint:disable:next line_length
self.pendingActionLogger.info("Pending action replay trigger=\(trigger, privacy: .public) id=\(action.id, privacy: .public) command=\(action.command, privacy: .public) ok=\(result.ok, privacy: .public)")
guard result.ok else { return }
let acked = await self.ackPendingForegroundNodeAction(
id: action.id,
@@ -2603,15 +2609,15 @@ extension NodeAppModel {
{
do {
let payload = try JSONEncoder().encode(PendingForegroundNodeActionsAckRequest(ids: [id]))
let paramsJSON = String(decoding: payload, as: UTF8.self)
let paramsJSON = String(bytes: payload, encoding: .utf8) ?? "{}"
_ = try await self.nodeGateway.request(
method: "node.pending.ack",
paramsJSON: paramsJSON,
timeoutSeconds: 6)
return true
} catch {
self.pendingActionLogger.error(
"Pending action ack failed trigger=\(trigger, privacy: .public) id=\(id, privacy: .public) command=\(command, privacy: .public) error=\(String(describing: error), privacy: .public)")
// swiftlint:disable:next line_length
self.pendingActionLogger.error("Pending action ack failed trigger=\(trigger, privacy: .public) id=\(id, privacy: .public) command=\(command, privacy: .public) error=\(String(describing: error), privacy: .public)")
return false
}
}
@@ -2623,7 +2629,7 @@ extension NodeAppModel {
case .deduped(let replyId):
self.watchReplyLogger.debug(
"watch reply deduped replyId=\(replyId, privacy: .public)")
case .queue(let replyId, let actionId):
case let .queue(replyId, actionId):
self.watchReplyLogger.info(
"watch reply queued replyId=\(replyId, privacy: .public) action=\(actionId, privacy: .public)")
case .forward:
@@ -2737,7 +2743,9 @@ extension NodeAppModel {
private func handleWatchMessagingStatusChanged(_ status: WatchMessagingStatus) async {
GatewayDiagnostics.log(
"watch exec approval: status changed reachable=\(status.reachable) activation=\(status.activationState) backgrounded=\(self.isBackgrounded)")
"watch exec approval: status changed "
+ "reachable=\(status.reachable) activation=\(status.activationState) "
+ "backgrounded=\(self.isBackgrounded)")
guard self.isBackgrounded else { return }
guard status.supported, status.paired, status.appInstalled else { return }
guard status.reachable || status.activationState == "activated" else { return }
@@ -2752,7 +2760,8 @@ extension NodeAppModel {
self.pendingWatchExecApprovalRecoveryIDs.append(normalizedApprovalID)
self.pendingWatchExecApprovalRecoveryIDs.sort()
GatewayDiagnostics.log(
"watch exec approval: queued recovery id=\(normalizedApprovalID) pendingCount=\(self.pendingWatchExecApprovalRecoveryIDs.count)")
"watch exec approval: queued recovery "
+ "id=\(normalizedApprovalID) pendingCount=\(self.pendingWatchExecApprovalRecoveryIDs.count)")
self.persistWatchExecApprovalBridgeState()
}
@@ -2763,7 +2772,8 @@ extension NodeAppModel {
self.pendingWatchExecApprovalRecoveryIDs.removeAll { $0 == normalizedApprovalID }
guard self.pendingWatchExecApprovalRecoveryIDs.count != originalCount else { return }
GatewayDiagnostics.log(
"watch exec approval: cleared recovery id=\(normalizedApprovalID) pendingCount=\(self.pendingWatchExecApprovalRecoveryIDs.count)")
"watch exec approval: cleared recovery "
+ "id=\(normalizedApprovalID) pendingCount=\(self.pendingWatchExecApprovalRecoveryIDs.count)")
self.persistWatchExecApprovalBridgeState()
}
@@ -2818,8 +2828,8 @@ extension NodeAppModel {
self.watchExecApprovalLogger.debug(
"watch exec approval prompt sent id=\(prompt.id, privacy: .public) reason=\(reason, privacy: .public)")
} catch {
self.watchExecApprovalLogger.error(
"watch exec approval prompt failed id=\(prompt.id, privacy: .public) reason=\(reason, privacy: .public) error=\(error.localizedDescription, privacy: .public)")
// swiftlint:disable:next line_length
self.watchExecApprovalLogger.error("watch exec approval prompt failed id=\(prompt.id, privacy: .public) reason=\(reason, privacy: .public) error=\(error.localizedDescription, privacy: .public)")
}
await self.syncWatchExecApprovalSnapshot(reason: "\(reason)_snapshot")
}
@@ -2840,8 +2850,8 @@ extension NodeAppModel {
do {
_ = try await self.watchMessagingService.sendExecApprovalResolved(message)
} catch {
self.watchExecApprovalLogger.error(
"watch exec approval resolved update failed id=\(normalizedApprovalID, privacy: .public) error=\(error.localizedDescription, privacy: .public)")
// swiftlint:disable:next line_length
self.watchExecApprovalLogger.error("watch exec approval resolved update failed id=\(normalizedApprovalID, privacy: .public) error=\(error.localizedDescription, privacy: .public)")
}
await self.syncWatchExecApprovalSnapshot(reason: "resolved_snapshot")
}
@@ -2860,8 +2870,8 @@ extension NodeAppModel {
do {
_ = try await self.watchMessagingService.sendExecApprovalExpired(message)
} catch {
self.watchExecApprovalLogger.error(
"watch exec approval expiry update failed id=\(normalizedApprovalID, privacy: .public) error=\(error.localizedDescription, privacy: .public)")
// swiftlint:disable:next line_length
self.watchExecApprovalLogger.error("watch exec approval expiry update failed id=\(normalizedApprovalID, privacy: .public) error=\(error.localizedDescription, privacy: .public)")
}
await self.syncWatchExecApprovalSnapshot(reason: "expired_\(reason.rawValue)")
}
@@ -2869,7 +2879,9 @@ extension NodeAppModel {
private func syncWatchExecApprovalSnapshot(reason: String) async {
self.pruneExpiredWatchExecApprovalPrompts()
GatewayDiagnostics.log(
"watch exec approval: sync snapshot start reason=\(reason) cacheCount=\(self.watchExecApprovalPromptsByID.count) backgrounded=\(self.isBackgrounded)")
"watch exec approval: sync snapshot start "
+ "reason=\(reason) cacheCount=\(self.watchExecApprovalPromptsByID.count) "
+ "backgrounded=\(self.isBackgrounded)")
let approvals = self.watchExecApprovalPromptsByID.values
.sorted { lhs, rhs in
let lhsExpires = lhs.expiresAtMs ?? Int.max
@@ -2888,13 +2900,13 @@ extension NodeAppModel {
_ = try await self.watchMessagingService.syncExecApprovalSnapshot(message)
GatewayDiagnostics.log(
"watch exec approval: sync snapshot sent reason=\(reason) count=\(approvals.count)")
self.watchExecApprovalLogger.debug(
"watch exec approval snapshot sent reason=\(reason, privacy: .public) count=\(approvals.count, privacy: .public)")
// swiftlint:disable:next line_length
self.watchExecApprovalLogger.debug("watch exec approval snapshot sent reason=\(reason, privacy: .public) count=\(approvals.count, privacy: .public)")
} catch {
GatewayDiagnostics.log(
"watch exec approval: sync snapshot failed reason=\(reason) error=\(error.localizedDescription)")
self.watchExecApprovalLogger.error(
"watch exec approval snapshot failed reason=\(reason, privacy: .public) error=\(error.localizedDescription, privacy: .public)")
// swiftlint:disable:next line_length
self.watchExecApprovalLogger.error("watch exec approval snapshot failed reason=\(reason, privacy: .public) error=\(error.localizedDescription, privacy: .public)")
}
}
@@ -2933,7 +2945,10 @@ extension NodeAppModel {
candidateIDs: approvalIDs,
cachedApprovalIDs: Array(self.watchExecApprovalPromptsByID.keys))
GatewayDiagnostics.log(
"watch exec approval: hydrate candidates reason=\(reason) ids=\(approvalIDs.joined(separator: ",")) missing=\(missingApprovalIDs.joined(separator: ",")) cached=\(self.watchExecApprovalPromptsByID.count)")
"watch exec approval: hydrate candidates "
+ "reason=\(reason) ids=\(approvalIDs.joined(separator: ",")) "
+ "missing=\(missingApprovalIDs.joined(separator: ",")) "
+ "cached=\(self.watchExecApprovalPromptsByID.count)")
guard !missingApprovalIDs.isEmpty else {
self.watchExecApprovalLogger.debug(
"watch exec approval hydrate skipped reason=\(reason, privacy: .public): no missing approval ids")
@@ -2957,8 +2972,8 @@ extension NodeAppModel {
forApprovalID: approvalId,
notificationCenter: self.notificationCenter)
case let .failed(message):
self.watchExecApprovalLogger.error(
"watch exec approval hydrate failed id=\(approvalId, privacy: .public) reason=\(reason, privacy: .public) error=\(message, privacy: .public)")
// swiftlint:disable:next line_length
self.watchExecApprovalLogger.error("watch exec approval hydrate failed id=\(approvalId, privacy: .public) reason=\(reason, privacy: .public) error=\(message, privacy: .public)")
}
}
}
@@ -3039,8 +3054,8 @@ extension NodeAppModel {
reason: .notFound)
return true
case let .failed(message):
self.watchExecApprovalLogger.error(
"watch exec approval push fetch failed id=\(normalizedApprovalID, privacy: .public) error=\(message, privacy: .public)")
// swiftlint:disable:next line_length
self.watchExecApprovalLogger.error("watch exec approval push fetch failed id=\(normalizedApprovalID, privacy: .public) error=\(message, privacy: .public)")
return false
}
}
@@ -3086,13 +3101,15 @@ extension NodeAppModel {
return true
}
if ExecApprovalNotificationBridge.payloadKind(userInfo: userInfo) == ExecApprovalNotificationBridge.requestedKind,
let execApprovalPushKind = ExecApprovalNotificationBridge.payloadKind(userInfo: userInfo)
let isExecApprovalRequestPush = execApprovalPushKind == ExecApprovalNotificationBridge.requestedKind
if isExecApprovalRequestPush,
let approvalId = ExecApprovalNotificationBridge.approvalID(from: userInfo)
{
let handled = await self.handleExecApprovalRequestedRemotePush(approvalId: approvalId)
if handled {
self.execApprovalNotificationLogger.info(
"Handled exec approval request push wakeId=\(wakeId, privacy: .public) id=\(approvalId, privacy: .public)")
// swiftlint:disable:next line_length
self.execApprovalNotificationLogger.info("Handled exec approval request push wakeId=\(wakeId, privacy: .public) id=\(approvalId, privacy: .public)")
}
return handled
}
@@ -3313,8 +3330,8 @@ extension NodeAppModel {
self.clearPendingExecApprovalPromptIfMatches(approvalId)
await self.publishWatchExecApprovalExpired(approvalId: approvalId, reason: .notFound)
case let .failed(message):
self.execApprovalNotificationLogger.error(
"Exec approval prompt fetch failed id=\(approvalId, privacy: .public) reason=\(message, privacy: .public)")
// swiftlint:disable:next line_length
self.execApprovalNotificationLogger.error("Exec approval prompt fetch failed id=\(approvalId, privacy: .public) reason=\(message, privacy: .public)")
}
}
@@ -3417,7 +3434,9 @@ extension NodeAppModel {
return .stale
}
GatewayDiagnostics.log(
"watch exec approval: fetch prompt failed id=\(approvalId) reason=\(fetchReason) error=\(error.localizedDescription)")
"watch exec approval: fetch prompt failed "
+ "id=\(approvalId) reason=\(fetchReason) "
+ "error=\(error.localizedDescription)")
return .failed(message: error.localizedDescription)
}
}
@@ -3647,28 +3666,33 @@ extension NodeAppModel {
let reconnectReason = normalizedReason.isEmpty ? "watch_request" : normalizedReason
if await self.isOperatorConnected() {
GatewayDiagnostics.log(
"watch exec approval: watch_request_reconnect_connected reason=\(reconnectReason) phase=already_connected")
"watch exec approval: watch_request_reconnect_connected "
+ "reason=\(reconnectReason) phase=already_connected")
return true
}
guard self.isBackgrounded else {
GatewayDiagnostics.log(
"watch exec approval: watch_request_reconnect_begin reason=\(reconnectReason) backgrounded=false strategy=default")
"watch exec approval: watch_request_reconnect_begin "
+ "reason=\(reconnectReason) backgrounded=false strategy=default")
let connected = await self.ensureOperatorApprovalConnection(timeoutMs: timeoutMs)
GatewayDiagnostics.log(
"watch exec approval: watch_request_reconnect_\(connected ? "connected" : "timeout") reason=\(reconnectReason) phase=foreground_delegate")
"watch exec approval: watch_request_reconnect_\(connected ? "connected" : "timeout") "
+ "reason=\(reconnectReason) phase=foreground_delegate")
return connected
}
guard self.gatewayAutoReconnectEnabled else {
GatewayDiagnostics.log(
"watch exec approval: watch_request_reconnect_timeout reason=\(reconnectReason) phase=auto_reconnect_disabled")
"watch exec approval: watch_request_reconnect_timeout "
+ "reason=\(reconnectReason) phase=auto_reconnect_disabled")
return false
}
guard let cfg = self.activeGatewayConnectConfig else {
GatewayDiagnostics.log(
"watch exec approval: watch_request_reconnect_timeout reason=\(reconnectReason) phase=no_active_gateway_config")
"watch exec approval: watch_request_reconnect_timeout "
+ "reason=\(reconnectReason) phase=no_active_gateway_config")
return false
}
@@ -3677,7 +3701,8 @@ extension NodeAppModel {
let leaseSeconds = min(45.0, max(15.0, Double(max(timeoutMs, 1_000)) / 1000.0 + 8.0))
self.grantBackgroundReconnectLease(seconds: leaseSeconds, reason: "watch_review_\(reconnectReason)")
GatewayDiagnostics.log(
"watch exec approval: watch_request_reconnect_lease_granted reason=\(reconnectReason) seconds=\(leaseSeconds)")
"watch exec approval: watch_request_reconnect_lease_granted "
+ "reason=\(reconnectReason) seconds=\(leaseSeconds)")
let hadReconnectLoop = self.operatorGatewayTask != nil
let canStartReconnectLoop = hadReconnectLoop || self.shouldStartOperatorGatewayLoop(
@@ -3687,20 +3712,24 @@ extension NodeAppModel {
stableID: cfg.effectiveStableID)
guard canStartReconnectLoop else {
GatewayDiagnostics.log(
"watch exec approval: watch_request_reconnect_timeout reason=\(reconnectReason) phase=no_operator_reconnect_auth")
"watch exec approval: watch_request_reconnect_timeout "
+ "reason=\(reconnectReason) phase=no_operator_reconnect_auth")
return false
}
self.ensureOperatorReconnectLoopIfNeeded()
GatewayDiagnostics.log(
"watch exec approval: watch_request_reconnect_loop_\(hadReconnectLoop ? "reused" : "started") reason=\(reconnectReason)")
"watch exec approval: watch_request_reconnect_loop_\(hadReconnectLoop ? "reused" : "started") "
+ "reason=\(reconnectReason)")
let initialWaitMs = min(2_500, max(750, timeoutMs / 4))
GatewayDiagnostics.log(
"watch exec approval: watch_request_reconnect_wait reason=\(reconnectReason) phase=initial timeoutMs=\(initialWaitMs)")
"watch exec approval: watch_request_reconnect_wait "
+ "reason=\(reconnectReason) phase=initial timeoutMs=\(initialWaitMs)")
if await self.waitForOperatorConnection(timeoutMs: initialWaitMs, pollMs: 200) {
GatewayDiagnostics.log(
"watch exec approval: watch_request_reconnect_connected reason=\(reconnectReason) phase=initial")
"watch exec approval: watch_request_reconnect_connected "
+ "reason=\(reconnectReason) phase=initial")
return true
}
@@ -3725,10 +3754,12 @@ extension NodeAppModel {
let remainingWaitMs = max(250, timeoutMs - initialWaitMs)
GatewayDiagnostics.log(
"watch exec approval: watch_request_reconnect_wait reason=\(reconnectReason) phase=restart timeoutMs=\(remainingWaitMs)")
"watch exec approval: watch_request_reconnect_wait "
+ "reason=\(reconnectReason) phase=restart timeoutMs=\(remainingWaitMs)")
let connected = await self.waitForOperatorConnection(timeoutMs: remainingWaitMs, pollMs: 200)
GatewayDiagnostics.log(
"watch exec approval: watch_request_reconnect_\(connected ? "connected" : "timeout") reason=\(reconnectReason) phase=restart")
"watch exec approval: watch_request_reconnect_\(connected ? "connected" : "timeout") "
+ "reason=\(reconnectReason) phase=restart")
return connected
}

View File

@@ -338,7 +338,9 @@ struct OnboardingWizardView: View {
Text("Security notice")
.font(.headline)
Text(
"The connected OpenClaw agent can use device capabilities you enable, such as camera, microphone, photos, contacts, calendar, and location. Continue only if you trust the gateway and agent you connect to.")
"The connected OpenClaw agent can use device capabilities you enable, "
+ "such as camera, microphone, photos, contacts, calendar, and location. "
+ "Continue only if you trust the gateway and agent you connect to.")
.font(.footnote)
.foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true)

View File

@@ -13,8 +13,7 @@ private func sendReachableWatchMessage(_ payload: [String: Any], with session: W
// WatchConnectivity replies arrive on its own queue. Keep this continuation explicitly
// nonisolated so Swift 6 does not inherit a caller actor (for example MainActor) into the
// Objective-C callback boundary and trap on the reply callback executor check.
try await withCheckedThrowingContinuation(isolation: nil) {
(continuation: CheckedContinuation<Void, Error>) in
try await withCheckedThrowingContinuation(isolation: nil) { (continuation: CheckedContinuation<Void, Error>) in
session.sendMessage(
payload,
replyHandler: { _ in
@@ -259,7 +258,9 @@ extension WatchConnectivityTransport: WCSessionDelegate {
error: (any Error)?)
{
GatewayDiagnostics.log(
"watch messaging: activation complete state=\(Self.activationStateLabel(activationState)) error=\(error?.localizedDescription ?? "none")")
"watch messaging: activation complete "
+ "state=\(Self.activationStateLabel(activationState)) "
+ "error=\(error?.localizedDescription ?? "none")")
if let error {
Self.logger.error("watch activation failed: \(error.localizedDescription, privacy: .public)")
} else {
@@ -357,7 +358,9 @@ extension WatchConnectivityTransport: WCSessionDelegate {
func sessionReachabilityDidChange(_ session: WCSession) {
GatewayDiagnostics.log(
"watch messaging: reachability changed reachable=\(session.isReachable) paired=\(session.isPaired) installed=\(session.isWatchAppInstalled)")
"watch messaging: reachability changed "
+ "reachable=\(session.isReachable) paired=\(session.isPaired) "
+ "installed=\(session.isWatchAppInstalled)")
self.emitStatusUpdate(Self.status(for: session))
}
}

View File

@@ -74,7 +74,10 @@ final class WatchMessagingService: @preconcurrency WatchMessagingServicing {
let snapshot = self.transport.currentStatusSnapshot()
self.lastEmittedStatus = snapshot
GatewayDiagnostics.log(
"watch messaging: set status handler supported=\(snapshot.supported) paired=\(snapshot.paired) appInstalled=\(snapshot.appInstalled) reachable=\(snapshot.reachable) activation=\(snapshot.activationState)")
"watch messaging: set status handler "
+ "supported=\(snapshot.supported) paired=\(snapshot.paired) "
+ "appInstalled=\(snapshot.appInstalled) reachable=\(snapshot.reachable) "
+ "activation=\(snapshot.activationState)")
handler(snapshot)
}
@@ -134,7 +137,10 @@ final class WatchMessagingService: @preconcurrency WatchMessagingServicing {
}
self.lastEmittedStatus = snapshot
GatewayDiagnostics.log(
"watch messaging: status supported=\(snapshot.supported) paired=\(snapshot.paired) appInstalled=\(snapshot.appInstalled) reachable=\(snapshot.reachable) activation=\(snapshot.activationState)")
"watch messaging: status "
+ "supported=\(snapshot.supported) paired=\(snapshot.paired) "
+ "appInstalled=\(snapshot.appInstalled) reachable=\(snapshot.reachable) "
+ "activation=\(snapshot.activationState)")
self.statusHandler?(snapshot)
}
@@ -148,7 +154,9 @@ final class WatchMessagingService: @preconcurrency WatchMessagingServicing {
private func emitExecApprovalSnapshotRequest(_ event: WatchExecApprovalSnapshotRequestEvent) {
GatewayDiagnostics.log(
"watch messaging: snapshot request id=\(event.requestId) transport=\(event.transport) sentAtMs=\(event.sentAtMs ?? -1)")
"watch messaging: snapshot request "
+ "id=\(event.requestId) transport=\(event.transport) "
+ "sentAtMs=\(event.sentAtMs ?? -1)")
self.execApprovalSnapshotRequestHandler?(event)
}
}

View File

@@ -1008,7 +1008,9 @@ final class TalkModeManager: NSObject {
self.logger.warning("unknown voice alias \(requestedVoice ?? "?", privacy: .public)")
}
let configuredKey = self.apiKey?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false ? self.apiKey : nil
let configuredKey = self.apiKey?
.trimmingCharacters(in: .whitespacesAndNewlines)
.isEmpty == false ? self.apiKey : nil
#if DEBUG
let resolvedKey = configuredKey ?? ProcessInfo.processInfo.environment["ELEVENLABS_API_KEY"]
#else
@@ -1514,7 +1516,9 @@ final class TalkModeManager: NSObject {
"talk output_format unsupported for local playback: \(requestedOutputFormat, privacy: .public)")
}
let configuredKey = self.apiKey?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false ? self.apiKey : nil
let configuredKey = self.apiKey?
.trimmingCharacters(in: .whitespacesAndNewlines)
.isEmpty == false ? self.apiKey : nil
#if DEBUG
let resolvedKey = configuredKey ?? ProcessInfo.processInfo.environment["ELEVENLABS_API_KEY"]
#else

View File

@@ -1,3 +1,3 @@
{
"version": "2026.4.16"
"version": "2026.4.20"
}

View File

@@ -30,6 +30,26 @@ final class AppState {
case direct
}
struct RemoteGatewayConfigDraft {
var transport: RemoteTransport
var remoteUrl: String
var remoteHost: String?
var remoteTarget: String
var remoteIdentity: String
var remoteToken: String
var remoteTokenDirty: Bool
}
struct GatewayConfigSyncDraft {
var connectionMode: ConnectionMode
var remoteTransport: RemoteTransport
var remoteTarget: String
var remoteIdentity: String
var remoteUrl: String
var remoteToken: String
var remoteTokenDirty: Bool
}
var isPaused: Bool {
didSet { self.ifNotPreview { UserDefaults.standard.set(self.isPaused, forKey: pauseDefaultsKey) } }
}
@@ -420,25 +440,19 @@ final class AppState {
private static func updatedRemoteGatewayConfig(
current: [String: Any],
transport: RemoteTransport,
remoteUrl: String,
remoteHost: String?,
remoteTarget: String,
remoteIdentity: String,
remoteToken: String,
remoteTokenDirty: Bool) -> (remote: [String: Any], changed: Bool)
draft: RemoteGatewayConfigDraft) -> (remote: [String: Any], changed: Bool)
{
var remote = current
var changed = false
switch transport {
switch draft.transport {
case .direct:
changed = Self.updateGatewayString(
&remote,
key: "transport",
value: RemoteTransport.direct.rawValue) || changed
let trimmedUrl = remoteUrl.trimmingCharacters(in: .whitespacesAndNewlines)
let trimmedUrl = draft.remoteUrl.trimmingCharacters(in: .whitespacesAndNewlines)
if trimmedUrl.isEmpty {
changed = Self.updateGatewayString(&remote, key: "url", value: nil) || changed
} else if let normalizedUrl = GatewayRemoteConfig.normalizeGatewayUrlString(trimmedUrl) {
@@ -448,7 +462,7 @@ final class AppState {
case .ssh:
changed = Self.updateGatewayString(&remote, key: "transport", value: nil) || changed
if let host = remoteHost {
if let host = draft.remoteHost {
let existingUrl = (remote["url"] as? String)?
.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
let parsedExisting = existingUrl.isEmpty ? nil : URL(string: existingUrl)
@@ -458,13 +472,13 @@ final class AppState {
changed = Self.updateGatewayString(&remote, key: "url", value: desiredUrl) || changed
}
let sanitizedTarget = Self.sanitizeSSHTarget(remoteTarget)
let sanitizedTarget = Self.sanitizeSSHTarget(draft.remoteTarget)
changed = Self.updateGatewayString(&remote, key: "sshTarget", value: sanitizedTarget) || changed
changed = Self.updateGatewayString(&remote, key: "sshIdentity", value: remoteIdentity) || changed
changed = Self.updateGatewayString(&remote, key: "sshIdentity", value: draft.remoteIdentity) || changed
}
if remoteTokenDirty {
changed = Self.updateGatewayString(&remote, key: "token", value: remoteToken) || changed
if draft.remoteTokenDirty {
changed = Self.updateGatewayString(&remote, key: "token", value: draft.remoteToken) || changed
}
return (remote, changed)
@@ -550,19 +564,13 @@ final class AppState {
private static func syncedGatewayRoot(
currentRoot: [String: Any],
connectionMode: ConnectionMode,
remoteTransport: RemoteTransport,
remoteTarget: String,
remoteIdentity: String,
remoteUrl: String,
remoteToken: String,
remoteTokenDirty: Bool) -> (root: [String: Any], changed: Bool)
draft: GatewayConfigSyncDraft) -> (root: [String: Any], changed: Bool)
{
var root = currentRoot
var gateway = root["gateway"] as? [String: Any] ?? [:]
var changed = false
let desiredMode: String? = switch connectionMode {
let desiredMode: String? = switch draft.connectionMode {
case .local:
"local"
case .remote:
@@ -582,18 +590,19 @@ final class AppState {
changed = true
}
if connectionMode == .remote {
let remoteHost = CommandResolver.parseSSHTarget(remoteTarget)?.host
if draft.connectionMode == .remote {
let remoteHost = CommandResolver.parseSSHTarget(draft.remoteTarget)?.host
let currentRemote = gateway["remote"] as? [String: Any] ?? [:]
let updated = Self.updatedRemoteGatewayConfig(
current: currentRemote,
transport: remoteTransport,
remoteUrl: remoteUrl,
remoteHost: remoteHost,
remoteTarget: remoteTarget,
remoteIdentity: remoteIdentity,
remoteToken: remoteToken,
remoteTokenDirty: remoteTokenDirty)
draft: .init(
transport: draft.remoteTransport,
remoteUrl: draft.remoteUrl,
remoteHost: remoteHost,
remoteTarget: draft.remoteTarget,
remoteIdentity: draft.remoteIdentity,
remoteToken: draft.remoteToken,
remoteTokenDirty: draft.remoteTokenDirty))
if updated.changed {
gateway["remote"] = updated.remote
changed = true
@@ -625,13 +634,14 @@ final class AppState {
// Keep app-only connection settings local to avoid overwriting remote gateway config.
let synced = Self.syncedGatewayRoot(
currentRoot: OpenClawConfigFile.loadDict(),
connectionMode: self.connectionMode,
remoteTransport: self.remoteTransport,
remoteTarget: self.remoteTarget,
remoteIdentity: self.remoteIdentity,
remoteUrl: self.remoteUrl,
remoteToken: self.remoteToken,
remoteTokenDirty: self.remoteTokenDirty)
draft: .init(
connectionMode: self.connectionMode,
remoteTransport: self.remoteTransport,
remoteTarget: self.remoteTarget,
remoteIdentity: self.remoteIdentity,
remoteUrl: self.remoteUrl,
remoteToken: self.remoteToken,
remoteTokenDirty: self.remoteTokenDirty))
guard synced.changed else { return }
OpenClawConfigFile.saveDict(synced.root)
}
@@ -788,44 +798,20 @@ extension AppState {
extension AppState {
static func _testUpdatedRemoteGatewayConfig(
current: [String: Any],
transport: RemoteTransport,
remoteUrl: String,
remoteHost: String?,
remoteTarget: String,
remoteIdentity: String,
remoteToken: String,
remoteTokenDirty: Bool) -> [String: Any]
draft: RemoteGatewayConfigDraft) -> [String: Any]
{
self.updatedRemoteGatewayConfig(
current: current,
transport: transport,
remoteUrl: remoteUrl,
remoteHost: remoteHost,
remoteTarget: remoteTarget,
remoteIdentity: remoteIdentity,
remoteToken: remoteToken,
remoteTokenDirty: remoteTokenDirty).remote
draft: draft).remote
}
static func _testSyncedGatewayRoot(
currentRoot: [String: Any],
connectionMode: ConnectionMode,
remoteTransport: RemoteTransport,
remoteTarget: String,
remoteIdentity: String,
remoteUrl: String,
remoteToken: String,
remoteTokenDirty: Bool) -> [String: Any]
draft: GatewayConfigSyncDraft) -> [String: Any]
{
self.syncedGatewayRoot(
currentRoot: currentRoot,
connectionMode: connectionMode,
remoteTransport: remoteTransport,
remoteTarget: remoteTarget,
remoteIdentity: remoteIdentity,
remoteUrl: remoteUrl,
remoteToken: remoteToken,
remoteTokenDirty: remoteTokenDirty).root
draft: draft).root
}
}
#endif

View File

@@ -60,7 +60,7 @@ extension ChannelsStore {
timeoutMs: 35000)
self.whatsappLoginMessage = result.message
self.whatsappLoginQrDataUrl = result.qrDataUrl
self.whatsappLoginConnected = nil
self.whatsappLoginConnected = result.connected
shouldAutoWait = autoWait && result.qrDataUrl != nil
} catch {
self.whatsappLoginMessage = error.localizedDescription
@@ -148,6 +148,7 @@ extension ChannelsStore {
private struct WhatsAppLoginStartResult: Codable {
let qrDataUrl: String?
let message: String
let connected: Bool?
}
private struct WhatsAppLoginWaitResult: Codable {

View File

@@ -476,10 +476,8 @@ private enum ExecHostExecutor {
{
guard decision == .allowAlways, context.security == .allowlist else { return }
var seenPatterns = Set<String>()
for pattern in context.allowAlwaysPatterns {
if seenPatterns.insert(pattern).inserted {
ExecApprovalsStore.addAllowlistEntry(agentId: context.agentId, pattern: pattern)
}
for pattern in context.allowAlwaysPatterns where seenPatterns.insert(pattern).inserted {
ExecApprovalsStore.addAllowlistEntry(agentId: context.agentId, pattern: pattern)
}
}

View File

@@ -308,7 +308,9 @@ struct GeneralSettings: View {
.padding(.leading, self.remoteLabelWidth + 10)
if self.state.remoteTokenUnsupported {
Text(
"The current gateway.remote.token value is not plain text. OpenClaw for macOS cannot use it directly; enter a plaintext token here to replace it.")
"The current gateway.remote.token value is not plain text. "
+ "OpenClaw for macOS cannot use it directly; "
+ "enter a plaintext token here to replace it.")
.font(.caption)
.foregroundStyle(.orange)
.padding(.leading, self.remoteLabelWidth + 10)

View File

@@ -845,10 +845,8 @@ extension MacNodeRuntime {
{
guard persistAllowlist, security == .allowlist else { return }
var seenPatterns = Set<String>()
for pattern in allowAlwaysPatterns {
if seenPatterns.insert(pattern).inserted {
ExecApprovalsStore.addAllowlistEntry(agentId: agentId, pattern: pattern)
}
for pattern in allowAlwaysPatterns where seenPatterns.insert(pattern).inserted {
ExecApprovalsStore.addAllowlistEntry(agentId: agentId, pattern: pattern)
}
}

View File

@@ -398,7 +398,9 @@ extension OnboardingView {
.foregroundStyle(.secondary)
if self.state.remoteTokenUnsupported {
Text(
"The current gateway.remote.token value is not plain text. OpenClaw for macOS cannot use it directly; enter a plaintext token here to replace it.")
"The current gateway.remote.token value is not plain text. "
+ "OpenClaw for macOS cannot use it directly; "
+ "enter a plaintext token here to replace it.")
.font(.caption)
.foregroundStyle(.orange)
.fixedSize(horizontal: false, vertical: true)

View File

@@ -61,28 +61,36 @@ enum RemoteGatewayAuthIssue: Equatable {
var body: String {
switch self {
case .tokenRequired:
"Paste the token configured on the gateway host. On the gateway host, run `openclaw config get gateway.auth.token`. If the gateway uses an environment variable instead, use `OPENCLAW_GATEWAY_TOKEN`."
"Paste the token configured on the gateway host. "
+ "On the gateway host, run `openclaw config get gateway.auth.token`. "
+ "If the gateway uses an environment variable instead, use `OPENCLAW_GATEWAY_TOKEN`."
case .tokenMismatch:
"Check `gateway.auth.token` or `OPENCLAW_GATEWAY_TOKEN` on the gateway host and try again."
case .gatewayTokenNotConfigured:
"This gateway is set to token auth, but no `gateway.auth.token` is configured on the gateway host. If the gateway uses an environment variable instead, set `OPENCLAW_GATEWAY_TOKEN` before starting the gateway."
"This gateway is set to token auth, but no `gateway.auth.token` is configured on the gateway host. "
+ "If the gateway uses an environment variable instead, "
+ "set `OPENCLAW_GATEWAY_TOKEN` before starting the gateway."
case .setupCodeExpired:
"Scan or paste a fresh setup code from an already-paired OpenClaw client, then try again."
case .passwordRequired:
"This onboarding flow does not support password auth yet. Reconfigure the gateway to use token auth, then retry."
"This onboarding flow does not support password auth yet. "
+ "Reconfigure the gateway to use token auth, then retry."
case .pairingRequired:
"Approve this device from an already-paired OpenClaw client. In your OpenClaw chat, run `/pair approve`, then click **Check connection** again."
"Approve this device from an already-paired OpenClaw client. "
+ "In your OpenClaw chat, run `/pair approve`, then click **Check connection** again."
}
}
var footnote: String? {
switch self {
case .tokenRequired, .gatewayTokenNotConfigured:
"No token yet? Generate one on the gateway host with `openclaw doctor --generate-gateway-token`, then set it as `gateway.auth.token`."
"No token yet? Generate one on the gateway host with "
+ "`openclaw doctor --generate-gateway-token`, then set it as `gateway.auth.token`."
case .setupCodeExpired:
nil
case .pairingRequired:
"If you do not have another paired OpenClaw client yet, approve the pending request on the gateway host with `openclaw devices approve`."
"If you do not have another paired OpenClaw client yet, "
+ "approve the pending request on the gateway host with `openclaw devices approve`."
case .tokenMismatch, .passwordRequired:
nil
}
@@ -101,7 +109,8 @@ enum RemoteGatewayAuthIssue: Equatable {
case .passwordRequired:
"This gateway uses password auth. Remote onboarding on macOS cannot collect gateway passwords yet."
case .pairingRequired:
"Pairing required. In an already-paired OpenClaw client, run /pair approve, then check the connection again."
"Pairing required. In an already-paired OpenClaw client, "
+ "run /pair approve, then check the connection again."
}
}
}
@@ -135,7 +144,8 @@ struct RemoteGatewayProbeSuccess: Equatable {
case .some(.deviceToken):
"This Mac used a stored device token. New or unpaired devices may still need the gateway token."
case .some(.bootstrapToken):
"This Mac is still using the temporary setup code. Approve pairing to finish provisioning device-scoped auth."
"This Mac is still using the temporary setup code. "
+ "Approve pairing to finish provisioning device-scoped auth."
case .some(.sharedToken), .some(.password), .some(GatewayAuthSource.none), nil:
nil
}
@@ -219,7 +229,8 @@ enum RemoteGatewayProbe {
trimmed.localizedCaseInsensitiveContains("host key verification failed")
{
let host = CommandResolver.parseSSHTarget(target)?.host ?? target
return "SSH check failed: Host key verification failed. Remove the old key with ssh-keygen -R \(host) and try again."
return "SSH check failed: Host key verification failed. "
+ "Remove the old key with ssh-keygen -R \(host) and try again."
}
if let trimmed, !trimmed.isEmpty {
if let message = response.message, message.hasPrefix("exit ") {

View File

@@ -15,9 +15,9 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>2026.4.16</string>
<string>2026.4.20</string>
<key>CFBundleVersion</key>
<string>2026041690</string>
<string>2026042000</string>
<key>CFBundleIconFile</key>
<string>OpenClaw</string>
<key>CFBundleURLTypes</key>

View File

@@ -2481,6 +2481,24 @@ public struct ChannelsStatusResult: Codable, Sendable {
}
}
public struct ChannelsStartParams: Codable, Sendable {
public let channel: String
public let accountid: String?
public init(
channel: String,
accountid: String?)
{
self.channel = channel
self.accountid = accountid
}
private enum CodingKeys: String, CodingKey {
case channel
case accountid = "accountId"
}
}
public struct ChannelsLogoutParams: Codable, Sendable {
public let channel: String
public let accountid: String?

View File

@@ -8,13 +8,14 @@ struct AppStateRemoteConfigTests {
func updatedRemoteGatewayConfigSetsTrimmedToken() {
let remote = AppState._testUpdatedRemoteGatewayConfig(
current: [:],
transport: .ssh,
remoteUrl: "",
remoteHost: "gateway.example",
remoteTarget: "alice@gateway.example",
remoteIdentity: "/tmp/id_ed25519",
remoteToken: " secret-token ",
remoteTokenDirty: true)
draft: .init(
transport: .ssh,
remoteUrl: "",
remoteHost: "gateway.example",
remoteTarget: "alice@gateway.example",
remoteIdentity: "/tmp/id_ed25519",
remoteToken: " secret-token ",
remoteTokenDirty: true))
#expect(remote["token"] as? String == "secret-token")
}
@@ -23,13 +24,14 @@ struct AppStateRemoteConfigTests {
func updatedRemoteGatewayConfigClearsTokenWhenBlank() {
let remote = AppState._testUpdatedRemoteGatewayConfig(
current: ["token": "old-token"],
transport: .direct,
remoteUrl: "wss://gateway.example",
remoteHost: nil,
remoteTarget: "",
remoteIdentity: "",
remoteToken: " ",
remoteTokenDirty: true)
draft: .init(
transport: .direct,
remoteUrl: "wss://gateway.example",
remoteHost: nil,
remoteTarget: "",
remoteIdentity: "",
remoteToken: " ",
remoteTokenDirty: true))
#expect((remote["token"] as? String) == nil)
}
@@ -51,25 +53,27 @@ struct AppStateRemoteConfigTests {
let sshRoot = AppState._testSyncedGatewayRoot(
currentRoot: initialRoot,
connectionMode: .remote,
remoteTransport: .ssh,
remoteTarget: "alice@gateway.example",
remoteIdentity: "",
remoteUrl: "",
remoteToken: "",
remoteTokenDirty: false)
draft: .init(
connectionMode: .remote,
remoteTransport: .ssh,
remoteTarget: "alice@gateway.example",
remoteIdentity: "",
remoteUrl: "",
remoteToken: "",
remoteTokenDirty: false))
let sshRemote = (sshRoot["gateway"] as? [String: Any])?["remote"] as? [String: Any]
#expect((sshRemote?["token"] as? [String: String])?["$secretRef"] == "gateway-token") // pragma: allowlist secret
let localRoot = AppState._testSyncedGatewayRoot(
currentRoot: sshRoot,
connectionMode: .local,
remoteTransport: .ssh,
remoteTarget: "",
remoteIdentity: "",
remoteUrl: "",
remoteToken: "",
remoteTokenDirty: false)
draft: .init(
connectionMode: .local,
remoteTransport: .ssh,
remoteTarget: "",
remoteIdentity: "",
remoteUrl: "",
remoteToken: "",
remoteTokenDirty: false))
let localGateway = localRoot["gateway"] as? [String: Any]
let localRemote = localGateway?["remote"] as? [String: Any]
#expect(localGateway?["mode"] as? String == "local")
@@ -84,13 +88,14 @@ struct AppStateRemoteConfigTests {
"$secretRef": "gateway-token", // pragma: allowlist secret
],
],
transport: .direct,
remoteUrl: "wss://gateway.example",
remoteHost: nil,
remoteTarget: "",
remoteIdentity: "",
remoteToken: " fresh-token ",
remoteTokenDirty: true)
draft: .init(
transport: .direct,
remoteUrl: "wss://gateway.example",
remoteHost: nil,
remoteTarget: "",
remoteIdentity: "",
remoteToken: " fresh-token ",
remoteTokenDirty: true))
#expect(remote["token"] as? String == "fresh-token")
}
@@ -105,24 +110,26 @@ struct AppStateRemoteConfigTests {
let preserved = AppState._testUpdatedRemoteGatewayConfig(
current: current,
transport: .direct,
remoteUrl: "wss://gateway.example",
remoteHost: nil,
remoteTarget: "",
remoteIdentity: "",
remoteToken: "",
remoteTokenDirty: false)
draft: .init(
transport: .direct,
remoteUrl: "wss://gateway.example",
remoteHost: nil,
remoteTarget: "",
remoteIdentity: "",
remoteToken: "",
remoteTokenDirty: false))
#expect((preserved["token"] as? [String: String])?["$secretRef"] == "gateway-token") // pragma: allowlist secret
let cleared = AppState._testUpdatedRemoteGatewayConfig(
current: current,
transport: .direct,
remoteUrl: "wss://gateway.example",
remoteHost: nil,
remoteTarget: "",
remoteIdentity: "",
remoteToken: " ",
remoteTokenDirty: true)
draft: .init(
transport: .direct,
remoteUrl: "wss://gateway.example",
remoteHost: nil,
remoteTarget: "",
remoteIdentity: "",
remoteToken: " ",
remoteTokenDirty: true))
#expect((cleared["token"] as? String) == nil)
}
}

View File

@@ -2481,6 +2481,24 @@ public struct ChannelsStatusResult: Codable, Sendable {
}
}
public struct ChannelsStartParams: Codable, Sendable {
public let channel: String
public let accountid: String?
public init(
channel: String,
accountid: String?)
{
self.channel = channel
self.accountid = accountid
}
private enum CodingKeys: String, CodingKey {
case channel
case accountid = "accountId"
}
}
public struct ChannelsLogoutParams: Codable, Sendable {
public let channel: String
public let accountid: String?

View File

@@ -1,4 +1,4 @@
17d43e3c5dd6ac09fa6c7954e6923d2e0fba767483bac0a2b257fb7ec736f8a4 config-baseline.json
fdb7867bbc18792d3645ea36c31b64425d6f19c0b19a7460564f67eb97c0a71e config-baseline.core.json
99bb34fcf83ba6bb50a3fc11f170bd379bee5728b0938707fc39ebd7638e12eb config-baseline.channel.json
b695cb31b4c0cf1d31f842f2892e99cc3ff8d84263ae72b72977cae844b81d6e config-baseline.plugin.json
cc473bcd00e63c3d3f351e4de1ceb390aae88dddce8616929e98a9d94412b1b9 config-baseline.json
7956c319e82d288d496a51cb2ff4485ab72ef4900cb089f99e1df8b9ef3bfb73 config-baseline.core.json
cd467228990cdbdebde2fa87d8b1384b94c149e791f2e67250bf17b13162d4a1 config-baseline.channel.json
17a73724e5082b3aa846c220d38115916fb6003887439e6794510a99fc73f7de config-baseline.plugin.json

View File

@@ -1,2 +1,2 @@
d08f0e793e66192fdbc377183ce0d94adcbec53cf334522bce8c0c457b90b0a8 plugin-sdk-api-baseline.json
924f20b350a9f1997e95b3d7249cbb6720c9576c63e6c0c15cca0164734fd93d plugin-sdk-api-baseline.jsonl
f135ddc1802b7f8b2d29bf495fd0ac1f497a89bab8164ca8c7c8f18efc010e6e plugin-sdk-api-baseline.json
a47d06095ec5c3701a94888a11e89700d8a8511db46fa3122fb9407e160707b6 plugin-sdk-api-baseline.jsonl

View File

@@ -59,6 +59,14 @@
"source": "Feishu",
"target": "Feishu"
},
{
"source": "WeChat",
"target": "微信"
},
{
"source": "Weixin",
"target": "微信"
},
{
"source": "Mattermost",
"target": "Mattermost"
@@ -366,5 +374,17 @@
{
"source": "Testing",
"target": "测试"
},
{
"source": "/gateway/configuration#strict-validation",
"target": "/gateway/configuration#strict-validation"
},
{
"source": "/gateway/configuration#config-hot-reload",
"target": "/gateway/configuration#config-hot-reload"
},
{
"source": "/cli/config",
"target": "/cli/config"
}
]

View File

@@ -25,6 +25,7 @@ openclaw cron add \
# Check your jobs
openclaw cron list
openclaw cron show <job-id>
# See run history
openclaw cron runs --id <job-id>
@@ -33,7 +34,9 @@ openclaw cron runs --id <job-id>
## How cron works
- Cron runs **inside the Gateway** process (not inside the model).
- Jobs persist at `~/.openclaw/cron/jobs.json` so restarts do not lose schedules.
- Job definitions persist at `~/.openclaw/cron/jobs.json` so restarts do not lose schedules.
- Runtime execution state persists next to it in `~/.openclaw/cron/jobs-state.json`. If you track cron definitions in git, track `jobs.json` and gitignore `jobs-state.json`.
- After the split, older OpenClaw versions can read `jobs.json` but may treat jobs as fresh because runtime fields now live in `jobs-state.json`.
- All cron executions create [background task](/automation/tasks) records.
- One-shot jobs (`--at`) auto-delete after success by default.
- Isolated cron runs best-effort close tracked browser tabs/processes for their `cron:<jobId>` session when the run completes, so detached browser automation does not leave orphaned processes behind.
@@ -123,22 +126,19 @@ retries, cron aborts instead of looping forever.
## Delivery and output
| Mode | What happens |
| ---------- | -------------------------------------------------------- |
| `announce` | Deliver summary to target channel (default for isolated) |
| `webhook` | POST finished event payload to a URL |
| `none` | Internal only, no delivery |
| Mode | What happens |
| ---------- | ------------------------------------------------------------------- |
| `announce` | Fallback-deliver final text to the target if the agent did not send |
| `webhook` | POST finished event payload to a URL |
| `none` | No runner fallback delivery |
Use `--announce --channel telegram --to "-1001234567890"` for channel delivery. For Telegram forum topics, use `-1001234567890:topic:123`. Slack/Discord/Mattermost targets should use explicit prefixes (`channel:<id>`, `user:<id>`).
For cron-owned isolated jobs, the runner owns the final delivery path. The
agent is prompted to return a plain-text summary, and that summary is then sent
through `announce`, `webhook`, or kept internal for `none`. `--no-deliver`
does not hand delivery back to the agent; it keeps the run internal.
If the original task explicitly says to message some external recipient, the
agent should note who/where that message should go in its output instead of
trying to send it directly.
For isolated jobs, chat delivery is shared. If a chat route is available, the
agent can use the `message` tool even when the job uses `--no-deliver`. If the
agent sends to the configured/current target, OpenClaw skips the fallback
announce. Otherwise `announce`, `webhook`, and `none` only control what the
runner does with the final reply after the agent turn.
Failure notifications follow a separate destination path:
@@ -317,6 +317,9 @@ gog gmail watch start \
# List all jobs
openclaw cron list
# Show one job, including resolved delivery route
openclaw cron show <jobId>
# Edit a job
openclaw cron edit <jobId> --message "Updated prompt" --model "opus"
@@ -368,6 +371,10 @@ Model override note:
}
```
The runtime state sidecar is derived from `cron.store`: a `.json` store such as
`~/clawd/cron/jobs.json` uses `~/clawd/cron/jobs-state.json`, while a store path
without a `.json` suffix appends `-state.json`.
Disable cron: `cron.enabled: false` or `OPENCLAW_SKIP_CRON=1`.
**One-shot retry**: transient errors (rate limit, overload, network, server error) retry up to 3 times with exponential backoff. Permanent errors disable immediately.
@@ -400,15 +407,15 @@ openclaw doctor
### Cron fired but no delivery
- Delivery mode is `none` means no external message is expected.
- Delivery mode `none` means no runner fallback send is expected. The agent can
still send directly with the `message` tool when a chat route is available.
- Delivery target missing/invalid (`channel`/`to`) means outbound was skipped.
- Channel auth errors (`unauthorized`, `Forbidden`) mean delivery was blocked by credentials.
- If the isolated run returns only the silent token (`NO_REPLY` / `no_reply`),
OpenClaw suppresses direct outbound delivery and also suppresses the fallback
queued summary path, so nothing is posted back to chat.
- For cron-owned isolated jobs, do not expect the agent to use the message tool
as a fallback. The runner owns final delivery; `--no-deliver` keeps it
internal instead of allowing a direct send.
- If the agent should message the user itself, check that the job has a usable
route (`channel: "last"` with a previous chat, or an explicit channel/target).
### Timezone gotchas

View File

@@ -8,7 +8,7 @@ title: "Hooks"
# Hooks
Hooks are small scripts that run when something happens inside the Gateway. They are automatically discovered from directories and can be inspected with `openclaw hooks`.
Hooks are small scripts that run when something happens inside the Gateway. They can be discovered from directories and inspected with `openclaw hooks`. The Gateway loads internal hooks only after you enable hooks or configure at least one hook entry, hook pack, legacy handler, or extra hook directory.
There are two kinds of hooks in OpenClaw:
@@ -139,6 +139,8 @@ Hooks are discovered from these directories, in order of increasing override pre
Workspace hooks can add new hook names but cannot override bundled, managed, or plugin-provided hooks with the same name.
The Gateway skips internal hook discovery on startup until internal hooks are configured. Enable a bundled or managed hook with `openclaw hooks enable <name>`, install a hook pack, or set `hooks.internal.enabled=true` to opt in. When you enable one named hook, the Gateway loads only that hook's handler; `hooks.internal.enabled=true`, extra hook directories, and legacy handlers opt into broad discovery.
### Hook packs
Hook packs are npm packages that export hooks via `openclaw.hooks` in `package.json`. Install with:

View File

@@ -301,7 +301,7 @@ See [Task Flow](/automation/taskflow) for details.
### Tasks and cron
A cron job **definition** lives in `~/.openclaw/cron/jobs.json`. **Every** cron execution creates a task record — both main-session and isolated. Main-session cron tasks default to `silent` notify policy so they track without generating notifications.
A cron job **definition** lives in `~/.openclaw/cron/jobs.json`; runtime execution state lives beside it in `~/.openclaw/cron/jobs-state.json`. **Every** cron execution creates a task record — both main-session and isolated. Main-session cron tasks default to `silent` notify policy so they track without generating notifications.
See [Cron Jobs](/automation/cron-jobs).

View File

@@ -217,6 +217,54 @@ Per-group configuration:
- Uses `allowFrom` and `groupAllowFrom` to determine command authorization.
- Authorized senders can run control commands even without mentioning in groups.
### Per-group system prompt
Each entry under `channels.bluebubbles.groups.*` accepts an optional `systemPrompt` string. The value is injected into the agent's system prompt on every turn that handles a message in that group, so you can set per-group persona or behavioral rules without editing agent prompts:
```json5
{
channels: {
bluebubbles: {
groups: {
"iMessage;-;chat123": {
systemPrompt: "Keep responses under 3 sentences. Mirror the group's casual tone.",
},
},
},
},
}
```
The key matches whatever BlueBubbles reports as `chatGuid` / `chatIdentifier` / numeric `chatId` for the group, and a `"*"` wildcard entry provides a default for every group without an exact match (same pattern used by `requireMention` and per-group tool policies). Exact matches always win over the wildcard. DMs ignore this field; use agent-level or account-level prompt customization instead.
#### Worked example: threaded replies and tapback reactions (Private API)
With the BlueBubbles Private API enabled, inbound messages arrive with short message IDs (for example `[[reply_to:5]]`) and the agent can call `action=reply` to thread into a specific message or `action=react` to drop a tapback. A per-group `systemPrompt` is a reliable way to keep the agent choosing the right tool:
```json5
{
channels: {
bluebubbles: {
groups: {
"iMessage;+;chat-family": {
systemPrompt: [
"When replying in this group, always call action=reply with the",
"[[reply_to:N]] messageId from context so your response threads",
"under the triggering message. Never send a new unlinked message.",
"",
"For short acknowledgements ('ok', 'got it', 'on it'), use",
"action=react with an appropriate tapback emoji (❤️, 👍, 😂, ‼️, ❓)",
"instead of sending a text reply.",
].join(" "),
},
},
},
},
}
```
Tapback reactions and threaded replies both require the BlueBubbles Private API; see [Advanced actions](#advanced-actions) and [Message IDs](#message-ids-short-vs-full) for the underlying mechanics.
## ACP conversation bindings
BlueBubbles chats can be turned into durable ACP workspaces without changing the transport layer.
@@ -345,6 +393,103 @@ Use full IDs for durable automations and storage:
See [Configuration](/gateway/configuration) for template variables.
## Coalescing split-send DMs (command + URL in one composition)
When a user types a command and a URL together in iMessage — e.g. `Dump https://example.com/article` — Apple splits the send into **two separate webhook deliveries**:
1. A text message (`"Dump"`).
2. A URL-preview balloon (`"https://..."`) with OG-preview images as attachments.
The two webhooks arrive at OpenClaw ~0.8-2.0 s apart on most setups. Without coalescing, the agent receives the command alone on turn 1, replies (often "send me the URL"), and only sees the URL on turn 2 — at which point the command context is already lost.
`channels.bluebubbles.coalesceSameSenderDms` opts a DM into merging consecutive same-sender webhooks into a single agent turn. Group chats continue to key per-message so multi-user turn structure is preserved.
### When to enable
Enable when:
- You ship skills that expect `command + payload` in one message (dump, paste, save, queue, etc.).
- Your users paste URLs, images, or long content alongside commands.
- You can accept the added DM turn latency (see below).
Leave disabled when:
- You need minimum command latency for single-word DM triggers.
- All your flows are one-shot commands without payload follow-ups.
### Enabling
```json5
{
channels: {
bluebubbles: {
coalesceSameSenderDms: true, // opt in (default: false)
},
},
}
```
With the flag on and no explicit `messages.inbound.byChannel.bluebubbles`, the debounce window widens to **2500 ms** (the default for non-coalescing is 500 ms). The wider window is required — Apple's split-send cadence of 0.8-2.0 s does not fit in the tighter default.
To tune the window yourself:
```json5
{
messages: {
inbound: {
byChannel: {
// 2500 ms works for most setups; raise to 4000 ms if your Mac is slow
// or under memory pressure (observed gap can stretch past 2 s then).
bluebubbles: 2500,
},
},
},
}
```
### Trade-offs
- **Added latency for DM control commands.** With the flag on, DM control-command messages (like `Dump`, `Save`, etc.) now wait up to the debounce window before dispatching, in case a payload webhook is coming. Group-chat commands keep instant dispatch.
- **Merged output is bounded** — merged text caps at 4000 chars with an explicit `…[truncated]` marker; attachments cap at 20; source entries cap at 10 (first-plus-latest retained beyond that). Every source `messageId` still reaches inbound-dedupe so a later MessagePoller replay of any individual event is recognized as a duplicate.
- **Opt-in, per-channel.** Other channels (Telegram, WhatsApp, Slack, …) are unaffected.
### Scenarios and what the agent sees
| User composes | Apple delivers | Flag off (default) | Flag on + 2500 ms window |
| ------------------------------------------------------------------ | ------------------------- | --------------------------------------- | ----------------------------------------------------------------------- |
| `Dump https://example.com` (one send) | 2 webhooks ~1 s apart | Two agent turns: "Dump" alone, then URL | One turn: merged text `Dump https://example.com` |
| `Save this 📎image.jpg caption` (attachment + text) | 2 webhooks | Two turns | One turn: text + image |
| `/status` (standalone command) | 1 webhook | Instant dispatch | **Wait up to window, then dispatch** |
| URL pasted alone | 1 webhook | Instant dispatch | Instant dispatch (only one entry in bucket) |
| Text + URL sent as two deliberate separate messages, minutes apart | 2 webhooks outside window | Two turns | Two turns (window expires between them) |
| Rapid flood (>10 small DMs inside window) | N webhooks | N turns | One turn, bounded output (first + latest, text/attachment caps applied) |
### Split-send coalescing troubleshooting
If the flag is on and split-sends still arrive as two turns, check each layer:
1. **Config actually loaded.**
```
grep coalesceSameSenderDms ~/.openclaw/openclaw.json
```
Then `openclaw gateway restart` — the flag is read at debouncer-registry creation.
2. **Debounce window wide enough for your setup.** Look at the BlueBubbles server log under `~/Library/Logs/bluebubbles-server/main.log`:
```
grep -E "Dispatching event to webhook" main.log | tail -20
```
Measure the gap between the `"Dump"`-style text dispatch and the `"https://..."; Attachments:` dispatch that follows. Raise `messages.inbound.byChannel.bluebubbles` to comfortably cover that gap.
3. **Session JSONL timestamps ≠ webhook arrival.** Session event timestamps (`~/.openclaw/agents/<id>/sessions/*.jsonl`) reflect when the gateway hands a message to the agent, **not** when the webhook arrived. A queued-second message tagged `[Queued messages while agent was busy]` means the first turn was still running when the second webhook arrived — the coalesce bucket had already flushed. Tune the window against the BB server log, not the session log.
4. **Memory pressure slowing reply dispatch.** On smaller machines (8 GB), agent turns can take long enough that the coalesce bucket flushes before the reply completes, and the URL lands as a queued second turn. Check `memory_pressure` and `ps -o rss -p $(pgrep openclaw-gateway)`; if the gateway is over ~500 MB RSS and the compressor is active, close other heavy processes or bump to a larger host.
5. **Reply-quote sends are a different path.** If the user tapped `Dump` as a **reply** to an existing URL-balloon (iMessage shows a "1 Reply" badge on the Dump bubble), the URL lives in `replyToBody`, not in a second webhook. Coalescing does not apply — that's a skill/prompt concern, not a debouncer concern.
## Block streaming
Control whether responses are sent as a single message or streamed in blocks:
@@ -384,9 +529,11 @@ Provider options:
- `channels.bluebubbles.sendReadReceipts`: Send read receipts (default: `true`).
- `channels.bluebubbles.blockStreaming`: Enable block streaming (default: `false`; required for streaming replies).
- `channels.bluebubbles.textChunkLimit`: Outbound chunk size in chars (default: 4000).
- `channels.bluebubbles.sendTimeoutMs`: Per-request timeout in ms for outbound text sends via `/api/v1/message/text` (default: 30000). Raise on macOS 26 setups where Private API iMessage sends can stall for 60+ seconds inside the iMessage framework; for example `45000` or `60000`. Probes, chat lookups, reactions, edits, and health checks currently keep the shorter 10s default; broadening coverage to reactions and edits is planned as a follow-up. Per-account override: `channels.bluebubbles.accounts.<accountId>.sendTimeoutMs`.
- `channels.bluebubbles.chunkMode`: `length` (default) splits only when exceeding `textChunkLimit`; `newline` splits on blank lines (paragraph boundaries) before length chunking.
- `channels.bluebubbles.mediaMaxMb`: Inbound/outbound media cap in MB (default: 8).
- `channels.bluebubbles.mediaLocalRoots`: Explicit allowlist of absolute local directories permitted for outbound local media paths. Local path sends are denied by default unless this is configured. Per-account override: `channels.bluebubbles.accounts.<accountId>.mediaLocalRoots`.
- `channels.bluebubbles.coalesceSameSenderDms`: Merge consecutive same-sender DM webhooks into one agent turn so Apple's text+URL split-send arrives as a single message (default: `false`). See [Coalescing split-send DMs](#coalescing-split-send-dms-command--url-in-one-composition) for scenarios, window tuning, and trade-offs. Widens the default inbound debounce window from 500 ms to 2500 ms when enabled without an explicit `messages.inbound.byChannel.bluebubbles`.
- `channels.bluebubbles.historyLimit`: Max group messages for context (0 disables).
- `channels.bluebubbles.dmHistoryLimit`: DM history limit.
- `channels.bluebubbles.actions`: Enable/disable specific actions.
@@ -422,6 +569,7 @@ Prefer `chat_guid` for stable routing:
- Edit/unsend require macOS 13+ and a compatible BlueBubbles server version. On macOS 26 (Tahoe), edit is currently broken due to private API changes.
- Group icon updates can be flaky on macOS 26 (Tahoe): the API may return success but the new icon does not sync.
- OpenClaw auto-hides known-broken actions based on the BlueBubbles server's macOS version. If edit still appears on macOS 26 (Tahoe), disable it manually with `channels.bluebubbles.actions.edit=false`.
- `coalesceSameSenderDms` enabled but split-sends (e.g. `Dump` + URL) still arrive as two turns: see the [split-send coalescing troubleshooting](#split-send-coalescing-troubleshooting) checklist — common causes are too-tight debounce window, session-log timestamps misread as webhook arrival, or a reply-quote send (which uses `replyToBody`, not a second webhook).
- For status/health info: `openclaw status --all` or `openclaw status --deep`.
For general channel workflow reference, see [Channels](/channels) and the [Plugins](/tools/plugin) guide.

View File

@@ -82,12 +82,12 @@ If you want...
Yes — this works well if your “personal” traffic is **DMs** and your “public” traffic is **groups**.
Why: in single-agent mode, DMs typically land in the **main** session key (`agent:main:main`), while groups always use **non-main** session keys (`agent:main:<channel>:group:<id>`). If you enable sandboxing with `mode: "non-main"`, those group sessions run in Docker while your main DM session stays on-host.
Why: in single-agent mode, DMs typically land in the **main** session key (`agent:main:main`), while groups always use **non-main** session keys (`agent:main:<channel>:group:<id>`). If you enable sandboxing with `mode: "non-main"`, those group sessions run in the configured sandbox backend while your main DM session stays on-host. Docker is the default backend if you do not choose one.
This gives you one agent “brain” (shared workspace + memory), but two execution postures:
- **DMs**: full tools (host)
- **Groups**: sandbox + restricted tools (Docker)
- **Groups**: sandbox + restricted tools
> If you need truly separate workspaces/personas (“personal” and “public” must never mix), use a second agent + bindings. See [Multi-Agent Routing](/concepts/multi-agent).

View File

@@ -34,7 +34,7 @@ Text is supported everywhere; media and reactions vary by channel.
- [Twitch](/channels/twitch) — Twitch chat via IRC connection (bundled plugin).
- [Voice Call](/plugins/voice-call) — Telephony via Plivo or Twilio (plugin, installed separately).
- [WebChat](/web/webchat) — Gateway WebChat UI over WebSocket.
- [WeChat](https://www.npmjs.com/package/@tencent-weixin/openclaw-weixin) — Tencent iLink Bot plugin via QR login; private chats only.
- [WeChat](/channels/wechat) — Tencent iLink Bot plugin via QR login; private chats only (external plugin).
- [WhatsApp](/channels/whatsapp) — Most popular; uses Baileys and requires QR pairing.
- [Zalo](/channels/zalo) — Zalo Bot API; Vietnam's popular messenger (bundled plugin).
- [Zalo Personal](/channels/zalouser) — Zalo personal account via QR login (bundled plugin).

View File

@@ -1014,7 +1014,7 @@ Live directory lookup uses the logged-in Matrix account:
- `allowBots`: allow messages from other configured OpenClaw Matrix accounts (`true` or `"mentions"`).
- `groupPolicy`: `open`, `allowlist`, or `disabled`.
- `contextVisibility`: supplemental room-context visibility mode (`all`, `allowlist`, `allowlist_quote`).
- `groupAllowFrom`: allowlist of user IDs for room traffic. Entries should be full Matrix user IDs; unresolved names are ignored at runtime.
- `groupAllowFrom`: allowlist of user IDs for room traffic. Full Matrix user IDs are safest; exact directory matches are resolved at startup and when the allowlist changes while the monitor is running. Unresolved names are ignored.
- `historyLimit`: max room messages to include as group history context. Falls back to `messages.groupChat.historyLimit`; if both are unset, the effective default is `0`. Set `0` to disable.
- `replyToMode`: `off`, `first`, `all`, or `batched`.
- `markdown`: optional Markdown rendering configuration for outbound Matrix text.
@@ -1035,7 +1035,7 @@ Live directory lookup uses the logged-in Matrix account:
- `autoJoinAllowlist`: rooms/aliases allowed when `autoJoin` is `allowlist`. Alias entries are resolved to room IDs during invite handling; OpenClaw does not trust alias state claimed by the invited room.
- `dm`: DM policy block (`enabled`, `policy`, `allowFrom`, `sessionScope`, `threadReplies`).
- `dm.policy`: controls DM access after OpenClaw has joined the room and classified it as a DM. It does not change whether an invite is auto-joined.
- `dm.allowFrom`: entries should be full Matrix user IDs unless you already resolved them through live directory lookup.
- `dm.allowFrom`: allowlist of user IDs for DM traffic. Full Matrix user IDs are safest; exact directory matches are resolved at startup and when the allowlist changes while the monitor is running. Unresolved names are ignored.
- `dm.sessionScope`: `per-user` (default) or `per-room`. Use `per-room` when you want each Matrix DM room to keep separate context even if the peer is the same.
- `dm.threadReplies`: DM-only thread policy override (`off`, `inbound`, `always`). It overrides the top-level `threadReplies` setting for both reply placement and session isolation in DMs.
- `execApprovals`: Matrix-native exec approval delivery (`enabled`, `approvers`, `target`, `agentFilter`, `sessionFilter`).

View File

@@ -100,6 +100,12 @@ If the same device retries with different auth details (for example different
role/scopes/public key), the previous pending request is superseded and a new
`requestId` is created.
Important: an already paired device does not get broader access silently. If it
reconnects asking for more scopes or a broader role, OpenClaw keeps the
existing approval as-is and creates a fresh pending upgrade request. Use
`openclaw devices list` to compare the currently approved access with the newly
requested access before you approve.
### Node pairing state storage
Stored under `~/.openclaw/devices/`:

View File

@@ -327,7 +327,7 @@ Surface different features that extend the above defaults.
{
"command": "/think",
"description": "Set the thinking level",
"usage_hint": "<off|minimal|low|medium|high|xhigh>"
"usage_hint": "<level>"
},
{
"command": "/verbose",
@@ -448,7 +448,7 @@ Surface different features that extend the above defaults.
{
"command": "/think",
"description": "Set the thinking level",
"usage_hint": "<off|minimal|low|medium|high|xhigh>",
"usage_hint": "<level>",
"url": "https://gateway-host.example.com/slack/events"
},
{

View File

@@ -115,7 +115,7 @@ Token resolution order is account-aware. In practice, config values win over env
`channels.telegram.allowFrom` accepts numeric Telegram user IDs. `telegram:` / `tg:` prefixes are accepted and normalized.
`dmPolicy: "allowlist"` with empty `allowFrom` blocks all DMs and is rejected by config validation.
Onboarding accepts `@username` input and resolves it to numeric IDs.
Setup asks for numeric user IDs only.
If you upgraded and your config contains `@username` allowlist entries, run `openclaw doctor --fix` to resolve them (best-effort; requires a Telegram bot token).
If you previously relied on pairing-store allowlist files, `openclaw doctor --fix` can recover entries into `channels.telegram.allowFrom` in allowlist flows (for example when `dmPolicy: "allowlist"` has no explicit IDs yet).
@@ -259,6 +259,7 @@ curl "https://api.telegram.org/bot<bot_token>/getUpdates"
- Group sessions are isolated by group ID. Forum topics append `:topic:<threadId>` to keep topics isolated.
- DM messages can carry `message_thread_id`; OpenClaw routes them with thread-aware session keys and preserves thread ID for replies.
- Long polling uses grammY runner with per-chat/per-thread sequencing. Overall runner sink concurrency uses `agents.defaults.maxConcurrent`.
- Long-polling watchdog restarts trigger after 120 seconds without completed `getUpdates` liveness by default. Increase `channels.telegram.pollingStallThresholdMs` only if your deployment still sees false polling-stall restarts during long-running work. The value is in milliseconds and is allowed from `30000` to `600000`; per-account overrides are supported.
- Telegram Bot API has no read-receipt support (`sendReadReceipts` does not apply).
## Feature reference
@@ -766,6 +767,7 @@ curl "https://api.telegram.org/bot<bot_token>/getUpdates"
- `channels.telegram.chunkMode="newline"` prefers paragraph boundaries (blank lines) before length splitting.
- `channels.telegram.mediaMaxMb` (default 100) caps inbound and outbound Telegram media size.
- `channels.telegram.timeoutSeconds` overrides Telegram API client timeout (if unset, grammY default applies).
- `channels.telegram.pollingStallThresholdMs` defaults to `120000`; tune between `30000` and `600000` only for false-positive polling-stall restarts.
- group context history uses `channels.telegram.historyLimit` or `messages.groupChat.historyLimit` (default 50); `0` disables.
- reply/quote/forward supplemental context is currently passed as received.
- Telegram allowlists primarily gate who can trigger the agent, not a full supplemental-context redaction boundary.
@@ -917,6 +919,8 @@ Per-account, per-group, and per-topic overrides are supported (same inheritance
- Node 22+ + custom fetch/proxy can trigger immediate abort behavior if AbortSignal types mismatch.
- Some hosts resolve `api.telegram.org` to IPv6 first; broken IPv6 egress can cause intermittent Telegram API failures.
- If logs include `TypeError: fetch failed` or `Network request for 'getUpdates' failed!`, OpenClaw now retries these as recoverable network errors.
- If logs include `Polling stall detected`, OpenClaw restarts polling and rebuilds the Telegram transport after 120 seconds without completed long-poll liveness by default.
- Increase `channels.telegram.pollingStallThresholdMs` only when long-running `getUpdates` calls are healthy but your host still reports false polling-stall restarts. Persistent stalls usually point to proxy, DNS, IPv6, or TLS egress issues between the host and `api.telegram.org`.
- On VPS hosts with unstable direct egress/TLS, route Telegram API calls through `channels.telegram.proxy`:
```yaml
@@ -1055,7 +1059,7 @@ Telegram-specific high-signal fields:
- threading/replies: `replyToMode`
- streaming: `streaming` (preview), `blockStreaming`
- formatting/delivery: `textChunkLimit`, `chunkMode`, `linkPreview`, `responsePrefix`
- media/network: `mediaMaxMb`, `timeoutSeconds`, `retry`, `network.autoSelectFamily`, `network.dangerouslyAllowPrivateNetwork`, `proxy`
- media/network: `mediaMaxMb`, `timeoutSeconds`, `pollingStallThresholdMs`, `retry`, `network.autoSelectFamily`, `network.dangerouslyAllowPrivateNetwork`, `proxy`
- webhook: `webhookUrl`, `webhookSecret`, `webhookPath`, `webhookHost`
- actions/capabilities: `capabilities.inlineButtons`, `actions.sendMessage|editMessage|deleteMessage|reactions|sticker`
- reactions: `reactionNotifications`, `reactionLevel`

View File

@@ -25,7 +25,8 @@ openclaw channels status --probe
Healthy baseline:
- `Runtime: running`
- `RPC probe: ok`
- `Connectivity probe: ok`
- `Capability: read-only`, `write-capable`, or `admin-capable`
- Channel probe shows transport connected and, where supported, `works` or `audit ok`
## WhatsApp
@@ -44,13 +45,14 @@ Full troubleshooting: [/channels/whatsapp#troubleshooting](/channels/whatsapp#tr
### Telegram failure signatures
| Symptom | Fastest check | Fix |
| ----------------------------------- | ----------------------------------------------- | --------------------------------------------------------------------------- |
| `/start` but no usable reply flow | `openclaw pairing list telegram` | Approve pairing or change DM policy. |
| Bot online but group stays silent | Verify mention requirement and bot privacy mode | Disable privacy mode for group visibility or mention bot. |
| Send failures with network errors | Inspect logs for Telegram API call failures | Fix DNS/IPv6/proxy routing to `api.telegram.org`. |
| `setMyCommands` rejected at startup | Inspect logs for `BOT_COMMANDS_TOO_MUCH` | Reduce plugin/skill/custom Telegram commands or disable native menus. |
| Upgraded and allowlist blocks you | `openclaw security audit` and config allowlists | Run `openclaw doctor --fix` or replace `@username` with numeric sender IDs. |
| Symptom | Fastest check | Fix |
| ----------------------------------- | ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------- |
| `/start` but no usable reply flow | `openclaw pairing list telegram` | Approve pairing or change DM policy. |
| Bot online but group stays silent | Verify mention requirement and bot privacy mode | Disable privacy mode for group visibility or mention bot. |
| Send failures with network errors | Inspect logs for Telegram API call failures | Fix DNS/IPv6/proxy routing to `api.telegram.org`. |
| Polling stalls or reconnects slowly | `openclaw logs --follow` for polling diagnostics | Upgrade; if restarts are false positives, tune `pollingStallThresholdMs`. Persistent stalls still point to proxy/DNS/IPv6. |
| `setMyCommands` rejected at startup | Inspect logs for `BOT_COMMANDS_TOO_MUCH` | Reduce plugin/skill/custom Telegram commands or disable native menus. |
| Upgraded and allowlist blocks you | `openclaw security audit` and config allowlists | Run `openclaw doctor --fix` or replace `@username` with numeric sender IDs. |
Full troubleshooting: [/channels/telegram#troubleshooting](/channels/telegram#troubleshooting)

168
docs/channels/wechat.md Normal file
View File

@@ -0,0 +1,168 @@
---
summary: "WeChat channel setup through the external openclaw-weixin plugin"
read_when:
- You want to connect OpenClaw to WeChat or Weixin
- You are installing or troubleshooting the openclaw-weixin channel plugin
- You need to understand how external channel plugins run beside the Gateway
title: "WeChat"
---
# WeChat
OpenClaw connects to WeChat through Tencent's external
`@tencent-weixin/openclaw-weixin` channel plugin.
Status: external plugin. Direct chats and media are supported. Group chats are not
advertised by the current plugin capability metadata.
## Naming
- **WeChat** is the user-facing name in these docs.
- **Weixin** is the name used by Tencent's package and by the plugin id.
- `openclaw-weixin` is the OpenClaw channel id.
- `@tencent-weixin/openclaw-weixin` is the npm package.
Use `openclaw-weixin` in CLI commands and config paths.
## How it works
The WeChat code does not live in the OpenClaw core repo. OpenClaw provides the
generic channel plugin contract, and the external plugin provides the
WeChat-specific runtime:
1. `openclaw plugins install` installs `@tencent-weixin/openclaw-weixin`.
2. The Gateway discovers the plugin manifest and loads the plugin entrypoint.
3. The plugin registers channel id `openclaw-weixin`.
4. `openclaw channels login --channel openclaw-weixin` starts QR login.
5. The plugin stores account credentials under the OpenClaw state directory.
6. When the Gateway starts, the plugin starts its Weixin monitor for each
configured account.
7. Inbound WeChat messages are normalized through the channel contract, routed to
the selected OpenClaw agent, and sent back through the plugin outbound path.
That separation matters: OpenClaw core should stay channel-agnostic. WeChat login,
Tencent iLink API calls, media upload/download, context tokens, and account
monitoring are owned by the external plugin.
## Install
Quick install:
```bash
npx -y @tencent-weixin/openclaw-weixin-cli install
```
Manual install:
```bash
openclaw plugins install "@tencent-weixin/openclaw-weixin"
openclaw config set plugins.entries.openclaw-weixin.enabled true
```
Restart the Gateway after install:
```bash
openclaw gateway restart
```
## Login
Run QR login on the same machine that runs the Gateway:
```bash
openclaw channels login --channel openclaw-weixin
```
Scan the QR code with WeChat on your phone and confirm the login. The plugin saves
the account token locally after a successful scan.
To add another WeChat account, run the same login command again. For multiple
accounts, isolate direct-message sessions by account, channel, and sender:
```bash
openclaw config set session.dmScope per-account-channel-peer
```
## Access control
Direct messages use the normal OpenClaw pairing and allowlist model for channel
plugins.
Approve new senders:
```bash
openclaw pairing list openclaw-weixin
openclaw pairing approve openclaw-weixin <CODE>
```
For the full access-control model, see [Pairing](/channels/pairing).
## Compatibility
The plugin checks the host OpenClaw version at startup.
| Plugin line | OpenClaw version | npm tag |
| ----------- | ----------------------- | -------- |
| `2.x` | `>=2026.3.22` | `latest` |
| `1.x` | `>=2026.1.0 <2026.3.22` | `legacy` |
If the plugin reports that your OpenClaw version is too old, either update
OpenClaw or install the legacy plugin line:
```bash
openclaw plugins install @tencent-weixin/openclaw-weixin@legacy
```
## Sidecar process
The WeChat plugin can run helper work beside the Gateway while it monitors the
Tencent iLink API. In issue #68451, that helper path exposed a bug in OpenClaw's
generic stale-Gateway cleanup: a child process could try to clean up the parent
Gateway process, causing restart loops under process managers such as systemd.
Current OpenClaw startup cleanup excludes the current process and its ancestors,
so a channel helper must not kill the Gateway that launched it. This fix is
generic; it is not a WeChat-specific path in core.
## Troubleshooting
Check install and status:
```bash
openclaw plugins list
openclaw channels status --probe
openclaw --version
```
If the channel shows as installed but does not connect, confirm that the plugin is
enabled and restart:
```bash
openclaw config set plugins.entries.openclaw-weixin.enabled true
openclaw gateway restart
```
If the Gateway restarts repeatedly after enabling WeChat, update both OpenClaw and
the plugin:
```bash
npm view @tencent-weixin/openclaw-weixin version
openclaw plugins install "@tencent-weixin/openclaw-weixin" --force
openclaw gateway restart
```
Temporary disable:
```bash
openclaw config set plugins.entries.openclaw-weixin.enabled false
openclaw gateway restart
```
## Related docs
- Channel overview: [Chat Channels](/channels)
- Pairing: [Pairing](/channels/pairing)
- Channel routing: [Channel Routing](/channels/channel-routing)
- Plugin architecture: [Plugin Architecture](/plugins/architecture)
- Channel plugin SDK: [Channel Plugin SDK](/plugins/sdk-channel-plugins)
- External package: [@tencent-weixin/openclaw-weixin](https://www.npmjs.com/package/@tencent-weixin/openclaw-weixin)

View File

@@ -12,57 +12,71 @@ The CI runs on every push to `main` and every pull request. It uses smart scopin
## Job Overview
| Job | Purpose | When it runs |
| ------------------------ | --------------------------------------------------------------------------------------- | ----------------------------------- |
| `preflight` | Detect docs-only changes, changed scopes, changed extensions, and build the CI manifest | Always on non-draft pushes and PRs |
| `security-fast` | Private key detection, workflow audit via `zizmor`, production dependency audit | Always on non-draft pushes and PRs |
| `build-artifacts` | Build `dist/` and the Control UI once, upload reusable artifacts for downstream jobs | Node-relevant changes |
| `checks-fast-core` | Fast Linux correctness lanes such as bundled/plugin-contract/protocol checks | Node-relevant changes |
| `checks-node-extensions` | Full bundled-plugin test shards across the extension suite | Node-relevant changes |
| `checks-node-core-test` | Core Node test shards, excluding channel, bundled, contract, and extension lanes | Node-relevant changes |
| `extension-fast` | Focused tests for only the changed bundled plugins | When extension changes are detected |
| `check` | Main local gate in CI: `pnpm check` plus `pnpm build:strict-smoke` | Node-relevant changes |
| `check-additional` | Architecture, boundary, import-cycle guards plus the gateway watch regression harness | Node-relevant changes |
| `build-smoke` | Built-CLI smoke tests and startup-memory smoke | Node-relevant changes |
| `checks` | Remaining Linux Node lanes: channel tests and push-only Node 22 compatibility | Node-relevant changes |
| `check-docs` | Docs formatting, lint, and broken-link checks | Docs changed |
| `skills-python` | Ruff + pytest for Python-backed skills | Python-skill-relevant changes |
| `checks-windows` | Windows-specific test lanes | Windows-relevant changes |
| `macos-node` | macOS TypeScript test lane using the shared built artifacts | macOS-relevant changes |
| `macos-swift` | Swift lint, build, and tests for the macOS app | macOS-relevant changes |
| `android` | Android build and test matrix | Android-relevant changes |
| Job | Purpose | When it runs |
| -------------------------------- | -------------------------------------------------------------------------------------------- | ----------------------------------- |
| `preflight` | Detect docs-only changes, changed scopes, changed extensions, and build the CI manifest | Always on non-draft pushes and PRs |
| `security-scm-fast` | Private key detection and workflow audit via `zizmor` | Always on non-draft pushes and PRs |
| `security-dependency-audit` | Dependency-free production lockfile audit against npm advisories | Always on non-draft pushes and PRs |
| `security-fast` | Required aggregate for the fast security jobs | Always on non-draft pushes and PRs |
| `build-artifacts` | Build `dist/` and the Control UI once, upload reusable artifacts for downstream jobs | Node-relevant changes |
| `checks-fast-core` | Fast Linux correctness lanes such as bundled/plugin-contract/protocol checks | Node-relevant changes |
| `checks-fast-contracts-channels` | Sharded channel contract checks with a stable aggregate check result | Node-relevant changes |
| `checks-node-extensions` | Full bundled-plugin test shards across the extension suite | Node-relevant changes |
| `checks-node-core-test` | Core Node test shards, excluding channel, bundled, contract, and extension lanes | Node-relevant changes |
| `extension-fast` | Focused tests for only the changed bundled plugins | When extension changes are detected |
| `check` | Sharded main local gate equivalent: prod types, lint, guards, test types, and strict smoke | Node-relevant changes |
| `check-additional` | Architecture, boundary, extension-surface guards, package-boundary, and gateway-watch shards | Node-relevant changes |
| `build-smoke` | Built-CLI smoke tests and startup-memory smoke | Node-relevant changes |
| `checks` | Remaining Linux Node lanes: channel tests and push-only Node 22 compatibility | Node-relevant changes |
| `check-docs` | Docs formatting, lint, and broken-link checks | Docs changed |
| `skills-python` | Ruff + pytest for Python-backed skills | Python-skill-relevant changes |
| `checks-windows` | Windows-specific test lanes | Windows-relevant changes |
| `macos-node` | macOS TypeScript test lane using the shared built artifacts | macOS-relevant changes |
| `macos-swift` | Swift lint, build, and tests for the macOS app | macOS-relevant changes |
| `android` | Android build and test matrix | Android-relevant changes |
## Fail-Fast Order
Jobs are ordered so cheap checks fail before expensive ones run:
1. `preflight` decides which lanes exist at all. The `docs-scope` and `changed-scope` logic are steps inside this job, not standalone jobs.
2. `security-fast`, `check`, `check-additional`, `check-docs`, and `skills-python` fail quickly without waiting on the heavier artifact and platform matrix jobs.
2. `security-scm-fast`, `security-dependency-audit`, `security-fast`, `check`, `check-additional`, `check-docs`, and `skills-python` fail quickly without waiting on the heavier artifact and platform matrix jobs.
3. `build-artifacts` overlaps with the fast Linux lanes so downstream consumers can start as soon as the shared build is ready.
4. Heavier platform and runtime lanes fan out after that: `checks-fast-core`, `checks-node-extensions`, `checks-node-core-test`, `extension-fast`, `checks`, `checks-windows`, `macos-node`, `macos-swift`, and `android`.
4. Heavier platform and runtime lanes fan out after that: `checks-fast-core`, `checks-fast-contracts-channels`, `checks-node-extensions`, `checks-node-core-test`, `extension-fast`, `checks`, `checks-windows`, `macos-node`, `macos-swift`, and `android`.
Scope logic lives in `scripts/ci-changed-scope.mjs` and is covered by unit tests in `src/scripts/ci-changed-scope.test.ts`.
The separate `install-smoke` workflow reuses the same scope script through its own `preflight` job. It computes `run_install_smoke` from the narrower changed-smoke signal, so Docker/install smoke only runs for install, packaging, and container-relevant changes.
Local changed-lane logic lives in `scripts/changed-lanes.mjs` and is executed by `scripts/check-changed.mjs`. That local gate is stricter about architecture boundaries than the broad CI platform scope: core production changes run core prod typecheck plus core tests, core test-only changes run only core test typecheck/tests, extension production changes run extension prod typecheck plus extension tests, and extension test-only changes run only extension test typecheck/tests. Public Plugin SDK or plugin-contract changes expand to extension validation because extensions depend on those core contracts. Unknown root/config changes fail safe to all lanes.
On pushes, the `checks` matrix adds the push-only `compat-node22` lane. On pull requests, that lane is skipped and the matrix stays focused on the normal test/channel lanes.
The slowest Node test families are split into include-file shards so each job stays small: channel contracts split registry and core coverage into eight weighted shards each, auto-reply reply command tests split into four include-pattern shards, and the other large auto-reply reply prefix groups split into two shards each. `check-additional` also separates package-boundary compile/canary work from runtime topology gateway/architecture work.
GitHub may mark superseded jobs as `cancelled` when a newer push lands on the same PR or `main` ref. Treat that as CI noise unless the newest run for the same ref is also failing. The aggregate shard checks call out this cancellation case explicitly so it is easier to distinguish from a test failure.
## Runners
| Runner | Jobs |
| -------------------------------- | ---------------------------------------------------------------------------------------------------- |
| `blacksmith-16vcpu-ubuntu-2404` | `preflight`, `security-fast`, `build-artifacts`, Linux checks, docs checks, Python skills, `android` |
| `blacksmith-32vcpu-windows-2025` | `checks-windows` |
| `macos-latest` | `macos-node`, `macos-swift` |
| Runner | Jobs |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `blacksmith-16vcpu-ubuntu-2404` | `preflight`, `security-scm-fast`, `security-dependency-audit`, `security-fast`, `build-artifacts`, Linux checks, docs checks, Python skills, `android` |
| `blacksmith-32vcpu-windows-2025` | `checks-windows` |
| `macos-latest` | `macos-node`, `macos-swift` |
## Local Equivalents
```bash
pnpm check # types + lint + format
pnpm changed:lanes # inspect the local changed-lane classifier for origin/main...HEAD
pnpm check:changed # smart local gate: changed typecheck/lint/tests by boundary lane
pnpm check # fast local gate: production tsgo + sharded lint + parallel fast guards
pnpm check:test-types
pnpm check:timed # same gate with per-stage timings
pnpm build:strict-smoke
pnpm check:import-cycles
pnpm check:architecture
pnpm test:gateway:watch-regression
pnpm test # vitest tests
pnpm test:channels
pnpm test:contracts:channels
pnpm check:docs # docs format + lint + broken links
pnpm build # build dist when CI artifact/build-smoke lanes matter
```

View File

@@ -26,7 +26,7 @@ Related:
- `-t, --to <dest>`: recipient used to derive the session key
- `--session-id <id>`: explicit session id
- `--agent <id>`: agent id; overrides routing bindings
- `--thinking <off|minimal|low|medium|high|xhigh>`: agent thinking level
- `--thinking <level>`: agent thinking level (`off`, `minimal`, `low`, `medium`, `high`, plus provider-supported custom levels such as `xhigh`, `adaptive`, or `max`)
- `--verbose <on|off>`: persist verbose level for the session
- `--channel <channel>`: delivery channel; omit to use the main session channel
- `--reply-to <target>`: delivery target override

View File

@@ -336,6 +336,34 @@ If dry-run fails:
- `Dry run note: skipped <n> exec SecretRef resolvability check(s)`: dry-run skipped exec refs; rerun with `--allow-exec` if you need exec resolvability validation.
- For batch mode, fix failing entries and rerun `--dry-run` before writing.
## Write safety
`openclaw config set` and other OpenClaw-owned config writers validate the full
post-change config before committing it to disk. If the new payload fails schema
validation or looks like a destructive clobber, the active config is left alone
and the rejected payload is saved beside it as `openclaw.json.rejected.*`.
Prefer CLI writes for small edits:
```bash
openclaw config set gateway.reload.mode hybrid --dry-run
openclaw config set gateway.reload.mode hybrid
openclaw config validate
```
If a write is rejected, inspect the saved payload and fix the full config shape:
```bash
CONFIG="$(openclaw config file)"
ls -lt "$CONFIG".rejected.* 2>/dev/null | head
openclaw config validate
```
Direct editor writes are still allowed, but the running Gateway treats them as
untrusted until they validate. Invalid direct edits can be restored from the
last-known-good backup during startup or hot reload. See
[Gateway troubleshooting](/gateway/troubleshooting#gateway-restored-last-known-good-config).
## Subcommands
- `config file`: Print the active config file path (resolved from `OPENCLAW_CONFIG_PATH` or default location).

View File

@@ -16,12 +16,16 @@ Related:
Tip: run `openclaw cron --help` for the full command surface.
Note: `openclaw cron list` and `openclaw cron show <job-id>` preview the
resolved delivery route. For `channel: "last"`, the preview shows whether the
route resolved from the main/current session or will fail closed.
Note: isolated `cron add` jobs default to `--announce` delivery. Use `--no-deliver` to keep
output internal. `--deliver` remains as a deprecated alias for `--announce`.
Note: cron-owned isolated runs expect a plain-text summary and the runner owns
the final send path. `--no-deliver` keeps the run internal; it does not hand
delivery back to the agent's message tool.
Note: isolated cron chat delivery is shared. `--announce` is runner fallback
delivery for the final reply; `--no-deliver` disables that fallback but does
not remove the agent's `message` tool when a chat route is available.
Note: one-shot (`--at`) jobs delete after success by default. Use `--keep-after-run` to keep them.
@@ -124,22 +128,27 @@ openclaw cron add \
Delivery ownership note:
- Cron-owned isolated jobs always route final user-visible delivery through the
cron runner (`announce`, `webhook`, or internal-only `none`).
- If the task mentions messaging some external recipient, the agent should
describe the intended destination in its result instead of trying to send it
directly.
- Isolated cron chat delivery is shared. The agent can send directly with the
`message` tool when a chat route is available.
- `announce` fallback-delivers the final reply only when the agent did not send
directly to the resolved target. `webhook` posts the finished payload to a URL.
`none` disables runner fallback delivery.
## Common admin commands
Manual run:
```bash
openclaw cron list
openclaw cron show <job-id>
openclaw cron run <job-id>
openclaw cron run <job-id> --due
openclaw cron runs --id <job-id> --limit 50
```
`cron runs` entries include delivery diagnostics with the intended cron target,
the resolved target, message-tool sends, fallback use, and delivered state.
Agent/session retargeting:
```bash

View File

@@ -21,8 +21,9 @@ openclaw devices list
openclaw devices list --json
```
Pending request output includes the requested role and scopes so approvals can
be reviewed before you approve.
Pending request output shows the requested access next to the device's current
approved access when the device is already paired. This makes scope/role
upgrades explicit instead of looking like the pairing was lost.
### `openclaw devices remove <deviceId>`
@@ -59,6 +60,12 @@ key), OpenClaw supersedes the previous pending entry and issues a new
`requestId`. Run `openclaw devices list` right before approval to use the
current ID.
If the device is already paired and asks for broader scopes or a broader role,
OpenClaw keeps the existing approval in place and creates a new pending upgrade
request. Review the `Requested` vs `Approved` columns in `openclaw devices list`
or use `openclaw devices approve --latest` to preview the exact upgrade before
approving it.
```
openclaw devices approve
openclaw devices approve <requestId>

View File

@@ -63,6 +63,11 @@ Notes:
- `--raw-stream`: log raw model stream events to jsonl.
- `--raw-stream-path <path>`: raw stream jsonl path.
Startup profiling:
- Set `OPENCLAW_GATEWAY_STARTUP_TRACE=1` to log phase timings during Gateway startup.
- Run `pnpm test:startup:gateway -- --runs 5 --warmup 1` to benchmark Gateway startup. The benchmark records first process output, `/healthz`, `/readyz`, and startup trace timings.
## Query a running Gateway
All query commands use WebSocket RPC.
@@ -90,6 +95,8 @@ Pass `--token` or `--password` explicitly. Missing explicit credentials is an er
openclaw gateway health --url ws://127.0.0.1:18789
```
The HTTP `/healthz` endpoint is a liveness probe: it returns once the server can answer HTTP. The HTTP `/readyz` endpoint is stricter and stays red while startup sidecars, channels, or configured hooks are still settling.
### `gateway usage-cost`
Fetch usage-cost summaries from session logs.
@@ -106,7 +113,7 @@ Options:
### `gateway status`
`gateway status` shows the Gateway service (launchd/systemd/schtasks) plus an optional RPC probe.
`gateway status` shows the Gateway service (launchd/systemd/schtasks) plus an optional probe of connectivity/auth capability.
```bash
openclaw gateway status
@@ -120,17 +127,18 @@ Options:
- `--token <token>`: token auth for the probe.
- `--password <password>`: password auth for the probe.
- `--timeout <ms>`: probe timeout (default `10000`).
- `--no-probe`: skip the RPC probe (service-only view).
- `--no-probe`: skip the connectivity probe (service-only view).
- `--deep`: scan system-level services too.
- `--require-rpc`: exit non-zero when the RPC probe fails. Cannot be combined with `--no-probe`.
- `--require-rpc`: upgrade the default connectivity probe to a read probe and exit non-zero when that read probe fails. Cannot be combined with `--no-probe`.
Notes:
- `gateway status` stays available for diagnostics even when the local CLI config is missing or invalid.
- Default `gateway status` proves service state, WebSocket connect, and the auth capability visible at handshake time. It does not prove read/write/admin operations.
- `gateway status` resolves configured auth SecretRefs for probe auth when possible.
- If a required auth SecretRef is unresolved in this command path, `gateway status --json` reports `rpc.authWarning` when probe connectivity/auth fails; pass `--token`/`--password` explicitly or resolve the secret source first.
- If the probe succeeds, unresolved auth-ref warnings are suppressed to avoid false positives.
- Use `--require-rpc` in scripts and automation when a listening service is not enough and you need the Gateway RPC itself to be healthy.
- Use `--require-rpc` in scripts and automation when a listening service is not enough and you need read-scope RPC calls to be healthy too.
- `--deep` adds a best-effort scan for extra launchd/systemd/schtasks installs. When multiple gateway-like services are detected, human output prints cleanup hints and warns that most setups should run one gateway per machine.
- Human output includes the resolved file log path plus the CLI-vs-service config paths/validity snapshot to help diagnose profile or state-dir drift.
- On Linux systemd installs, service auth drift checks read both `Environment=` and `EnvironmentFile=` values from the unit (including `%h`, quoted paths, multiple files, and optional `-` files).
@@ -161,8 +169,9 @@ openclaw gateway probe --json
Interpretation:
- `Reachable: yes` means at least one target accepted a WebSocket connect.
- `RPC: ok` means detail RPC calls (`health`/`status`/`system-presence`/`config.get`) also succeeded.
- `RPC: limited - missing scope: operator.read` means connect succeeded but detail RPC is scope-limited. This is reported as **degraded** reachability, not full failure.
- `Capability: read-only|write-capable|admin-capable|pairing-pending|connect-only` reports what the probe could prove about auth. It is separate from reachability.
- `Read probe: ok` means read-scope detail RPC calls (`health`/`status`/`system-presence`/`config.get`) also succeeded.
- `Read probe: limited - missing scope: operator.read` means connect succeeded but read-scope RPC is limited. This is reported as **degraded** reachability, not full failure.
- Exit code is non-zero only when no probed target is reachable.
JSON notes (`--json`):
@@ -170,6 +179,7 @@ JSON notes (`--json`):
- Top level:
- `ok`: at least one target is reachable.
- `degraded`: at least one target had scope-limited detail RPC.
- `capability`: best capability seen across reachable targets (`read_only`, `write_capable`, `admin_capable`, `pairing_pending`, `connected_no_operator_scope`, or `unknown`).
- `primaryTargetId`: best target to treat as the active winner in this order: explicit URL, SSH tunnel, configured remote, then local loopback.
- `warnings[]`: best-effort warning records with `code`, `message`, and optional `targetIds`.
- `network`: local loopback/tailnet URL hints derived from current config and host networking.
@@ -178,13 +188,17 @@ JSON notes (`--json`):
- `ok`: reachability after connect + degraded classification.
- `rpcOk`: full detail RPC success.
- `scopeLimited`: detail RPC failed due to missing operator scope.
- Per target (`targets[].auth`):
- `role`: auth role reported in `hello-ok` when available.
- `scopes`: granted scopes reported in `hello-ok` when available.
- `capability`: the surfaced auth capability classification for that target.
Common warning codes:
- `ssh_tunnel_failed`: SSH tunnel setup failed; the command fell back to direct probes.
- `multiple_gateways`: more than one target was reachable; this is unusual unless you intentionally run isolated profiles, such as a rescue bot.
- `auth_secretref_unresolved`: a configured auth SecretRef could not be resolved for a failed target.
- `probe_scope_limited`: WebSocket connect succeeded, but detail RPC was limited by missing `operator.read`.
- `probe_scope_limited`: WebSocket connect succeeded, but the read probe was limited by missing `operator.read`.
#### Remote over SSH (Mac app parity)

View File

@@ -24,6 +24,7 @@ openclaw hooks list
```
List all discovered hooks from workspace, managed, extra, and bundled directories.
Gateway startup does not load internal hook handlers until at least one internal hook is configured.
**Options:**

View File

@@ -994,7 +994,7 @@ Options:
- `-t, --to <dest>` (for session key and optional delivery)
- `--session-id <id>`
- `--agent <id>` (agent id; overrides routing bindings)
- `--thinking <off|minimal|low|medium|high|xhigh>` (provider support varies; not model-gated at CLI level)
- `--thinking <level>` (validated against the selected model's provider profile)
- `--verbose <on|off>`
- `--channel <channel>` (delivery channel; omit to use the main session channel)
- `--reply-to <target>` (delivery target override, separate from session routing)

View File

@@ -625,10 +625,10 @@ The most important fields are:
| `config.model` | `string` | Optional blocking memory sub-agent model ref; when unset, active memory uses the current session model |
| `config.queryMode` | `"message" \| "recent" \| "full"` | Controls how much conversation the blocking memory sub-agent sees |
| `config.promptStyle` | `"balanced" \| "strict" \| "contextual" \| "recall-heavy" \| "precision-heavy" \| "preference-only"` | Controls how eager or strict the blocking memory sub-agent is when deciding whether to return memory |
| `config.thinking` | `"off" \| "minimal" \| "low" \| "medium" \| "high" \| "xhigh" \| "adaptive"` | Advanced thinking override for the blocking memory sub-agent; default `off` for speed |
| `config.thinking` | `"off" \| "minimal" \| "low" \| "medium" \| "high" \| "xhigh" \| "adaptive" \| "max"` | Advanced thinking override for the blocking memory sub-agent; default `off` for speed |
| `config.promptOverride` | `string` | Advanced full prompt replacement; not recommended for normal use |
| `config.promptAppend` | `string` | Advanced extra instructions appended to the default or overridden prompt |
| `config.timeoutMs` | `number` | Hard timeout for the blocking memory sub-agent |
| `config.timeoutMs` | `number` | Hard timeout for the blocking memory sub-agent, capped at 120000 ms |
| `config.maxSummaryChars` | `number` | Maximum total characters allowed in the active-memory summary |
| `config.logging` | `boolean` | Emits active memory logs while tuning |
| `config.persistTranscripts` | `boolean` | Keeps blocking memory sub-agent transcripts on disk instead of deleting temp files |

View File

@@ -132,10 +132,10 @@ capable model for better summaries:
}
```
## Compaction start notice
## Compaction notices
By default, compaction runs silently. To show a brief notice when compaction
starts, enable `notifyUser`:
By default, compaction runs silently. To show brief notices when compaction
starts and when it completes, enable `notifyUser`:
```json5
{
@@ -149,8 +149,8 @@ starts, enable `notifyUser`:
}
```
When enabled, the user sees a short message (for example, "Compacting
context...") at the start of each compaction run.
When enabled, the user sees short status messages around each compaction run
(for example, "Compacting context..." and "Compaction complete").
## Compaction vs pruning

View File

@@ -62,7 +62,7 @@ Config (global default + per-channel overrides):
Notes:
- Debounce applies to **text-only** messages; media/attachments flush immediately.
- Control commands bypass debouncing so they remain standalone.
- Control commands bypass debouncing so they remain standalone**except** when a channel explicitly opts in to same-sender DM coalescing (e.g. [BlueBubbles `coalesceSameSenderDms`](/channels/bluebubbles#coalescing-split-send-dms-command--url-in-one-composition)), where DM commands wait inside the debounce window so a split-send payload can join the same agent turn.
## Sessions and devices
@@ -153,6 +153,20 @@ Outbound message formatting is centralized in `messages`:
Details: [Configuration](/gateway/configuration-reference#messages) and channel docs.
## Silent replies
The exact silent token `NO_REPLY` / `no_reply` means “do not deliver a user-visible reply”.
OpenClaw resolves that behavior by conversation type:
- Direct conversations disallow silence by default and rewrite a bare silent
reply to a short visible fallback.
- Groups/channels allow silence by default.
- Internal orchestration allows silence by default.
Defaults live under `agents.defaults.silentReply` and
`agents.defaults.silentReplyRewrite`; `surfaces.<id>.silentReply` and
`surfaces.<id>.silentReplyRewrite` can override them per surface.
## Related
- [Streaming](/concepts/streaming) — real-time message delivery

View File

@@ -42,8 +42,9 @@ For model selection rules, see [/concepts/models](/concepts/models).
`buildAuthDoctorHint`,
`matchesContextOverflowError`, `classifyFailoverReason`,
`isCacheTtlEligible`, `buildMissingAuthMessage`, `suppressBuiltInModel`,
`augmentModelCatalog`, `isBinaryThinking`, `supportsXHighThinking`,
`resolveDefaultThinkingLevel`, `applyConfigDefaults`, `isModernModelRef`,
`augmentModelCatalog`, `resolveThinkingProfile`, `isBinaryThinking`,
`supportsXHighThinking`, `resolveDefaultThinkingLevel`,
`applyConfigDefaults`, `isModernModelRef`,
`prepareRuntimeAuth`, `resolveUsageAuth`, `fetchUsageSnapshot`, and
`onModelSelected`.
- Note: provider runtime `capabilities` is shared runner metadata (provider
@@ -131,10 +132,11 @@ Typical split:
vendor-owned error for direct resolution failures
- `augmentModelCatalog`: provider appends synthetic/final catalog rows after
discovery and config merging
- `isBinaryThinking`: provider owns binary on/off thinking UX
- `supportsXHighThinking`: provider opts selected models into `xhigh`
- `resolveDefaultThinkingLevel`: provider owns default `/think` policy for a
model family
- `resolveThinkingProfile`: provider owns the exact `/think` level set,
optional display labels, and default level for a selected model
- `isBinaryThinking`: compatibility hook for binary on/off thinking UX
- `supportsXHighThinking`: compatibility hook for selected `xhigh` models
- `resolveDefaultThinkingLevel`: compatibility hook for default `/think` policy
- `applyConfigDefaults`: provider applies provider-specific global defaults
during config materialization based on auth mode, env, or model family
- `isModernModelRef`: provider owns live/smoke preferred-model matching
@@ -431,7 +433,7 @@ See [/providers/kilocode](/providers/kilocode) for setup details.
`input: ["text", "image"]`; the bundled provider catalog keeps the chat refs
text-only until that provider config is materialized
- Moonshot: `moonshot` (`MOONSHOT_API_KEY`)
- Example model: `moonshot/kimi-k2.5`
- Example model: `moonshot/kimi-k2.6`
- Kimi Coding: `kimi` (`KIMI_API_KEY` or `KIMICODE_API_KEY`)
- Example model: `kimi/kimi-code`
- Qianfan: `qianfan` (`QIANFAN_API_KEY`)
@@ -488,13 +490,14 @@ need to override the base URL or model metadata:
- Provider: `moonshot`
- Auth: `MOONSHOT_API_KEY`
- Example model: `moonshot/kimi-k2.5`
- Example model: `moonshot/kimi-k2.6`
- CLI: `openclaw onboard --auth-choice moonshot-api-key` or `openclaw onboard --auth-choice moonshot-api-key-cn`
Kimi K2 model IDs:
[//]: # "moonshot-kimi-k2-model-refs:start"
- `moonshot/kimi-k2.6`
- `moonshot/kimi-k2.5`
- `moonshot/kimi-k2-thinking`
- `moonshot/kimi-k2-thinking-turbo`
@@ -505,7 +508,7 @@ Kimi K2 model IDs:
```json5
{
agents: {
defaults: { model: { primary: "moonshot/kimi-k2.5" } },
defaults: { model: { primary: "moonshot/kimi-k2.6" } },
},
models: {
mode: "merge",
@@ -514,7 +517,7 @@ Kimi K2 model IDs:
baseUrl: "https://api.moonshot.ai/v1",
apiKey: "${MOONSHOT_API_KEY}",
api: "openai-completions",
models: [{ id: "kimi-k2.5", name: "Kimi K2.5" }],
models: [{ id: "kimi-k2.6", name: "Kimi K2.6" }],
},
},
},

View File

@@ -80,6 +80,8 @@ disposable server. It requires `OPENCLAW_QA_TELEGRAM_GROUP_ID`,
private group. The SUT bot must have a Telegram username, and bot-to-bot
observation works best when both bots have Bot-to-Bot Communication Mode
enabled in `@BotFather`.
The command exits non-zero when any scenario fails. Use `--allow-failures` when
you want artifacts without a failing exit code.
Live transport lanes now share one smaller contract instead of each inventing
their own scenario list shape:
@@ -107,9 +109,11 @@ inside the guest, runs `qa suite`, then copies the normal QA report and
summary back into `.artifacts/qa-e2e/...` on the host.
It reuses the same scenario-selection behavior as `qa suite` on the host.
Host and Multipass suite runs execute multiple selected scenarios in parallel
with isolated gateway workers by default, up to 64 workers or the selected
scenario count. Use `--concurrency <count>` to tune the worker count, or
`--concurrency 1` for serial execution.
with isolated gateway workers by default. `qa-channel` defaults to concurrency
4, capped by the selected scenario count. Use `--concurrency <count>` to tune
the worker count, or `--concurrency 1` for serial execution.
The command exits non-zero when any scenario fails. Use `--allow-failures` when
you want artifacts without a failing exit code.
Live runs forward the supported QA auth inputs that are practical for the
guest: env-based provider keys, the QA live provider config path, and
`CODEX_HOME` when present. Keep `--output-dir` under the repo root so the guest

View File

@@ -24,11 +24,19 @@ Use provider-owned contributions for model-family-specific tuning. Keep legacy
`before_prompt_build` prompt mutation for compatibility or truly global prompt
changes, not normal provider behavior.
The OpenAI GPT-5 family overlay keeps the core execution rule small and adds
model-specific guidance for persona latching, concise output, tool discipline,
parallel lookup, deliverable coverage, verification, missing context, and
terminal-tool hygiene.
## Structure
The prompt is intentionally compact and uses fixed sections:
- **Tooling**: structured-tool source-of-truth reminder plus runtime tool-use guidance.
- **Execution Bias**: compact follow-through guidance: act in-turn on
actionable requests, continue until done or blocked, recover from weak tool
results, check mutable state live, and verify before finalizing.
- **Safety**: short guardrail reminder to avoid power-seeking behavior or bypassing oversight.
- **Skills** (when available): tells the model how to load skill instructions on demand.
- **OpenClaw Self-Update**: how to inspect config safely with

View File

@@ -61,14 +61,14 @@ node --import tsx scripts/repro/tsx-name-repro.ts
## Workarounds
- Use Bun for dev scripts (current temporary revert).
- Use Node + tsc watch, then run compiled output:
- Use `tsgo` for repo type checking, then run the built output:
```bash
pnpm exec tsc --watch --preserveWatchOutput
node --watch openclaw.mjs status
pnpm tsgo
node openclaw.mjs status
```
- Confirmed locally: `pnpm exec tsc -p tsconfig.json` + `node openclaw.mjs status` works on Node 25.
- Historical note: `tsc` was used here while debugging this Node/tsx issue, but repo type-check lanes now use `tsgo`.
- Disable esbuild keepNames in the TS loader if possible (prevents `__name` helper insertion); tsx does not currently expose this.
- Test Node LTS (22/24) with `tsx` to see if the issue is Node 25specific.

View File

@@ -456,6 +456,14 @@
"source": "/channels/grammy",
"destination": "/channels/telegram"
},
{
"source": "/channels/openclaw-weixin",
"destination": "/channels/wechat"
},
{
"source": "/channels/weixin",
"destination": "/channels/wechat"
},
{
"source": "/group-messages",
"destination": "/channels/group-messages"
@@ -1028,6 +1036,7 @@
"channels/telegram",
"channels/tlon",
"channels/twitch",
"channels/wechat",
"channels/whatsapp",
"channels/zalo",
"channels/zalouser"

View File

@@ -1392,7 +1392,7 @@ Periodic heartbeat runs.
identifierInstructions: "Preserve deployment IDs, ticket IDs, and host:port pairs exactly.", // used when identifierPolicy=custom
postCompactionSections: ["Session Startup", "Red Lines"], // [] disables reinjection
model: "openrouter/anthropic/claude-sonnet-4-6", // optional compaction-only model override
notifyUser: true, // send a brief notice when compaction starts (default: false)
notifyUser: true, // send brief notices when compaction starts and completes (default: false)
memoryFlush: {
enabled: true,
softThresholdTokens: 6000,
@@ -1412,7 +1412,7 @@ Periodic heartbeat runs.
- `identifierInstructions`: optional custom identifier-preservation text used when `identifierPolicy=custom`.
- `postCompactionSections`: optional AGENTS.md H2/H3 section names to re-inject after compaction. Defaults to `["Session Startup", "Red Lines"]`; set `[]` to disable reinjection. When unset or explicitly set to that default pair, older `Every Session`/`Safety` headings are also accepted as a legacy fallback.
- `model`: optional `provider/model-id` override for compaction summarization only. Use this when the main session should keep one model but compaction summaries should run on another; when unset, compaction uses the session's primary model.
- `notifyUser`: when `true`, sends a brief notice to the user when compaction starts (for example, "Compacting context..."). Disabled by default to keep compaction silent.
- `notifyUser`: when `true`, sends brief notices to the user when compaction starts and when it completes (for example, "Compacting context..." and "Compaction complete"). Disabled by default to keep compaction silent.
- `memoryFlush`: silent agentic turn before auto-compaction to store durable memories. Skipped when workspace is read-only.
### `agents.defaults.contextPruning`
@@ -1791,7 +1791,7 @@ scripts/sandbox-browser-setup.sh # optional browser image
- `model`: string form overrides `primary` only; object form `{ primary, fallbacks }` overrides both (`[]` disables global fallbacks). Cron jobs that only override `primary` still inherit default fallbacks unless you set `fallbacks: []`.
- `params`: per-agent stream params merged over the selected model entry in `agents.defaults.models`. Use this for agent-specific overrides like `cacheRetention`, `temperature`, or `maxTokens` without duplicating the whole model catalog.
- `skills`: optional per-agent skill allowlist. If omitted, the agent inherits `agents.defaults.skills` when set; an explicit list replaces defaults instead of merging, and `[]` means no skills.
- `thinkingDefault`: optional per-agent default thinking level (`off | minimal | low | medium | high | xhigh | adaptive`). Overrides `agents.defaults.thinkingDefault` for this agent when no per-message or session override is set.
- `thinkingDefault`: optional per-agent default thinking level (`off | minimal | low | medium | high | xhigh | adaptive | max`). Overrides `agents.defaults.thinkingDefault` for this agent when no per-message or session override is set.
- `reasoningDefault`: optional per-agent default reasoning visibility (`on | off | stream`). Applies when no per-message or session reasoning override is set.
- `fastModeDefault`: optional per-agent default for fast mode (`true | false`). Applies when no per-message or session fast-mode override is set.
- `embeddedHarness`: optional per-agent low-level harness policy override. Use `{ runtime: "codex", fallback: "none" }` to make one agent Codex-only while other agents keep the default PI fallback.
@@ -2673,8 +2673,8 @@ Set `ZAI_API_KEY`. `z.ai/*` and `z-ai/*` are accepted aliases. Shortcut: `opencl
env: { MOONSHOT_API_KEY: "sk-..." },
agents: {
defaults: {
model: { primary: "moonshot/kimi-k2.5" },
models: { "moonshot/kimi-k2.5": { alias: "Kimi K2.5" } },
model: { primary: "moonshot/kimi-k2.6" },
models: { "moonshot/kimi-k2.6": { alias: "Kimi K2.6" } },
},
},
models: {
@@ -2686,11 +2686,11 @@ Set `ZAI_API_KEY`. `z.ai/*` and `z-ai/*` are accepted aliases. Shortcut: `opencl
api: "openai-completions",
models: [
{
id: "kimi-k2.5",
name: "Kimi K2.5",
id: "kimi-k2.6",
name: "Kimi K2.6",
reasoning: false,
input: ["text", "image"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
cost: { input: 0.95, output: 4, cacheRead: 0.16, cacheWrite: 0 },
contextWindow: 262144,
maxTokens: 262144,
},
@@ -2967,7 +2967,8 @@ See [Plugins](/tools/plugin).
- `profiles.*.cdpUrl` accepts `http://`, `https://`, `ws://`, and `wss://`.
Use HTTP(S) when you want OpenClaw to discover `/json/version`; use WS(S)
when your provider gives you a direct DevTools WebSocket URL.
- `existing-session` profiles are host-only and use Chrome MCP instead of CDP.
- `existing-session` profiles use Chrome MCP instead of CDP and can attach on
the selected host or through a connected browser node.
- `existing-session` profiles can set `userDataDir` to target a specific
Chromium-based browser profile such as Brave or Edge.
- `existing-session` profiles keep the current Chrome MCP route limits:
@@ -3192,8 +3193,8 @@ See [Multiple Gateways](/gateway/multiple-gateways).
path: "/hooks",
maxBodyBytes: 262144,
defaultSessionKey: "hook:ingress",
allowRequestSessionKey: false,
allowedSessionKeyPrefixes: ["hook:"],
allowRequestSessionKey: true,
allowedSessionKeyPrefixes: ["hook:", "hook:gmail:"],
allowedAgentIds: ["hooks", "main"],
presets: ["gmail"],
transformsDir: "~/.openclaw/hooks/transforms",
@@ -3224,6 +3225,7 @@ Validation and safety notes:
- `hooks.token` must be **distinct** from `gateway.auth.token`; reusing the Gateway token is rejected.
- `hooks.path` cannot be `/`; use a dedicated subpath such as `/hooks`.
- If `hooks.allowRequestSessionKey=true`, constrain `hooks.allowedSessionKeyPrefixes` (for example `["hook:"]`).
- If a mapping or preset uses a templated `sessionKey`, set `hooks.allowedSessionKeyPrefixes` and `hooks.allowRequestSessionKey=true`. Static mapping keys do not require that opt-in.
**Endpoints:**
@@ -3231,6 +3233,7 @@ Validation and safety notes:
- `POST /hooks/agent``{ message, name?, agentId?, sessionKey?, wakeMode?, deliver?, channel?, to?, model?, thinking?, timeoutSeconds? }`
- `sessionKey` from request payload is accepted only when `hooks.allowRequestSessionKey=true` (default: `false`).
- `POST /hooks/<name>` → resolved via `hooks.mappings`
- Template-rendered mapping `sessionKey` values are treated as externally supplied and also require `hooks.allowRequestSessionKey=true`.
<Accordion title="Mapping details">
@@ -3242,8 +3245,8 @@ Validation and safety notes:
- `agentId` routes to a specific agent; unknown IDs fall back to default.
- `allowedAgentIds`: restricts explicit routing (`*` or omitted = allow all, `[]` = deny all).
- `defaultSessionKey`: optional fixed session key for hook agent runs without explicit `sessionKey`.
- `allowRequestSessionKey`: allow `/hooks/agent` callers to set `sessionKey` (default: `false`).
- `allowedSessionKeyPrefixes`: optional prefix allowlist for explicit `sessionKey` values (request + mapping), e.g. `["hook:"]`.
- `allowRequestSessionKey`: allow `/hooks/agent` callers and template-driven mapping session keys to set `sessionKey` (default: `false`).
- `allowedSessionKeyPrefixes`: optional prefix allowlist for explicit `sessionKey` values (request + mapping), e.g. `["hook:"]`. It becomes required when any mapping or preset uses a templated `sessionKey`.
- `deliver: true` sends final reply to a channel; `channel` defaults to `last`.
- `model` overrides LLM for this hook run (must be allowed if model catalog is set).
@@ -3251,6 +3254,10 @@ Validation and safety notes:
### Gmail integration
- The built-in Gmail preset uses `sessionKey: "hook:gmail:{{messages[0].id}}"`.
- If you keep that per-message routing, set `hooks.allowRequestSessionKey: true` and constrain `hooks.allowedSessionKeyPrefixes` to match the Gmail namespace, for example `["hook:", "hook:gmail:"]`.
- If you need `hooks.allowRequestSessionKey: false`, override the preset with a static `sessionKey` instead of the templated default.
```json5
{
hooks: {

View File

@@ -96,6 +96,17 @@ When validation fails:
- Run `openclaw doctor` to see exact issues
- Run `openclaw doctor --fix` (or `--yes`) to apply repairs
The Gateway also keeps a trusted last-known-good copy after a successful startup. If
`openclaw.json` is later changed outside OpenClaw and no longer validates, startup
and hot reload preserve the broken file as a timestamped `.clobbered.*` snapshot,
restore the last-known-good copy, and log a loud warning with the recovery reason.
The next main-agent turn also receives a system-event warning telling it that the
config was restored and must not be blindly rewritten. Last-known-good promotion
is updated after validated startup and after accepted hot reloads, including
OpenClaw-owned config writes whose persisted file hash still matches the accepted
write. Promotion is skipped when the candidate contains redacted secret
placeholders such as `***` or shortened token values.
## Common tasks
<AccordionGroup>
@@ -287,7 +298,7 @@ When validation fails:
</Accordion>
<Accordion title="Enable sandboxing">
Run agent sessions in isolated Docker containers:
Run agent sessions in isolated sandbox runtimes:
```json5
{
@@ -494,6 +505,19 @@ When validation fails:
The Gateway watches `~/.openclaw/openclaw.json` and applies changes automatically — no manual restart needed for most settings.
Direct file edits are treated as untrusted until they validate. The watcher waits
for editor temp-write/rename churn to settle, reads the final file, and rejects
invalid external edits by restoring the last-known-good config. OpenClaw-owned
config writes use the same schema gate before writing; destructive clobbers such
as dropping `gateway.mode` or shrinking the file by more than half are rejected
and saved as `.rejected.*` for inspection.
If you see `Config auto-restored from last-known-good` or
`config reload restored last-known-good config` in logs, inspect the matching
`.clobbered.*` file next to `openclaw.json`, fix the rejected payload, then run
`openclaw config validate`. See [Gateway troubleshooting](/gateway/troubleshooting#gateway-restored-last-known-good-config)
for the recovery checklist.
### Reload modes
| Mode | Behavior |

View File

@@ -86,6 +86,7 @@ cat ~/.openclaw/openclaw.json
- Gateway port collision diagnostics (default `18789`).
- Security warnings for open DM policies.
- Gateway auth checks for local token mode (offers token generation when no token source exists; does not overwrite token SecretRef configs).
- Device pairing trouble detection (pending first-time pair requests, pending role/scope upgrades, stale local device-token cache drift, and paired-record auth drift).
- systemd linger check on Linux.
- Workspace bootstrap file size check (truncation/near-limit warnings for context files).
- Shell completion status check and auto-install/upgrade.
@@ -379,10 +380,14 @@ switch to legacy names if the current image is missing.
### 7b) Bundled plugin runtime deps
Doctor verifies that bundled plugin runtime dependencies (for example the
Discord plugin runtime packages) are present in the OpenClaw install root.
If any are missing, doctor reports the packages and installs them in
`openclaw doctor --fix` / `openclaw doctor --repair` mode.
Doctor verifies runtime dependencies only for bundled plugins that are active in
the current config or enabled by their bundled manifest default, for example
`plugins.entries.discord.enabled: true`, legacy
`channels.discord.enabled: true`, or a default-enabled bundled provider. If any
are missing, doctor reports the packages and installs them in
`openclaw doctor --fix` / `openclaw doctor --repair` mode. External plugins still
use `openclaw plugins install` / `openclaw plugins update`; doctor does not
install dependencies for arbitrary plugin paths.
### 8) Gateway service migrations and cleanup hints
@@ -401,6 +406,34 @@ encrypted-state preparation. Both steps are non-fatal; errors are logged and
startup continues. In read-only mode (`openclaw doctor` without `--fix`) this check
is skipped entirely.
### 8c) Device pairing and auth drift
Doctor now inspects device-pairing state as part of the normal health pass.
What it reports:
- pending first-time pairing requests
- pending role upgrades for already paired devices
- pending scope upgrades for already paired devices
- public-key mismatch repairs where the device id still matches but the device
identity no longer matches the approved record
- paired records missing an active token for an approved role
- paired tokens whose scopes drift outside the approved pairing baseline
- local cached device-token entries for the current machine that predate a
gateway-side token rotation or carry stale scope metadata
Doctor does not auto-approve pair requests or auto-rotate device tokens. It
prints the exact next steps instead:
- inspect pending requests with `openclaw devices list`
- approve the exact request with `openclaw devices approve <requestId>`
- rotate a fresh token with `openclaw devices rotate --device <deviceId> --role <role>`
- remove and re-approve a stale record with `openclaw devices remove <deviceId>`
This closes the common "already paired but still getting pairing required"
hole: doctor now distinguishes first-time pairing from pending role/scope
upgrades and from stale token/device-identity drift.
### 9) Security warnings
Doctor emits warnings when a provider is open to DMs without an allowlist, or

View File

@@ -47,7 +47,7 @@ openclaw status
openclaw logs --follow
```
Healthy baseline: `Runtime: running` and `RPC probe: ok`.
Healthy baseline: `Runtime: running`, `Connectivity probe: ok`, and `Capability: ...` that matches what you expect. Use `openclaw gateway status --require-rpc` when you need read-scope RPC proof, not just reachability.
</Step>

View File

@@ -10,70 +10,85 @@ title: "Multiple Gateways"
Most setups should use one Gateway because a single Gateway can handle multiple messaging connections and agents. If you need stronger isolation or redundancy (e.g., a rescue bot), run separate Gateways with isolated profiles/ports.
## Isolation checklist (required)
## Best Recommended Setup
For most users, the simplest rescue-bot setup is:
- keep the main bot on the default profile
- run the rescue bot on `--profile rescue`
- use a completely separate Telegram bot for the rescue account
- keep the rescue bot on a different base port such as `19001`
This keeps the rescue bot isolated from the main bot so it can debug or apply
config changes if the primary bot is down. Leave at least 20 ports between
base ports so the derived browser/canvas/CDP ports never collide.
## Rescue-Bot Quickstart
Use this as the default path unless you have a strong reason to do something
else:
```bash
# Rescue bot (separate Telegram bot, separate profile, port 19001)
openclaw --profile rescue onboard
openclaw --profile rescue gateway install
```
If your main bot is already running, that is usually all you need.
During `openclaw --profile rescue onboard`:
- use the separate Telegram bot token
- keep the `rescue` profile
- use a base port at least 20 higher than the main bot
- accept the default rescue workspace unless you already manage one yourself
If onboarding already installed the rescue service for you, the final
`gateway install` is not needed.
## Why This Works
The rescue bot stays independent because it has its own:
- profile/config
- state directory
- workspace
- base port (plus derived ports)
- Telegram bot token
For most setups, use a completely separate Telegram bot for the rescue profile:
- easy to keep operator-only
- separate bot token and identity
- independent from the main bot's channel/app install
- simple DM-based recovery path when the main bot is broken
## What `--profile rescue onboard` Changes
`openclaw --profile rescue onboard` uses the normal onboarding flow, but it
writes everything into a separate profile.
In practice, that means the rescue bot gets its own:
- config file
- state directory
- workspace (by default `~/.openclaw/workspace-rescue`)
- managed service name
The prompts are otherwise the same as normal onboarding.
## Isolation Checklist
Keep these unique per Gateway instance:
- `OPENCLAW_CONFIG_PATH` — per-instance config file
- `OPENCLAW_STATE_DIR` — per-instance sessions, creds, caches
- `agents.defaults.workspace` — per-instance workspace root
- `gateway.port` (or `--port`) — unique per instance
- Derived ports (browser/canvas) must not overlap
- derived browser/canvas/CDP ports
If these are shared, you will hit config races and port conflicts.
## Recommended: profiles (`--profile`)
Profiles auto-scope `OPENCLAW_STATE_DIR` + `OPENCLAW_CONFIG_PATH` and suffix service names.
```bash
# main
openclaw --profile main setup
openclaw --profile main gateway --port 18789
# rescue
openclaw --profile rescue setup
openclaw --profile rescue gateway --port 19001
```
Per-profile services:
```bash
openclaw --profile main gateway install
openclaw --profile rescue gateway install
```
## Rescue-bot guide
Run a second Gateway on the same host with its own:
- profile/config
- state dir
- workspace
- base port (plus derived ports)
This keeps the rescue bot isolated from the main bot so it can debug or apply config changes if the primary bot is down.
Port spacing: leave at least 20 ports between base ports so the derived browser/canvas/CDP ports never collide.
### How to install (rescue bot)
```bash
# Main bot (existing or fresh, without --profile param)
# Runs on port 18789 + Chrome CDC/Canvas/... Ports
openclaw onboard
openclaw gateway install
# Rescue bot (isolated profile + ports)
openclaw --profile rescue onboard
# Notes:
# - workspace name will be postfixed with -rescue per default
# - Port should be at least 18789 + 20 Ports,
# better choose completely different base port, like 19789,
# - rest of the onboarding is the same as normal
# To install the service (if not happened automatically during setup)
openclaw --profile rescue gateway install
```
## Port mapping (derived)
Base port = `gateway.port` (or `OPENCLAW_GATEWAY_PORT` / `--port`).
@@ -95,7 +110,7 @@ If you override any of these in config or env, you must keep them unique per ins
```bash
OPENCLAW_CONFIG_PATH=~/.openclaw/main.json \
OPENCLAW_STATE_DIR=~/.openclaw-main \
OPENCLAW_STATE_DIR=~/.openclaw \
openclaw gateway --port 18789
OPENCLAW_CONFIG_PATH=~/.openclaw/rescue.json \
@@ -106,10 +121,10 @@ openclaw gateway --port 19001
## Quick checks
```bash
openclaw --profile main gateway status --deep
openclaw gateway status --deep
openclaw --profile rescue gateway status --deep
openclaw --profile rescue gateway probe
openclaw --profile main status
openclaw status
openclaw --profile rescue status
openclaw --profile rescue browser status
```

View File

@@ -9,7 +9,7 @@ status: active
OpenClaw has three related (but different) controls:
1. **Sandbox** (`agents.defaults.sandbox.*` / `agents.list[].sandbox.*`) decides **where tools run** (Docker vs host).
1. **Sandbox** (`agents.defaults.sandbox.*` / `agents.list[].sandbox.*`) decides **where tools run** (sandbox backend vs host).
2. **Tool policy** (`tools.*`, `tools.sandbox.tools.*`, `agents.list[].tools.*`) decides **which tools are available/allowed**.
3. **Elevated** (`tools.elevated.*`, `agents.list[].tools.elevated.*`) is an **exec-only escape hatch** to run outside the sandbox when youre sandboxed (`gateway` by default, or `node` when the exec target is configured to `node`).

View File

@@ -58,7 +58,7 @@ Not sandboxed:
`agents.defaults.sandbox.backend` controls **which runtime** provides the sandbox:
- `"docker"` (default): local Docker-backed sandbox runtime.
- `"docker"` (default when sandboxing is enabled): local Docker-backed sandbox runtime.
- `"ssh"`: generic SSH-backed remote sandbox runtime.
- `"openshell"`: OpenShell-backed sandbox runtime.
@@ -79,7 +79,10 @@ OpenShell-specific config lives under `plugins.entries.openshell.config`.
### Docker backend
The Docker backend is the default runtime, executing tools and sandbox browsers locally via the Docker daemon socket (`/var/run/docker.sock`). Sandbox container isolation is determined by Docker namespaces.
Sandboxing is off by default. If you enable sandboxing and do not choose a
backend, OpenClaw uses the Docker backend. It executes tools and sandbox browsers
locally via the Docker daemon socket (`/var/run/docker.sock`). Sandbox container
isolation is determined by Docker namespaces.
**Docker-out-of-Docker (DooD) Constraints**:
If you deploy the OpenClaw Gateway itself as a Docker container, it orchestrates sibling sandbox containers using the host's Docker socket (DooD). This introduces a specific path mapping constraint:

View File

@@ -1109,7 +1109,7 @@ Dedicated doc: [Sandboxing](/gateway/sandboxing)
Two complementary approaches:
- **Run the full Gateway in Docker** (container boundary): [Docker](/install/docker)
- **Tool sandbox** (`agents.defaults.sandbox`, host gateway + Docker-isolated tools): [Sandboxing](/gateway/sandboxing)
- **Tool sandbox** (`agents.defaults.sandbox`, host gateway + sandbox-isolated tools; Docker is the default backend): [Sandboxing](/gateway/sandboxing)
Note: to prevent cross-agent access, keep `agents.defaults.sandbox.scope` at `"agent"` (default)
or `"session"` for stricter per-session isolation. `scope: "shared"` uses a

View File

@@ -25,7 +25,7 @@ openclaw channels status --probe
Expected healthy signals:
- `openclaw gateway status` shows `Runtime: running` and `RPC probe: ok`.
- `openclaw gateway status` shows `Runtime: running`, `Connectivity probe: ok`, and a `Capability: ...` line.
- `openclaw doctor` reports no blocking config/service issues.
- `openclaw channels status --probe` shows live per-account transport status and,
where supported, probe/audit results such as `works` or `audit ok`.
@@ -193,12 +193,12 @@ Common signatures:
Use `error.details.code` from the failed `connect` response to pick the next action:
| Detail code | Meaning | Recommended action |
| ---------------------------- | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `AUTH_TOKEN_MISSING` | Client did not send a required shared token. | Paste/set token in the client and retry. For dashboard paths: `openclaw config get gateway.auth.token` then paste into Control UI settings. |
| `AUTH_TOKEN_MISMATCH` | Shared token did not match gateway auth token. | If `canRetryWithDeviceToken=true`, allow one trusted retry. Cached-token retries reuse stored approved scopes; explicit `deviceToken` / `scopes` callers keep requested scopes. If still failing, run the [token drift recovery checklist](/cli/devices#token-drift-recovery-checklist). |
| `AUTH_DEVICE_TOKEN_MISMATCH` | Cached per-device token is stale or revoked. | Rotate/re-approve device token using [devices CLI](/cli/devices), then reconnect. |
| `PAIRING_REQUIRED` | Device identity is known but not approved for this role. | Approve pending request: `openclaw devices list` then `openclaw devices approve <requestId>`. |
| Detail code | Meaning | Recommended action |
| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `AUTH_TOKEN_MISSING` | Client did not send a required shared token. | Paste/set token in the client and retry. For dashboard paths: `openclaw config get gateway.auth.token` then paste into Control UI settings. |
| `AUTH_TOKEN_MISMATCH` | Shared token did not match gateway auth token. | If `canRetryWithDeviceToken=true`, allow one trusted retry. Cached-token retries reuse stored approved scopes; explicit `deviceToken` / `scopes` callers keep requested scopes. If still failing, run the [token drift recovery checklist](/cli/devices#token-drift-recovery-checklist). |
| `AUTH_DEVICE_TOKEN_MISMATCH` | Cached per-device token is stale or revoked. | Rotate/re-approve device token using [devices CLI](/cli/devices), then reconnect. |
| `PAIRING_REQUIRED` | Device identity needs approval. Check `error.details.reason` for `not-paired`, `scope-upgrade`, `role-upgrade`, or `metadata-upgrade`, and use `requestId` / `remediationHint` when present. | Approve pending request: `openclaw devices list` then `openclaw devices approve <requestId>`. Scope/role upgrades use the same flow after you review the requested access. |
Device auth v2 migration check:
@@ -262,6 +262,63 @@ Related:
- [/gateway/configuration](/gateway/configuration)
- [/gateway/doctor](/gateway/doctor)
## Gateway restored last-known-good config
Use this when the Gateway starts, but logs say it restored `openclaw.json`.
```bash
openclaw logs --follow
openclaw config file
openclaw config validate
openclaw doctor
```
Look for:
- `Config auto-restored from last-known-good`
- `gateway: invalid config was restored from last-known-good backup`
- `config reload restored last-known-good config after invalid-config`
- A timestamped `openclaw.json.clobbered.*` file beside the active config
- A main-agent system event that starts with `Config recovery warning`
What happened:
- The rejected config did not validate during startup or hot reload.
- OpenClaw preserved the rejected payload as `.clobbered.*`.
- The active config was restored from the last validated last-known-good copy.
- The next main-agent turn is warned not to blindly rewrite the rejected config.
Inspect and repair:
```bash
CONFIG="$(openclaw config file)"
ls -lt "$CONFIG".clobbered.* "$CONFIG".rejected.* 2>/dev/null | head
diff -u "$CONFIG" "$(ls -t "$CONFIG".clobbered.* 2>/dev/null | head -n 1)"
openclaw config validate
openclaw doctor
```
Common signatures:
- `.clobbered.*` exists → an external direct edit or startup read was restored.
- `.rejected.*` exists → an OpenClaw-owned config write failed schema or clobber checks before commit.
- `Config write rejected:` → the write tried to drop required shape, shrink the file sharply, or persist invalid config.
- `Config last-known-good promotion skipped` → the candidate contained redacted secret placeholders such as `***`.
Fix options:
1. Keep the restored active config if it is correct.
2. Copy only the intended keys from `.clobbered.*` or `.rejected.*`, then apply them with `openclaw config set` or `config.patch`.
3. Run `openclaw config validate` before restarting.
4. If you edit by hand, keep the full JSON5 config, not just the partial object you wanted to change.
Related:
- [/gateway/configuration#strict-validation](/gateway/configuration#strict-validation)
- [/gateway/configuration#config-hot-reload](/gateway/configuration#config-hot-reload)
- [/cli/config](/cli/config)
- [/gateway/doctor](/gateway/doctor)
## Gateway probe warnings
Use this when `openclaw gateway probe` reaches something, but still prints a warning block.
@@ -281,7 +338,8 @@ Common signatures:
- `SSH tunnel failed to start; falling back to direct probes.` → SSH setup failed, but the command still tried direct configured/loopback targets.
- `multiple reachable gateways detected` → more than one target answered. Usually this means an intentional multi-gateway setup or stale/duplicate listeners.
- `Probe diagnostics are limited by gateway scopes (missing operator.read)` → connect worked, but detail RPC is scope-limited; pair device identity or use credentials with `operator.read`.
- `Read-probe diagnostics are limited by gateway scopes (missing operator.read)` → connect worked, but detail RPC is scope-limited; pair device identity or use credentials with `operator.read`.
- `Capability: pairing-pending` or `gateway closed (1008): pairing required` → the gateway answered, but this client still needs pairing/approval before normal operator access.
- unresolved `gateway.auth.*` / `gateway.remote.*` SecretRef warning text → auth material was unavailable in this command path for the failed target.
Related:
@@ -413,6 +471,7 @@ Common signatures:
- `browser.executablePath not found` → configured path is invalid.
- `browser.cdpUrl must be http(s) or ws(s)` → the configured CDP URL uses an unsupported scheme such as `file:` or `ftp:`.
- `browser.cdpUrl has invalid port` → the configured CDP URL has a bad or out-of-range port.
- `Could not find DevToolsActivePort for chrome` → Chrome MCP existing-session could not attach to the selected browser data dir yet. Open the browser inspect page, enable remote debugging, keep the browser open, approve the first attach prompt, then retry. If signed-in state is not required, prefer the managed `openclaw` profile.
- `No Chrome tabs found for profile="user"` → the Chrome MCP attach profile has no open local Chrome tabs.
- `Remote CDP for profile "<name>" is not reachable` → the configured remote CDP endpoint is not reachable from the gateway host.
- `Browser attachOnly is enabled ... not reachable` or `Browser attachOnly is enabled and CDP websocket ... is not reachable` → attach-only profile has no reachable target, or the HTTP endpoint answered but the CDP WebSocket still could not be opened.
@@ -471,7 +530,7 @@ What to check:
Common signatures:
- `refusing to bind gateway ... without auth` → non-loopback bind without a valid gateway auth path.
- `RPC probe: failed` while runtime is running → gateway alive but inaccessible with current auth/url.
- `Connectivity probe: failed` while runtime is running → gateway alive but inaccessible with current auth/url.
### 3) Pairing and device identity state changed

View File

@@ -160,7 +160,7 @@ Quick answers plus deeper troubleshooting for real-world setups (local dev, VPS,
cd openclaw
pnpm install
pnpm build
pnpm ui:build # auto-installs UI deps on first run
pnpm ui:build
openclaw onboard
```
@@ -767,7 +767,7 @@ for usage/billing and raise limits as needed.
<Accordion title="Telegram: what goes in allowFrom?">
`channels.telegram.allowFrom` is **the human sender's Telegram user ID** (numeric). It is not the bot username.
Onboarding accepts `@username` input and resolves it to a numeric ID, but OpenClaw authorization uses numeric IDs only.
Setup asks for numeric user IDs only. If you already have legacy `@username` entries in config, `openclaw doctor --fix` can try to resolve them.
Safer (no third-party bot):
@@ -1094,15 +1094,14 @@ for usage/billing and raise limits as needed.
<Accordion title="Cron fired, but nothing was sent to the channel. Why?">
Check the delivery mode first:
- `--no-deliver` / `delivery.mode: "none"` means no external message is expected.
- `--no-deliver` / `delivery.mode: "none"` means no runner fallback send is expected.
- Missing or invalid announce target (`channel` / `to`) means the runner skipped outbound delivery.
- Channel auth failures (`unauthorized`, `Forbidden`) mean the runner tried to deliver but credentials blocked it.
- A silent isolated result (`NO_REPLY` / `no_reply` only) is treated as intentionally non-deliverable, so the runner also suppresses queued fallback delivery.
For isolated cron jobs, the runner owns final delivery. The agent is expected
to return a plain-text summary for the runner to send. `--no-deliver` keeps
that result internal; it does not let the agent send directly with the
message tool instead.
For isolated cron jobs, the agent can still send directly with the `message`
tool when a chat route is available. `--announce` only controls the runner
fallback path for final text that the agent did not already send.
Debug:
@@ -1258,7 +1257,7 @@ for usage/billing and raise limits as needed.
openclaw browser --browser-profile chrome-live tabs
```
This path is host-local. If the Gateway runs elsewhere, either run a node host on the browser machine or use remote CDP instead.
This path can use the local host browser or a connected browser node. If the Gateway runs elsewhere, either run a node host on the browser machine or use remote CDP instead.
Current limits on `existing-session` / `user`:
@@ -1293,7 +1292,7 @@ for usage/billing and raise limits as needed.
<Accordion title="Can I keep DMs personal but make groups public/sandboxed with one agent?">
Yes - if your private traffic is **DMs** and your public traffic is **groups**.
Use `agents.defaults.sandbox.mode: "non-main"` so group/channel sessions (non-main keys) run in Docker, while the main DM session stays on-host. Then restrict what tools are available in sandboxed sessions via `tools.sandbox.tools`.
Use `agents.defaults.sandbox.mode: "non-main"` so group/channel sessions (non-main keys) run in the configured sandbox backend, while the main DM session stays on-host. Docker is the default backend if you do not choose one. Then restrict what tools are available in sandboxed sessions via `tools.sandbox.tools`.
Setup walkthrough + example config: [Groups: personal DMs + public groups](/channels/groups#pattern-personal-dms-public-groups-single-agent)
@@ -1629,10 +1628,20 @@ for usage/billing and raise limits as needed.
`config.apply` replaces the **entire config**. If you send a partial object, everything
else is removed.
Current OpenClaw protects many accidental clobbers:
- OpenClaw-owned config writes validate the full post-change config before writing.
- Invalid or destructive OpenClaw-owned writes are rejected and saved as `openclaw.json.rejected.*`.
- If a direct edit breaks startup or hot reload, the Gateway restores the last-known-good config and saves the rejected file as `openclaw.json.clobbered.*`.
- The main agent receives a boot warning after recovery so it does not blindly write the bad config again.
Recover:
- Restore from backup (git or a copied `~/.openclaw/openclaw.json`).
- If you have no backup, re-run `openclaw doctor` and reconfigure channels/models.
- Check `openclaw logs --follow` for `Config auto-restored from last-known-good`, `Config write rejected:`, or `config reload restored last-known-good config`.
- Inspect the newest `openclaw.json.clobbered.*` or `openclaw.json.rejected.*` beside the active config.
- Keep the active restored config if it works, then copy only the intended keys back with `openclaw config set` or `config.patch`.
- Run `openclaw config validate` and `openclaw doctor`.
- If you have no last-known-good or rejected payload, restore from backup, or re-run `openclaw doctor` and reconfigure channels/models.
- If this was unexpected, file a bug and include your last known config or any backup.
- A local coding agent can often reconstruct a working config from logs or history.
@@ -1644,7 +1653,7 @@ for usage/billing and raise limits as needed.
- Use `config.patch` for partial RPC edits; keep `config.apply` for full-config replacement only.
- If you are using the owner-only `gateway` tool from an agent run, it will still reject writes to `tools.exec.ask` / `tools.exec.security` (including legacy `tools.bash.*` aliases that normalize to the same protected exec paths).
Docs: [Config](/cli/config), [Configure](/cli/configure), [Doctor](/gateway/doctor).
Docs: [Config](/cli/config), [Configure](/cli/configure), [Gateway troubleshooting](/gateway/troubleshooting#gateway-restored-last-known-good-config), [Doctor](/gateway/doctor).
</Accordion>
@@ -2728,8 +2737,8 @@ Related: [/concepts/oauth](/concepts/oauth) (OAuth flows, token storage, multi-a
</Accordion>
<Accordion title='Why does openclaw gateway status say "Runtime: running" but "RPC probe: failed"?'>
Because "running" is the **supervisor's** view (launchd/systemd/schtasks). The RPC probe is the CLI actually connecting to the gateway WebSocket and calling `status`.
<Accordion title='Why does openclaw gateway status say "Runtime: running" but "Connectivity probe: failed"?'>
Because "running" is the **supervisor's** view (launchd/systemd/schtasks). The connectivity probe is the CLI actually connecting to the gateway WebSocket.
Use `openclaw gateway status` and trust these lines:

View File

@@ -22,7 +22,7 @@ This doc is a “how we test” guide:
Most days:
- Full gate (expected before push): `pnpm build && pnpm check && pnpm test`
- Full gate (expected before push): `pnpm build && pnpm check && pnpm check:test-types && pnpm test`
- Faster local full-suite run on a roomy machine: `pnpm test:max`
- Direct Vitest watch loop: `pnpm test:watch`
- Direct file targeting now routes extension/channel paths too: `pnpm test extensions/discord/src/monitor/message-handler.preflight.test.ts`
@@ -39,6 +39,11 @@ When debugging real providers/models (requires real creds):
- Live suite (models + gateway tool/image probes): `pnpm test:live`
- Target one live file quietly: `pnpm test:live -- src/agents/models.profiles.live.test.ts`
- Moonshot/Kimi cost smoke: with `MOONSHOT_API_KEY` set, run
`openclaw models list --provider moonshot --json`, then run an isolated
`openclaw agent --local --session-id live-kimi-cost --message 'Reply exactly: KIMI_LIVE_OK' --thinking off --json`
against `moonshot/kimi-k2.6`. Verify the JSON reports Moonshot/K2.6 and the
assistant transcript stores normalized `usage.cost`.
Tip: when you only need one failing case, prefer narrowing live tests via the allowlist env vars described below.
@@ -49,9 +54,11 @@ These commands sit beside the main test suites when you need QA-lab realism:
- `pnpm openclaw qa suite`
- Runs repo-backed QA scenarios directly on the host.
- Runs multiple selected scenarios in parallel by default with isolated
gateway workers, up to 64 workers or the selected scenario count. Use
`--concurrency <count>` to tune the worker count, or `--concurrency 1` for
the older serial lane.
gateway workers. `qa-channel` defaults to concurrency 4 (bounded by the
selected scenario count). Use `--concurrency <count>` to tune the worker
count, or `--concurrency 1` for the older serial lane.
- Exits non-zero when any scenario fails. Use `--allow-failures` when you
want artifacts without a failing exit code.
- Supports provider modes `live-frontier`, `mock-openai`, and `aimock`.
`aimock` starts a local AIMock-backed provider server for experimental
fixture and protocol-mock coverage without replacing the scenario-aware
@@ -69,6 +76,12 @@ These commands sit beside the main test suites when you need QA-lab realism:
`.artifacts/qa-e2e/...`.
- `pnpm qa:lab:up`
- Starts the Docker-backed QA site for operator-style QA work.
- `pnpm test:docker:bundled-channel-deps`
- Packs and installs the current OpenClaw build in Docker, starts the Gateway
with OpenAI configured, then enables Telegram and Discord via config edits.
- Verifies the first Gateway restart installs each bundled channel plugin's
runtime dependencies on demand, and a second restart does not reinstall
dependencies that were already activated.
- `pnpm openclaw qa aimock`
- Starts only the local AIMock provider server for direct protocol smoke
testing.
@@ -86,6 +99,8 @@ These commands sit beside the main test suites when you need QA-lab realism:
- Runs the Telegram live QA lane against a real private group using the driver and SUT bot tokens from env.
- Requires `OPENCLAW_QA_TELEGRAM_GROUP_ID`, `OPENCLAW_QA_TELEGRAM_DRIVER_BOT_TOKEN`, and `OPENCLAW_QA_TELEGRAM_SUT_BOT_TOKEN`. The group id must be the numeric Telegram chat id.
- Supports `--credential-source convex` for shared pooled credentials. Use env mode by default, or set `OPENCLAW_QA_CREDENTIAL_SOURCE=convex` to opt into pooled leases.
- Exits non-zero when any scenario fails. Use `--allow-failures` when you
want artifacts without a failing exit code.
- Requires two distinct bots in the same private group, with the SUT bot exposing a Telegram username.
- For stable bot-to-bot observation, enable Bot-to-Bot Communication Mode in `@BotFather` for both bots and ensure the driver bot can observe group bot traffic.
- Writes a Telegram QA report, summary, and observed-messages artifact under `.artifacts/qa-e2e/...`.
@@ -118,7 +133,7 @@ Required env vars:
- `OPENCLAW_QA_CONVEX_SECRET_CI` for `ci`
- Credential role selection:
- CLI: `--credential-role maintainer|ci`
- Env default: `OPENCLAW_QA_CREDENTIAL_ROLE` (defaults to `maintainer`)
- Env default: `OPENCLAW_QA_CREDENTIAL_ROLE` (defaults to `ci` in CI, `maintainer` otherwise)
Optional env vars:
@@ -273,6 +288,7 @@ Think of the suites as “increasing realism” (and increasing flakiness/cost):
- `pnpm test --watch` still uses the native root `vitest.config.ts` project graph, because a multi-shard watch loop is not practical.
- `pnpm test`, `pnpm test:watch`, and `pnpm test:perf:imports` route explicit file/directory targets through scoped lanes first, so `pnpm test extensions/discord/src/monitor/message-handler.preflight.test.ts` avoids paying the full root project startup tax.
- `pnpm test:changed` expands changed git paths into the same scoped lanes when the diff only touches routable source/test files; config/setup edits still fall back to the broad root-project rerun.
- `pnpm check:changed` is the normal smart local gate for narrow work. It classifies the diff into core, core tests, extensions, extension tests, apps, docs, and tooling, then runs the matching typecheck/lint/test lanes. Public Plugin SDK and plugin-contract changes include extension validation because extensions depend on those core contracts.
- Import-light unit tests from agents, commands, plugins, auto-reply helpers, `plugin-sdk`, and similar pure utility areas route through the `unit-fast` lane, which skips `test/setup-openclaw-runtime.ts`; stateful/runtime-heavy files stay on the existing lanes.
- Selected `plugin-sdk` and `commands` helper source files also map changed-mode runs to explicit sibling tests in those light lanes, so helper edits avoid rerunning the full heavy suite for that directory.
- `auto-reply` now has three dedicated buckets: top-level core helpers, top-level `reply.*` integration tests, and the `src/auto-reply/reply/**` subtree. This keeps the heaviest reply harness work off the cheap status/chunk/token tests.
@@ -294,6 +310,8 @@ Think of the suites as “increasing realism” (and increasing flakiness/cost):
- Each `pnpm test` shard inherits the same `threads` + `isolate: false` defaults from the shared Vitest config.
- The shared `scripts/run-vitest.mjs` launcher now also adds `--no-maglev` for Vitest child Node processes by default to reduce V8 compile churn during big local runs. Set `OPENCLAW_VITEST_ENABLE_MAGLEV=1` if you need to compare against stock V8 behavior.
- Fast-local iteration note:
- `pnpm changed:lanes` shows which architectural lanes a diff triggers.
- The pre-commit hook runs `pnpm check:changed --staged` after staged formatting/linting, so core-only commits do not pay extension test cost unless they touch public extension-facing contracts.
- `pnpm test:changed` routes through scoped lanes when the changed paths map cleanly to a smaller suite.
- `pnpm test:max` and `pnpm test:changed:max` keep the same routing behavior, just with a higher worker cap.
- Local worker auto-scaling is intentionally conservative now and also backs off when the host load average is already high, so multiple concurrent Vitest runs do less damage by default.

View File

@@ -28,8 +28,8 @@ Good output in one line:
- `openclaw status` → shows configured channels and no obvious auth errors.
- `openclaw status --all` → full report is present and shareable.
- `openclaw gateway probe` → expected gateway target is reachable (`Reachable: yes`). `RPC: limited - missing scope: operator.read` is degraded diagnostics, not a connect failure.
- `openclaw gateway status``Runtime: running` and `RPC probe: ok`.
- `openclaw gateway probe` → expected gateway target is reachable (`Reachable: yes`). `Capability: ...` tells you what auth level the probe could prove, and `Read probe: limited - missing scope: operator.read` is degraded diagnostics, not a connect failure.
- `openclaw gateway status``Runtime: running`, `Connectivity probe: ok`, and a plausible `Capability: ...` line. Use `--require-rpc` if you need read-scope RPC proof too.
- `openclaw doctor` → no blocking config/service errors.
- `openclaw channels status --probe` → reachable gateway returns live per-account
transport state plus probe/audit results such as `works` or `audit ok`; if the
@@ -117,7 +117,8 @@ flowchart TD
Good output looks like:
- `Runtime: running`
- `RPC probe: ok`
- `Connectivity probe: ok`
- `Capability: read-only`, `write-capable`, or `admin-capable`
- Your channel shows transport connected and, where supported, `works` or `audit ok` in `channels status --probe`
- Sender appears approved (or DM policy is open/allowlist)
@@ -147,7 +148,8 @@ flowchart TD
Good output looks like:
- `Dashboard: http://...` is shown in `openclaw gateway status`
- `RPC probe: ok`
- `Connectivity probe: ok`
- `Capability: read-only`, `write-capable`, or `admin-capable`
- No auth loop in logs
Common log signatures:
@@ -189,7 +191,8 @@ flowchart TD
- `Service: ... (loaded)`
- `Runtime: running`
- `RPC probe: ok`
- `Connectivity probe: ok`
- `Capability: read-only`, `write-capable`, or `admin-capable`
Common log signatures:

View File

@@ -47,13 +47,15 @@ The Ansible playbook installs and configures:
1. **Tailscale** -- mesh VPN for secure remote access
2. **UFW firewall** -- SSH + Tailscale ports only
3. **Docker CE + Compose V2** -- for agent sandboxes
3. **Docker CE + Compose V2** -- for the default agent sandbox backend
4. **Node.js 24 + pnpm** -- runtime dependencies (Node 22 LTS, currently `22.14+`, remains supported)
5. **OpenClaw** -- host-based, not containerized
6. **Systemd service** -- auto-start with security hardening
<Note>
The gateway runs directly on the host (not in Docker), but agent sandboxes use Docker for isolation. See [Sandboxing](/gateway/sandboxing) for details.
The gateway runs directly on the host (not in Docker). Agent sandboxing is
optional; this playbook installs Docker because it is the default sandbox
backend. See [Sandboxing](/gateway/sandboxing) for details and other backends.
</Note>
## Post-Install Setup

View File

@@ -14,7 +14,7 @@ Docker is **optional**. Use it only if you want a containerized gateway or to va
- **Yes**: you want an isolated, throwaway gateway environment or to run OpenClaw on a host without local installs.
- **No**: you are running on your own machine and just want the fastest dev loop. Use the normal install flow instead.
- **Sandboxing note**: agent sandboxing uses Docker too, but it does **not** require the full gateway to run in Docker. See [Sandboxing](/gateway/sandboxing).
- **Sandboxing note**: the default sandbox backend uses Docker when sandboxing is enabled, but sandboxing is off by default and does **not** require the full gateway to run in Docker. SSH and OpenShell sandbox backends are also available. See [Sandboxing](/gateway/sandboxing).
## Prerequisites
@@ -311,10 +311,11 @@ including binary baking, persistence, and updates.
## Agent Sandbox
When `agents.defaults.sandbox` is enabled, the gateway runs agent tool execution
(shell, file read/write, etc.) inside isolated Docker containers while the
gateway itself stays on the host. This gives you a hard wall around untrusted or
multi-tenant agent sessions without containerizing the entire gateway.
When `agents.defaults.sandbox` is enabled with the Docker backend, the gateway
runs agent tool execution (shell, file read/write, etc.) inside isolated Docker
containers while the gateway itself stays on the host. This gives you a hard wall
around untrusted or multi-tenant agent sessions without containerizing the entire
gateway.
Sandbox scope can be per-agent (default), per-session, or shared. Each scope
gets its own workspace mounted at `/workspace`. You can also configure

View File

@@ -213,18 +213,21 @@ For the generic Docker flow, see [Docker](/install/docker).
```bash
OPENCLAW_IMAGE=openclaw:latest
OPENCLAW_GATEWAY_TOKEN=change-me-now
OPENCLAW_GATEWAY_TOKEN=
OPENCLAW_GATEWAY_BIND=lan
OPENCLAW_GATEWAY_PORT=18789
OPENCLAW_CONFIG_DIR=/home/$USER/.openclaw
OPENCLAW_WORKSPACE_DIR=/home/$USER/.openclaw/workspace
GOG_KEYRING_PASSWORD=change-me-now
GOG_KEYRING_PASSWORD=
XDG_CONFIG_HOME=/home/node/.openclaw
```
Generate strong secrets:
Leave `OPENCLAW_GATEWAY_TOKEN` blank unless you explicitly want to
manage it through `.env`; OpenClaw writes a random gateway token to
config on first start. Generate a keyring password and paste it into
`GOG_KEYRING_PASSWORD`:
```bash
openssl rand -hex 32

View File

@@ -134,18 +134,21 @@ For the generic Docker flow, see [Docker](/install/docker).
```bash
OPENCLAW_IMAGE=openclaw:latest
OPENCLAW_GATEWAY_TOKEN=change-me-now
OPENCLAW_GATEWAY_TOKEN=
OPENCLAW_GATEWAY_BIND=lan
OPENCLAW_GATEWAY_PORT=18789
OPENCLAW_CONFIG_DIR=/root/.openclaw
OPENCLAW_WORKSPACE_DIR=/root/.openclaw/workspace
GOG_KEYRING_PASSWORD=change-me-now
GOG_KEYRING_PASSWORD=
XDG_CONFIG_HOME=/home/node/.openclaw
```
Generate strong secrets:
Leave `OPENCLAW_GATEWAY_TOKEN` blank unless you explicitly want to
manage it through `.env`; OpenClaw writes a random gateway token to
config on first start. Generate a keyring password and paste it into
`GOG_KEYRING_PASSWORD`:
```bash
openssl rand -hex 32

View File

@@ -115,7 +115,7 @@ For contributors or anyone who wants to run from a local checkout:
```bash
git clone https://github.com/openclaw/openclaw.git
cd openclaw
pnpm install && pnpm ui:build && pnpm build
pnpm install && pnpm build && pnpm ui:build
pnpm link --global
openclaw onboard --install-daemon
```

View File

@@ -225,7 +225,7 @@ Events handled include:
- `tool_execution_start` / `tool_execution_update` / `tool_execution_end`
- `turn_start` / `turn_end`
- `agent_start` / `agent_end`
- `auto_compaction_start` / `auto_compaction_end`
- `compaction_start` / `compaction_end`
### 4. Prompting

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