Compare commits

..

1302 Commits

Author SHA1 Message Date
Peter Steinberger
aa1c472a21 fix: preserve outbound thread sessions 2026-04-22 02:38:42 +01:00
Bek
848129c05b fix(slack): harden thread continuation recovery 2026-04-22 02:37:44 +01:00
Bek
b14fe065bb fix(slack): preserve gateway thread continuations 2026-04-22 02:37:44 +01:00
claycurry
6a68f1dd57 Docs: link feature cards to relevant pages
Link docs feature cards to their intended destination pages in the English docs surfaces.

- add hrefs to the feature cards in docs/concepts/features.md
- add hrefs to the key capability cards in docs/index.md
- preserve current main branch copy while landing the navigation fix
2026-04-21 20:36:55 -05:00
Peter Steinberger
fb9a21ae8f fix: centralize draft preview finalization 2026-04-22 02:32:55 +01:00
Peter Steinberger
ffef84dea7 ci: start runtime tests without dist 2026-04-22 02:27:03 +01:00
Peter Steinberger
e5909f3e5d ci: scope mlx helper as macos native 2026-04-22 02:19:58 +01:00
Peter Steinberger
e836b5b6d7 ci: isolate mlx from macos swift checks 2026-04-22 02:12:07 +01:00
Peter Steinberger
710e4e9e51 ci: widen package boundary cache inputs 2026-04-22 01:53:22 +01:00
Gustavo Madeira Santana
f4478a142a Fix channel presence gating for disabled plugins (#69862)
Merged via squash.

Prepared head SHA: f76f6212b2
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-04-21 20:51:09 -04:00
Peter Steinberger
eb6006730d fix(line): guard outbound media targets 2026-04-22 01:48:14 +01:00
Peter Steinberger
66576f3355 test(extensions): fix lint-clean test assertions 2026-04-22 01:43:18 +01:00
Peter Steinberger
d57fe63ee0 ci: cache package boundary artifacts 2026-04-22 01:42:44 +01:00
Peter Steinberger
5c74e9da01 fix(qqbot): avoid eager storage directory creation 2026-04-22 01:42:10 +01:00
Peter Steinberger
540171ddbd docs: clarify ACP delivery model 2026-04-22 01:32:20 +01:00
Peter Steinberger
73d9746e6a ci: reuse swift build cache for unchanged inputs 2026-04-22 01:30:40 +01:00
Peter Steinberger
ce05418930 ci: preserve exact swift build cache 2026-04-22 01:26:05 +01:00
Gustavo Madeira Santana
819d15481d fix: validate plugin source entries before runtime inference (#69868)
Merged via squash.

Prepared head SHA: b67644cdda
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-04-21 20:12:19 -04:00
Gustavo Madeira Santana
19354c9a6a fix(discord): keep slash follow-ups ephemeral (#69869)
Merged via squash.

Prepared head SHA: 0f5ab77156
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-04-21 20:02:59 -04:00
Ron Cohen
08bc16853e WhatsApp: add group and direct system prompt support (#59553)
Merged via squash.

Prepared head SHA: 63e2b50e01
Co-authored-by: Bluetegu <1525690+Bluetegu@users.noreply.github.com>
Co-authored-by: omarshahine <10343873+omarshahine@users.noreply.github.com>
Reviewed-by: @omarshahine
2026-04-21 16:40:32 -07:00
Gustavo Madeira Santana
06a6dd5a6b chore(docs): update stale docs ref 2026-04-21 19:33:36 -04:00
Peter Steinberger
37463af5e1 ci: increase package boundary compile concurrency 2026-04-22 00:26:03 +01:00
Onur Solmaz
99787dbf45 docs(skills): add duplicate triage maintainer skill (#69780)
* docs(skills): add duplicate triage maintainer skill

* docs(skills): align duplicate triage with prtags sync

* docs(skills): remove local paths from duplicate triage skill

* docs(skills): use pr-search-cli naming consistently

* docs(skills): fix pr-search-cli command usage

* docs(skills): use tested release install commands

* docs(skills): treat prtags comment sync as automatic

* docs(skills): adjust duplicate triage skill title

* docs(skills): add duplicate triage UI metadata
2026-04-22 01:18:07 +02:00
Peter Steinberger
85c63942a5 ci: skip swift package patch in macos node lane 2026-04-22 00:16:45 +01:00
Peter Steinberger
a426ef5b6a ci: preserve swift build cache hits 2026-04-22 00:12:03 +01:00
Bek
e116b343b2 feat(slack): Annotate inbound Slack mention tokens in Slack RawBody and BodyForAgent content so the agent sees both the actionable Slack mention token and a human-readable name. (#65731)
* Annotate inbound Slack mentions in raw bodies

* Avoid shared regex state in Slack mention rendering

* Bound Slack mention lookups with concurrency

* slack: keep mention concurrency helper plugin-local

* test: stabilize node core CI assertions

* slack: cap mention lookups per inbound message

* test: reset suite gateway runtime state

* fix(slack): reuse plugin sdk concurrency helper
2026-04-21 19:03:50 -04:00
Peter Steinberger
6bf56d8637 ci: cap android checkout and use build cache 2026-04-22 00:02:40 +01:00
Peter Steinberger
cc8ecde364 ci: avoid external gradle action in android checks 2026-04-21 23:56:52 +01:00
Peter Steinberger
6966f018f7 ci: quiet mlx swift manifest warnings 2026-04-21 23:52:04 +01:00
Peter Steinberger
e822e71410 ci: cap stuck checkout retries 2026-04-21 23:47:17 +01:00
Peter Steinberger
df3fcbd716 test: lazy-load openai provider catalog contract 2026-04-21 23:35:37 +01:00
Bek
70683179a0 fix(slack): narrow first turn context seeding to remove redundant thread-starter content (#68402)
Fix Slack thread bootstrap replaying the bot's own prior turns into new sessions and duplicating the thread-starter prompt block.

Narrows first-turn context seeding to exclude only the current Slack bot's own starter/history entries, so self-authored turns no longer pollute new session prompts while preserving human and third-party bot context

Removes the redundant plain-text starter prelude in runPreparedReply() that doubled thread-starter content when no ThreadHistoryBody was present
2026-04-21 18:28:34 -04:00
Peter Steinberger
acf67c1a42 docs: tighten optimizetests skill 2026-04-21 23:24:51 +01:00
Bek
dfe0e49c8a fix(qmd): Dedup in-flight manager creation so only one full QMD manager arms per agent/config at a time, eliminating the concurrent exportSessions() collisions that triggered path changed during write errors (#65226)
Fixes concurrent manager creation races that caused SafeOpenErrors during session export.

Deduplicates in-flight manager creation so only one full QMD manager arms per agent/config at a time, eliminating the concurrent exportSessions() collisions that triggered path changed during write errors
Resolves and snapshots runtime inputs before cache reuse, replacing stale managers atomically when workspace/config changes, and aborting queued export work promptly on close()
2026-04-21 18:22:21 -04:00
Bek
1acb094579 fix: wrap oversized session lines before JSONL write (#64494)
updates the real session-export path so pathological transcript messages no longer become a single toxic export line for downstream indexing.
2026-04-21 18:18:22 -04:00
Gustavo Madeira Santana
66add9fcd9 perf(cli): lazy-load doctor plugin paths (#69840)
Merged via squash.

Prepared head SHA: ebf93ad913
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-04-21 18:17:19 -04:00
Bek
0e1d324dd8 fix(agents): Wake active requester sessions for subagent completions while keeping dormant sessions externally deliverable (#62963)
Route subagent completion announces through embedded-run wake for active requesters, preserve external delivery for dormant ones
2026-04-21 18:13:53 -04:00
Bek
14dcbd4044 fix(prompt): align system prompt messaging and subagent routing guidance (#64059)
replace legacy `to` with `target` in prompt
2026-04-21 18:10:53 -04:00
Peter Steinberger
824c4785e4 test: speed channel contract suites 2026-04-21 23:09:22 +01:00
Devin Robison
ee316dbc4b fix(tlon): guard memex upload target (#69794)
* fix(tlon): guard memex upload target

* fix(tlon): harden guarded memex upload

* fix(tlon): validate hosted memex upload targets

* fix(tlon): tighten hosted domain matching

* fix(tlon): reject non-standard memex upload ports

* fix(tlon): disable memex upload redirects

* test(tlon): drop redundant mock resets in memex upload test

* chore(lint): update tlon raw-fetch allowlist for guarded memex upload

* fix(tlon): reject unparseable ship URLs in hosted-ship classifier

* fix(lint): point tlon raw-fetch allowlist at fetch callee lines

* fix(tlon): guard custom-S3 upload through fetchWithSsrFGuard

* fix(tlon): preserve scheme-less hosted ship routing and allow explicit :443

* docs(changelog): note tlon upload guard

* fix(tlon): guard memex lookup and private s3 opt-in

* fix(tlon): validate upload result URLs
2026-04-21 15:57:49 -06:00
Peter Steinberger
74668ea8a1 fix(image-generation): log provider fallback failures 2026-04-21 22:50:09 +01:00
Vincent Koc
b5c4aaf2a7 fix(install): mirror node-domexception override for npm (#69819)
* fix(install): mirror node-domexception override for npm

* docs(changelog): credit npm install override fix

* fix(install): pin domexception override exactly

* docs(changelog): drop leftover npm fix merge markers

* Update CHANGELOG.md
2026-04-21 14:45:05 -07:00
Peter Steinberger
d1e3789e15 test: optimize slow test hotspots 2026-04-21 22:42:08 +01:00
Bek
49b233caa1 fix(slack): preserve thread aliases in runtime outbound sends (#62947)
Slack-threaded direct sends that go through the generic runtime wrapper now stay in the intended thread when the caller supplies threadTs.
2026-04-21 17:40:47 -04:00
Vincent Koc
475e6ff1d1 docs(gateway): replace user-facing 'extension' references with 'plugin' per terminology rules 2026-04-21 14:39:10 -07:00
Peter Steinberger
d2f68af615 docs: document Ollama image understanding 2026-04-21 22:33:56 +01:00
Vincent Koc
f1f6214fd5 docs(help): add frontmatter to gpt54-codex parity docs 2026-04-21 14:29:58 -07:00
Peter Steinberger
e71e543350 fix: route explicit image describe models 2026-04-21 22:25:45 +01:00
Peter Steinberger
a7ff7dd945 docs: note Ollama image routing (#69816) (thanks @soloclz) 2026-04-21 22:25:45 +01:00
soloclz
9a22cd212b fix(ollama): register media-understanding provider so image tool can route ollama/* models
Ollama chat models already support image inputs (extensions/ollama/src/stream.ts
extracts image parts and forwards them via the Ollama API), but the ollama
plugin did not register a MediaUnderstandingProvider. The image tool's provider
registry therefore had no 'ollama' entry, so requests like
`imageModel: 'ollama/qwen2.5vl:7b'` failed to resolve and fell back to
unrelated providers.

Register ollamaMediaUnderstandingProvider with:
- capabilities: ['image']
- describeImage/describeImages wired to the shared core helpers (reuses the
  same pi-ai complete path Ollama chat already goes through)
- no defaultModels or autoPriority: Ollama vision support depends on which
  model the user has pulled, so we don't pick a canonical default and don't
  auto-steal image duty from configured providers.

Fixes #69071 (and supersedes #60280).
2026-04-21 22:25:45 +01:00
Vincent Koc
b2f96f7f05 docs(providers): alphabetize Cloudflare/ComfyUI and vLLM/Vydra entries 2026-04-21 14:25:31 -07:00
Devin Robison
7be82d4fd1 fix(openshell): pin host writes to sandbox root (#69797)
* fix(openshell): pin host writes to sandbox root

* fix(openshell): use plugin sdk infra runtime

* fix(openshell): reject symlink write targets

* chore(changelog): note openshell sandbox write fix
2026-04-21 15:18:28 -06:00
Peter Steinberger
ae4c5cd460 fix: land ACP child sessions_send guard (#69817) (thanks @scotthuang) 2026-04-21 22:17:28 +01:00
scotthuang
8a7c21407a fix(agents): gate sessions_send A2A skip on requester ownership
Greptile/Codex review follow-ups on #69817:

- Narrow skipA2AFlow from target-only detection to a combined check that
  the caller is the parent of the target (new
  isRequesterParentOfBackgroundAcpSession helper). Under
  tools.sessions.visibility=all a non-parent sender can see the same
  oneshot ACP session; the previous guard would have suppressed their
  only follow-up delivery path. With requester ownership required, those
  senders continue through the normal A2A flow.
- When the A2A flow is skipped, return delivery.status="skipped" instead
  of "pending" so the parent LLM does not wait for a second result that
  will never arrive.
- Add unit tests for resolveAcpSessionInteractionMode and
  isRequesterParentOfBackgroundAcpSession covering both the new
  ownership gate and the existing target-type branches.
2026-04-21 22:17:28 +01:00
scotthuang
1c3fbbd72a fix(agents): skip sessions_send A2A flow for parent-owned ACP children
The A2A ping-pong + announce flow in runSessionsSendA2AFlow treats the
send target as a peer agent and echoes replies back and forth between
requester and target. When the target is an ACP child spawned by the
requester, this creates an infinite loop: the parent is woken with the
child's reply, generates a user-facing response, and has that response
forwarded back to the child as a new user message — effectively granting
the child an implicit sessions_send capability back to the parent.

ACP children already report their results through the
[Internal task completion event] announcement path, so no A2A flow is
needed when the send target is a parent-owned background ACP session.

Detect this case via isParentOwnedBackgroundAcpSession and short-circuit
startA2AFlow before runSessionsSendA2AFlow is invoked.
2026-04-21 22:17:28 +01:00
Vincent Koc
ff67a890af docs(channels): clean troubleshooting link labels, generic imessage path placeholder, drop msteams stamped date 2026-04-21 13:59:12 -07:00
Peter Steinberger
8d1b3d4578 ci: speed up release metadata pre-commit checks 2026-04-21 21:56:06 +01:00
Peter Steinberger
aa94501f5f feat(openai): default images to gpt-image-2 2026-04-21 21:49:16 +01:00
Peter Steinberger
0b1a35363e chore: start 2026.4.21 development 2026-04-21 21:42:15 +01:00
Vincent Koc
8f1a87ea47 docs: note Kimi K2.6 thinking-disabled on Fireworks and Ollama cloud onboard live-tag fetch 2026-04-21 13:41:10 -07:00
Vincent Koc
9702f0bf21 docs: tool-progress preview streaming, Control UI avatar auth, exec heredoc and external-content token sanitization 2026-04-21 13:39:55 -07:00
Devin Robison
3cb1a56bfc fix(gateway): derive loopback owner context from token (#69796)
* fix(gateway): derive loopback owner context from token

* docs(changelog): note loopback owner token hardening

* refactor(gateway): clarify loopback runtime cleanup

* fix(gateway): compare both loopback bearer classes
2026-04-21 14:39:48 -06:00
Peter Steinberger
674feda214 docs(plugins): document message presentation cards 2026-04-21 21:29:44 +01:00
Peter Steinberger
c742a706bf feat(plugins): add experimental skill workshop 2026-04-21 21:29:44 +01:00
Peter Steinberger
fd0970c077 refactor(channels): decouple presentation rendering 2026-04-21 21:29:44 +01:00
Peter Steinberger
d7a173e60e feat(plugin-sdk): add presentation and skills runtime contracts 2026-04-21 21:29:44 +01:00
Vincent Koc
78030d0d52 docs: plugin manifest precedence, QQBot engine/bot-approve/QR onboarding, web-search plugin-scoped SecretRefs 2026-04-21 13:26:25 -07:00
Vincent Koc
b4a59be9b6 docs: document stdio env filter, enforceOwnerForCommands, OPENCLAW_* .env blocking 2026-04-21 13:21:34 -07:00
Vincent Koc
32ccf27e60 docs: document WS broadcast scope gating and Control UI img-src CSP 2026-04-21 13:14:15 -07:00
Vincent Koc
7d7c0b1dfe docs: cover BB tapback fallback, iMessage/SMS routing, Mattermost streaming, Matrix mention-prefixed slash 2026-04-21 13:09:09 -07:00
Peter Steinberger
e5af4e3b5c ci(deps): gate extension-owned root dependencies 2026-04-21 21:08:08 +01:00
Devin Robison
b2e8b7d4bb fix(exec): block heredoc parameter expansion (#69795)
* fix(exec): block heredoc parameter expansion

* chore(changelog): note heredoc parameter expansion fix

* fix(exec): tighten heredoc expansion guardrails

* fix(exec): reject continued heredoc expansions

* fix(exec): buffer heredoc continuation chunks

* fix(exec): harden heredoc continuation parsing

* fix(exec): cap heredoc continuation chunks

* fix(exec): reject continued heredoc param expansion across delimiter

Bash splices `$VAR\\<newline>REST` into `$VARREST` inside an
unquoted heredoc body even when the continued physical line matches the
heredoc delimiter; the heredoc only terminates at EOF with a warning.
The analyzer previously shifted the pending heredoc the moment a line
equaled the delimiter, so a payload like `cat <<KEY\n$OPENAI_API_\\\nKEY`
passed allowlist review while the runtime would expand and print
$OPENAI_API_KEY.

Mirror bash's splicing: only treat a delimiter-matching line as the
terminator when no continuation chunks are pending, otherwise append it
to the logical line and evaluate it through the expansion check. The
tail handler does the same splice + expansion check before falling back
to "unterminated heredoc".
2026-04-21 14:01:35 -06:00
Peter Steinberger
ccfef0f13f chore: update appcast for 2026.4.20 2026-04-21 21:01:19 +01:00
Peter Steinberger
8d289306de ci: support release branch mac validation 2026-04-21 21:01:05 +01:00
Devin Robison
2ce16e558e fix(gateway): require auth for control UI avatar route (#69775)
* fix(gateway): require auth for control UI avatar route

* chore: add changelog for control UI avatar auth

* fix(control-ui): honor device auth for avatar urls

* fix(control-ui): avoid query tokens for avatar auth

* fix(control-ui): render authenticated avatar blob URLs in chat views

* fix(control-ui): restore normalizeOptionalString import in render helpers
2026-04-21 13:51:03 -06:00
Gustavo Madeira Santana
6b185e2849 perf: speed up discord channel registration (#69791)
Merged via squash.

Prepared head SHA: 231d8763b4
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-04-21 15:48:08 -04:00
Peter Steinberger
895ac965da test: cover Telegram session recreation 2026-04-21 20:36:32 +01:00
Peter Steinberger
0a6ce260ed fix(deps): keep qqbot connector plugin-local 2026-04-21 20:33:16 +01:00
Peter Steinberger
6f004ed4d4 feat(fireworks): add Kimi K2.6 model 2026-04-21 20:31:33 +01:00
Peter Steinberger
2514746b32 fix: sanitize LLM special tokens in external content 2026-04-21 20:29:02 +01:00
Shakker
fb7bfb411c docs: add Copilot Opus changelog (#69818) (thanks @shakkernerd) 2026-04-21 20:00:06 +01:00
Shakker
2161ed8259 fix: update Copilot Opus default to 4.7 2026-04-21 20:00:06 +01:00
Peter Steinberger
11efbf5a2e fix: prevent stale subagent failure announces 2026-04-21 19:59:12 +01:00
Tak Hoffman
dcf131e54c docs: restore general multi-gateway guidance (#69810) 2026-04-21 13:34:18 -05:00
Peter Steinberger
47cfdd2df1 test: cover active provider thinking registry 2026-04-21 19:24:26 +01:00
Peter Steinberger
61564147f3 fix: break provider thinking import cycle 2026-04-21 19:19:03 +01:00
Peter Steinberger
b2b43085bc ci: use larger Blacksmith macOS runners 2026-04-21 19:03:50 +01:00
Tak Hoffman
5218c1a01f docs: front-load rescue bot quickstart (#69803)
* docs: front-load rescue bot quickstart

* docs: recommend rescue port 19789

* docs: show rescue port in quickstart command
2026-04-21 13:01:23 -05:00
Agustin Rivera
38356c658a fix(synology): validate webhook file urls (#69784)
* fix(synology): validate webhook file urls

* fix(synology): restore file send throttle

* docs(changelog): note synology webhook file_url SSRF guard (#69784)

---------

Co-authored-by: Devin Robison <drobison@nvidia.com>
2026-04-21 12:00:28 -06:00
Peter Steinberger
bcfa781a1b fix: remap thinking levels on model switch 2026-04-21 18:53:49 +01:00
Gustavo Madeira Santana
24db09a19b fix(cli): keep channel status checks off plugin runtimes (#69479)
Merged via squash.

Prepared head SHA: 63f6e416a9
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-04-21 13:53:08 -04:00
Tak Hoffman
09c5669299 docs: clarify rescue bot gateway setup (#69788)
* docs: clarify rescue bot gateway setup

* docs: make rescue bot guide more prescriptive
2026-04-21 12:29:40 -05:00
Gustavo Madeira Santana
ddc1d9aa54 perf: speed up telegram channel registration (#69786)
Merged via squash.

Prepared head SHA: ac03f96e0d
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-04-21 13:24:28 -04:00
cxy
5e72e39c18 feat(qqbot): extract self-contained engine/ architecture with QR-code onboarding, approval handling (#67960)
* feat(qqbot): add core architecture modules

* feat(qqbot): extract engine modules with DI adapters

* refactor(qqbot): remove plugin-level TTS, delegate to framework

Remove qqbot's internal TTS implementation and unify voice synthesis
through the framework's global TTS provider registry.

- Delete engine/gateway/tts-config.ts (plugin-specific TTS config)
- Simplify TTSProvider interface to textToSpeech + audioFileToSilkBase64
- Remove dual-strategy TTS in handleAudioPayload (plugin + global fallback)
- Strip QQBotTtsSchema from config-schema, plugin.json, and tests
- Remove TTS diagnostics logging and hasTTS system prompt from gateway
- Delete ~260 lines of TTS code from utils/audio-convert.ts

Made-with: Cursor

* feat(qqbot): extract shared engine modules for config, tools, and audio

Add engine-layer modules that are self-contained and portable across
both the built-in and standalone qqbot packages:

- engine/config: account resolution helpers, field readers
- engine/tools: channel API proxy, remind scheduling logic
- engine/utils: audio format conversion, duration/error formatting,
  debug logging

Consolidate duplicate utility functions across the codebase:

- Merge debug-log.ts into log.ts
- Merge error-format.ts into format.ts with full .cause chain support
- Unify normalizeLowercase/readNumber/readBoolean/readStringMap into
  string-normalize.ts, removing private copies in resolve.ts,
  remind-logic.ts, and audio-convert.ts
- Remove dead formatDuration export from audio-convert.ts
- Delete unused config/schema.ts and config/helpers.ts

Made-with: Cursor

* refactor(qqbot): streamline account configuration and credential management

Refactor the QQBot account configuration logic by consolidating credential management into dedicated engine modules. Key changes include:

- Migrate credential clearing and validation logic to engine/config/credentials.ts.
- Simplify setup input validation and application in engine/config/setup-logic.ts.
- Enhance account resolution and configuration application in engine/config/resolve.ts.
- Update channel and messaging logic to utilize the new credential management functions.

This refactor improves code maintainability and clarity by separating concerns and reducing duplication across the codebase.

* feat(qqbot): simplify api architecture

* feat: 支持扫码绑定QQ机器人

* feat(qqbot): refactor gateway into inbound pipeline + outbound dispatch

- Extract handleMessage (620 lines) into three modules:
  - inbound-context.ts: InboundContext type definition
  - inbound-pipeline.ts: buildInboundContext()
  - outbound-dispatch.ts: dispatchOutbound()
- gateway.ts handleMessage reduced to ~35 line shell
- Unify parseRefIndices: support both ext prefix formats + MSG_TYPE_QUOTE
- Add ref/format-message-ref.ts for cache-miss quote formatting
- Remove [QQBot] to= from agentBody, use GroupSystemPrompt instead
- QueuedMessage: add msgType/msgElements for quote messages

* fix(qqbot): fix markdownSupport loss + dynamic User-Agent

Root cause: setOpenClawVersion() called _ensureInitialized(true) which
cleared _appRegistry, destroying the MessageApi instance created by
initApiConfig() with markdownSupport=true. Subsequent block deliver
calls created a default markdownSupport=false instance, causing:
1. Markdown messages sent as plain text (msg_type=0 instead of 2)
2. message_reference incorrectly added (only suppressed in MD mode)

Fix: ApiClient and TokenManager now accept userAgent as string | (() => string).
sender.ts passes the buildUserAgent function reference, so UA changes
propagate automatically on next request without rebuilding any objects.

- ApiClient: userAgent -> resolveUserAgent getter, called per-request
- TokenManager: same pattern
- types.ts: ApiClientConfig.userAgent supports string | (() => string)
- sender.ts: remove force re-init + _rebuildAppRegistry hack
  - initSender/setOpenClawVersion only update version variables
  - _ensureInitialized creates singletons once, never destroys them
  - _appRegistry is never cleared -> markdownSupport always preserved
- runtime.ts: inject framework version via setOpenClawVersion(runtime.version)
- gateway.ts: pass openclawVersion to initSender + registerPluginVersion
- slash-commands-impl.ts: remove fragile require("../package.json")

* feat(qqbot): implement native approval handling and configuration

Add a new approval handling system for QQBot that integrates with the existing framework. Key features include:

- Introduce `approval-handler.runtime.ts` for managing approval requests via QQ messages with inline keyboard support.
- Create `approval-native.ts` as the entry point for QQBot's approval capability, allowing for simplified approval processes without explicit approver lists.
- Implement configuration schema for exec approvals, enabling fine-grained control over who can approve requests.
- Enhance messaging and interaction handling to support approval decisions through button interactions.

This implementation streamlines the approval process, making it more user-friendly and efficient for QQBot users.

* refactor(qqbot): enhance error handling across API and messaging modules

This update introduces a centralized error formatting utility, `formatErrorMessage`, to improve consistency in error logging throughout the QQBot codebase. Key changes include:

- Integration of `formatErrorMessage` in various API client, messaging, and gateway modules to standardize error messages.
- Replacement of direct error message handling with the new utility to enhance readability and maintainability.

These improvements streamline error reporting and provide clearer insights into issues encountered during operation.

* refactor(qqbot): enhance API and messaging structure with type improvements

This update refines the API and messaging modules by introducing type enhancements and restructuring function signatures for better clarity and maintainability. Key changes include:

- Updated import statements to streamline type usage in  and .
- Refactored message sending functions to accept options objects, improving readability and flexibility.
- Introduced a new  method in  to facilitate external message-sent notifications.
- Enhanced error handling in the retry mechanism to ensure more robust behavior.

These modifications aim to improve the overall code quality and developer experience within the QQBot framework.

* feat: 优化文案

* refactor(qqbot): unify Logger interfaces + eliminate P0 code smells

Logger unification (17 files):
- Introduce single EngineLogger interface in engine/types.ts
  { info, error, warn?, debug? }
- Delete 5 fragmented Logger interfaces:
  GatewayLogger, ReconnectLogger, MessageRefLogger, PathLogger, SenderLogger
- Replace all references across engine/ to use EngineLogger directly

P0 code smell fixes (sender.ts + messages.ts + outbound-dispatch.ts):
- messages.ts: add public notifyMessageSent() method on MessageApi,
  replacing 8x 'as unknown as { messageSentHook }' private field hack
- sender.ts: extract notifyMediaHook() helper, deduplicate 4 media
  send functions (sendImage/sendVoice/sendVideo/sendFile)
- sender.ts: replace magic numbers 1/2/3/4 with MediaFileType enum
- sender.ts: remove 4 redundant 'as MessageResponse' type assertions
- outbound-dispatch.ts: remove 5 unnecessary 'as never' casts

* feat(qqbot): add /bot-clear-storage command + consolidate utils/types into engine/

/bot-clear-storage (slash-commands-impl.ts):
- Migrate from standalone version, aligned with its two-step flow:
  1. No args: scan ~/.openclaw/media/qqbot/downloads/{appId}/ and
     display file list with confirmation button
  2. --force: delete files + removeEmptyDirs cleanup
- C2C only (group chat returns hint)
- bot-help: exclude bot-upgrade and bot-clear-storage in group listings

Consolidate into engine/:
- Delete src/utils/audio-convert.ts (pure re-export shell, zero consumers)
- Move 5 test files from src/utils/ to src/engine/utils/ (fix import paths)
- Move src/types/silk-wasm.d.ts to src/engine/types/
- Remove empty src/utils/ and src/types/ directories

* refactor(qqbot): restructure API and bridge components for improved modularity

This update enhances the QQBot framework by reorganizing the API and bridge components, promoting better modularity and maintainability. Key changes include:

- Refactored import paths to streamline access to bridge tools and configurations.
- Introduced new bridge files for channel entry, runtime, and approval capabilities, centralizing related functionalities.
- Updated existing functions to utilize the new bridge structure, ensuring consistency across the codebase.
- Removed deprecated functions and types, simplifying the overall architecture.

These modifications aim to improve code clarity and facilitate future development within the QQBot ecosystem.

* refactor(qqbot): standardize engine log levels and unify log tag prefix

- Rename client.ts to api-client.ts to match ApiClient class name
- Downgrade ~60 non-critical info logs to debug level across 12 files
  (token request/response, HTTP request/response, session restore,
  media tag detection, image classification, quote detection,
  attachment download/transcode, retry attempts, etc.)
- Unify log tag prefix to [qqbot:xxx] format across all engine modules
  ([core-api] -> [qqbot:api], [token:x] -> [qqbot:token:x],
  [retry] -> [qqbot:retry], [messages] -> [qqbot:messages],
  [sender:x] -> [qqbot:x])
- Remove unnecessary reqTs timestamp from api-client.ts log output
- Add dispatch event debug log in gateway-connection.ts
- Merge sendProactiveMessage into sendText, remove dead code
  (sendProactiveText import, getRefIdx, QQMessageResult type)
- Narrow allow-from.ts type from unknown[] to Array<string | number>

* refactor(qqbot): move interaction handler from bridge to engine

- Move onInteraction approval handler into engine/gateway.ts as
  createApprovalInteractionHandler(), eliminating the callback
  indirection through CoreGatewayContext
- Remove onInteraction from CoreGatewayContext interface and its
  unused InteractionEvent import from gateway/types.ts
- Remove getPlatformAdapter, parseApprovalButtonData and
  InteractionEvent imports from bridge/gateway.ts

* refactor(qqbot): route bridge and sender logs through framework logger

- Add bridge/logger.ts as a shared logger holder for bridge-layer
  modules, injected with ctx.log during gateway startup
- Replace all console.log/console.error in bridge/ with
  getBridgeLogger() calls (approval, bootstrap, tools)
- Restore framework logger support in sender.ts via initSender()
  so API-layer logs flow through OpenClaw log system
- Remove all direct debugLog/debugError imports from bridge/

* feat(qqbot): per-account isolated resource stack + multi-account logger

- sender.ts: global singletons (ApiClient/TokenManager/MediaApi) -> per-account AccountContext
  - Add _accountRegistry: Map<appId, AccountContext>
  - Each account owns independent client/tokenMgr/mediaApi/messageApi/logger
  - registerAccount() atomically sets up all resources
  - resolveAccount() routes to correct resource stack by appId
  - Remove _sharedLogger/_loggerRegistry/_appRegistry and old structures

- bridge/gateway.ts: createAccountLogger() with auto [accountId] prefix
  - registerAccount() merges logger + markdownSupport + full API resources

- engine-wide: remove ~60 manual [qqbot:${accountId}] log prefixes
  - Prefixes now auto-injected by per-account logger
  - Remove prefix/logPrefix parameter chains (outbound/outbound-deliver/typing-keepalive etc)

* feat(qqbot): completes fallback path for approval with multi-account isolation

When the execApprovals are not configured, multiple QQBot accounts' handlers will attempt to deliver the same approval message. The openid is account-level, and cross-account delivery will trigger a QQ Bot API 500 error.

- Add account ownership verification in the fallback shouldHandle: Only match the account's handler when the request includes turnSourceAccountId; if unbound, delivery is only permitted when the number of enabled+secret accounts is ≤1.

- Consolidate account ownership determination into the unified export `matchesQQBotApprovalAccount` in `exec-approvals.ts`, with both capability and native runtime paths sharing the same logic to eliminate redundancy.

* feat(qqbot): optimize permission validation strategy

* feat(qqbot): show plugin version in /bot-version and /bot-help

Align /bot-version output with the standalone openclaw-qqbot build so users see both the QQBot plugin version and the OpenClaw framework version. Append the plugin version as a footer in /bot-help as well, matching the standalone UX.

Also fix the plugin version lookup that previously rendered as 'vunknown': the old code used a hardcoded '../../package.json' relative path which resolved to 'src/package.json' (non-existent) when executed from raw sources, so the require threw and the default 'unknown' value was retained. The same broken value also leaked into the QQ Bot API User-Agent header.

Replace the hardcoded path with a dedicated helper (bridge/plugin-version.ts) that walks up the directory tree from import.meta.url and validates the manifest's name field (@openclaw/qqbot) to avoid misreading the monorepo root package.json. Covered by 6 unit tests.

* feat(qqbot): trust shared ~/.openclaw/media root for payload files

Add getOpenClawMediaDir() and include it alongside getQQBotMediaDir() in the allowed roots of resolveQQBotPayloadLocalFilePath, so framework-produced attachments under sibling directories (e.g. media/outbound/ written by saveMediaBuffer) are trusted by auto-routed sends without triggering the path-outside-storage guard.

Covered by a new test case that verifies files under ~/.openclaw/media/outbound/ resolve successfully.

* fix(qqbot): ensure PlatformAdapter is registered before approval delivery

After the framework centralized approval handler bootstrap (#62135), the native approval handler is spawned by the framework layer outside the qqbot gateway startAccount context. This means channel.ts's side-effect `import "./bridge/bootstrap.js"` may not have run, leaving PlatformAdapter unregistered when deliverPending calls resolveQQBotAccount -> getPlatformAdapter().

Extract ensurePlatformAdapter() from bootstrap.ts as an idempotent, re-entrant helper and call it in both capability.ts (load callback) and handler-runtime.ts (deliverPending entry) to guarantee the adapter is available regardless of initialization order.

* fix(qqbot): add lazy factory for PlatformAdapter to eliminate import-order dependency

The bundler splits qqbot code into multiple chunks where the adapter singleton and its consumers may live in different modules. When a consumer chunk evaluates before the bootstrap side-effect chunk, getPlatformAdapter() throws because the singleton is still null.

Introduce registerPlatformAdapterFactory() in adapter/index.ts so getPlatformAdapter() can auto-initialize the adapter on first access. bootstrap.ts registers the factory at module evaluation time alongside the existing eager registration path. Also add error logging in downloadFile's catch block to surface fetch failures.

* feat(qqbot): add /bot-approve slash command for exec approval config management

Add /bot-approve command to the built-in QQBot plugin, ported from the
standalone openclaw-qqbot implementation. This command allows users to
manage tools.exec.security and tools.exec.ask settings directly from QQ.

Supported sub-commands:
  /bot-approve on      - allowlist + on-miss (recommended)
  /bot-approve off     - full + off (no approval)
  /bot-approve always  - allowlist + always (strict mode)
  /bot-approve reset   - remove overrides, restore framework defaults
  /bot-approve status  - show current security/ask values

The runtime config API is injected via registerApproveRuntimeGetter()
following the existing dependency injection pattern used by
registerVersionResolver() and registerPluginVersion().

* fix(qqbot): ACK INTERACTION_CREATE events before processing approval buttons

Send PUT /interactions/{id} immediately upon receiving any
INTERACTION_CREATE event to prevent QQ from showing a timeout
error to the user. The ACK is fire-and-forget and does not block
subsequent approval button resolution.

Also resolve merge conflict in pnpm-lock.yaml (keep
@tencent-connect/qqbot-connector@1.1.0 and newer
@thi.ng/bitstream@2.4.46).

* feat(qqbot): enhance reminder functionality with delivery context and credential backup

This update improves the QQBot reminder system by introducing a delivery context for reminders, allowing for more flexible target resolution. Key changes include:

- Updated reminder logic to utilize a delivery envelope, ensuring that reminders are sent with the correct context.
- Implemented credential backup and recovery mechanisms to prevent loss of appId and clientSecret during hot upgrades.
- Added tests for credential backup functionality and admin resolver to ensure reliability.
- Enhanced the remind tool to automatically resolve the target from the current conversation context when not explicitly provided.

These enhancements aim to improve the user experience and reliability of the reminder feature within the QQBot framework.

* fix(qqbot): ensure PlatformAdapter is registered before gateway message processing

Call ensurePlatformAdapter() at the start of bridge/gateway.ts's
startGateway() to guarantee the adapter is available when engine
code (e.g. downloadFile in file-utils.ts) calls getPlatformAdapter().

When the bundler splits code into separate chunks, bootstrap.ts's
module-level side-effect registration may not have executed yet by
the time the gateway processes its first inbound attachment download.

Also fix the TS2339 error in registerApproveRuntimeGetter by using
getQQBotRuntime() (full PluginRuntime with config) instead of
getQQBotRuntimeForEngine() (GatewayPluginRuntime subset without config).

* fix(qqbot): make isAudioFile safe when OutboundAudioAdapter is not registered

sendMedia() calls isAudioFile() as part of its media-type dispatch logic
before any actual audio processing. When the audio adapter is not yet
registered (e.g. framework tool calls sendMedia before gateway startup),
isAudioFile() would throw 'OutboundAudioAdapter not registered' even
for non-audio files like images.

Wrap the getAudio() call in isAudioFile() with try/catch to return false
when the adapter is unavailable, allowing non-audio media sends to
proceed normally.

* refactor(qqbot): remove plugin startup/upgrade greeting pipeline

Drop the startup / upgrade greeting feature that was folded into the
previous reminder + credential-backup commit. The pipeline has proven
unnecessary for the fused build and its supporting admin-resolver
scaffolding has no other consumers, so both are removed wholesale.

- Delete engine/session/startup-greeting.ts and its tests: the
  first-launch "soul online" / "updated to vX.Y.Z" messages, the
  per-(accountId, appId) startup marker, the failure cooldown, and the
  legacy startup-marker.json migration path are all gone.
- Delete engine/session/admin-resolver.ts and its tests: admin openid
  persistence/resolution, upgrade-greeting-target load/clear and the
  sendStartupGreetings dispatcher only ever served the greeting flow
  and were not referenced elsewhere.
- channel.ts: drop the sendStartupGreetings import and the READY /
  RESUMED hooks that triggered greetings; credential-backup snapshots
  stay untouched.
- engine/utils/data-paths.ts: remove getAdminMarkerFile /
  getLegacyAdminMarkerFile / getUpgradeGreetingTargetFile /
  getStartupMarkerFile / getLegacyStartupMarkerFile along with the
  now-stale module docblock sections. Credential-backup helpers and
  safeName are preserved.

Net -655 LOC across 6 files. tsc --noEmit passes on
extensions/qqbot/tsconfig.json and no references to the removed
symbols remain in the workspace.

* fix(qqbot): resolve test failures in extension batch, contracts and bundled runtime deps

- bootstrap: replace sync require() with static imports for secret-input
  and temp-path so vitest resolve.alias works correctly (require bypasses
  vitest aliases causing Cannot find module errors)
- format: handle null/undefined in formatErrorMessage before JSON.stringify
  since JSON.stringify(undefined) returns JS undefined, not a string
- gateway/types: reword comment to avoid triggering the channel-import
  guardrail regex that forbids quoted openclaw/plugin-sdk references
- package.json: mirror @tencent-connect/qqbot-connector ^1.1.0 in root
  dependencies as required by bundled plugin runtime dependency checks

* chore: revert non-qqbot changes to align with upstream main

Revert modifications to src/agents/system-prompt, src/auto-reply/reply/dispatch-from-config, and src/canvas-host/a2ui build artifacts that were inadvertently included in the qqbot feature branch. Also fix .gitignore Core/ pattern to match subdirectories.

* fix(qqbot): remove unused logUnsupportedStructuredMediaTarget after API simplification

* fix(qqbot): restore channel-plugin-api.ts for bundled plugin surface convention

* fix(qqbot): update CI lint allowlists for restructured engine paths

- Update raw fetch() allowlist in check-no-raw-channel-fetch.mjs to
  reflect engine/ directory restructure (src/api.ts → src/engine/api/api-client.ts, etc.)
- Remove stale qqbot allowlist entry for deleted src/utils/audio-convert.ts

* fix(qqbot): eliminate os.tmpdir() in engine layer via adapter injection

- Make hasPlatformAdapter() also check for registered factory, so adapter
  is always discoverable once bootstrap has run
- Remove os.tmpdir() fallbacks in platform.ts getHomeDir()/getTempDir(),
  delegate entirely to PlatformAdapter.getTempDir() which calls
  resolvePreferredOpenClawTmpDir() under the hood
- Keeps engine/ layer free of openclaw/plugin-sdk imports

* chore(qqbot): update CHANGELOG for engine architecture refactor (#67960) (thanks @cxyhhhhh)

---------

Co-authored-by: Bobby <zkd8907@live.com>
Co-authored-by: neilhwang <neilhwang@tencent.com>
Co-authored-by: sliverp <870080352@qq.com>
2026-04-22 01:05:12 +08:00
Shadow
38aaa23e63 feat(channels): stream tool progress into preview edits (#69611) (thanks @thewilloftheshadow) 2026-04-21 11:51:16 -05:00
Gustavo Madeira Santana
13636c4521 perf(matrix): narrow register-time runtime surface (#69782)
Merged via squash.

Prepared head SHA: ec32828b52
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-04-21 12:50:53 -04:00
Patrick Erichsen
acb27bac3a fix(dev): release run-node build lock on SIGINT/SIGTERM/exit (#69785) 2026-04-21 09:33:19 -07:00
Devin Robison
e6e83e6ccf fix(control-ui): block remote image loads (#69773)
* fix(control-ui): block remote image loads

* fix(control-ui): reject protocol-relative avatar URLs

* docs(changelog): note control-ui image CSP tightening (#69773)
2026-04-21 10:30:32 -06:00
Devin Robison
2aa93d44a1 fix: require owner identity for owner-enforced commands (#69774)
* fix: require owner identity for owner-enforced commands

Stop wildcard channel allowlists from authorizing non-owner senders when a plugin requires owner-only commands.

Add a regression test for the owner-enforced wildcard allowFrom path.

* docs(changelog): note owner identity requirement for owner-enforced commands (#69774)
2026-04-21 10:16:33 -06:00
Patrick Erichsen
4fdd005b88 onboard: plain-prose security disclaimer, searchable pickers for search/plugins/model-provider (#69760) 2026-04-21 08:54:00 -07:00
Bruce MacDonald
1be94b7a37 onboard (ollama): populate cloud-only model list from ollama.com/api/tags (#68463)
Merged via squash.

Prepared head SHA: fb12af3d63
Co-authored-by: BruceMacD <5853428+BruceMacD@users.noreply.github.com>
Co-authored-by: BruceMacD <5853428+BruceMacD@users.noreply.github.com>
Reviewed-by: @BruceMacD
2026-04-21 08:51:54 -07:00
Peter Steinberger
06b4e3885e test: stabilize stale-pid ancestor override
(cherry picked from commit 4e25479cb2)
2026-04-21 16:45:22 +01:00
Peter Steinberger
34a52ea777 fix: lazy-load discord carbon runtime for npm install
Forward-port release branch fix without beta version file changes.

(cherry picked from commit 3243c14547)
2026-04-21 16:40:18 +01:00
Peter Steinberger
99c3ec15df test: accept codex not-approved fallback
(cherry picked from commit 542086ccea)
2026-04-21 16:40:07 +01:00
Peter Steinberger
68e97c9969 test: generalize codex rejected-permission fallback
(cherry picked from commit 1e9627f92d)
2026-04-21 16:40:07 +01:00
Peter Steinberger
f992542132 test: accept codex elevated execution fallback
(cherry picked from commit 26b359bebd)
2026-04-21 16:40:07 +01:00
Peter Steinberger
9a7a637117 test: accept codex sandbox approval fallback
(cherry picked from commit 8eac996344)
2026-04-21 16:40:07 +01:00
Peter Steinberger
de31f91417 test: accept codex active-model fallback
(cherry picked from commit 87b81fa66f)
2026-04-21 16:40:07 +01:00
Peter Steinberger
e01c76eaf9 fix: guard empty docker host args in install smoke
(cherry picked from commit ddd05f4e89)
2026-04-21 16:40:07 +01:00
Peter Steinberger
9d3c155bf8 fix: avoid empty bash arrays in linux smoke
(cherry picked from commit 2db45c7892)
2026-04-21 16:40:07 +01:00
Peter Steinberger
66a5864c2a fix: support older shells in parallels smoke
(cherry picked from commit 8ce7c4f08b)
2026-04-21 16:40:07 +01:00
Peter Steinberger
d2185bd45b fix: run packed bundled postinstall in release check
(cherry picked from commit e57e54e591)
2026-04-21 16:40:07 +01:00
Tak Hoffman
714598774f feat: add soft reset command (#68635)
* feat: add soft reset command

* fix: harden soft reset follow-up behavior

* fix: accept whitespace-delimited soft reset tails

* test: cover newline soft reset normalization

* fix: preserve stale sessions for soft reset

* fix: gate soft reset stale bypass

* fix: align soft reset auth gating

* fix: normalize soft reset session detection

* test: cover multiline soft reset session state

* test: cover multiline soft reset parsing
2026-04-21 10:17:52 -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
Vincent Koc
840bf00887 test(infra): avoid repeated module reloads 2026-04-18 07:21:08 -07:00
Vincent Koc
e85e6bc4fb perf(ci): reuse macos swift build outputs 2026-04-18 07:18:21 -07:00
Vincent Koc
40c30d0062 chore(ci): remove impossible local checkout action 2026-04-18 06:49:39 -07:00
Vincent Koc
6d55fa19db fix(ci): inline fast checkout bootstrap 2026-04-18 06:49:08 -07:00
Vincent Koc
e5747629c3 fix(test): stabilize workspace package test imports 2026-04-18 06:45:09 -07:00
Vincent Koc
552c0f22a6 refactor(ci): extract fast checkout action 2026-04-18 06:41:22 -07:00
Vincent Koc
dd618aa545 perf(ci): split protocol check from contracts lane 2026-04-18 06:36:44 -07:00
Vincent Koc
de4429ceb3 perf(ci): fan out additional checks 2026-04-18 06:28:50 -07:00
Vincent Koc
334f0a4de2 fix(ci): harden checkout on hot linux lanes 2026-04-18 06:27:27 -07:00
Frank Yang
442deb0816 fix(cli): normalize reply-media paths for agent --deliver (#68516) 2026-04-18 20:05:41 +08:00
@zimeg
25ce5a5822 fix(slack): resolve stream recipient team in shared channels 2026-04-18 04:11:06 -07:00
Mason Huang
992b2143dd fix(secret-scanning): remove maintainer @<LOGIN> attribution from replacement comment template (#68521) 2026-04-18 19:02:46 +08:00
Altay
f7ceb98b72 docs(changelog): add $schema entry (#68515) 2026-04-18 13:34:47 +03:00
Efe Baran Durmaz
a2eb8fa48f fix(config): preserve $schema field across config rewrites (#47322)
* fix(config): preserve \$schema field across config rewrites

Add \$schema to the OpenClawConfig TypeScript type so it survives
the config write-back cycle. The Zod schema already accepted it
(added in #14998) but the TypeScript type omitted it, causing the
field to be silently stripped during config serialization.

Adds a round-trip test through validateConfigObject to prevent
regression.

Closes #43578

* fix(config): preserve root $schema during partial writes

* fix(config): preserve root $schema only when omitted

* fix(config): preserve root-authored $schema only

---------

Co-authored-by: Altay <altay@uinaf.dev>
2026-04-18 13:32:50 +03:00
Mason Huang
26cc1bc681 changelog: move #67807 entry to Fixes section (#68509)
* changelog: move #67807 entry to Fixes section

* changelog: move #67807 entry to Fixes section with correct PR-number ordering
2026-04-18 18:29:27 +08:00
Ted Li
9501656a8e fix(cron): clean up deleteAfterRun direct deliveries (#67807)
Merged via squash.

Prepared head SHA: d23711c2e9
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-18 18:17:18 +08:00
junyuc25
ef3f9796c8 fix(failover): widen raw 402 detection for third-party proxy messages (#45827)
Merged via squash.

Prepared head SHA: 5f4b5d7283
Co-authored-by: junyuc25 <10862251+junyuc25@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-04-18 12:38:04 +03:00
Ayaan Zaidi
eaaab098fb test(cron): use CronDeliveryMode in policy helpers 2026-04-18 14:44:37 +05:30
Ayaan Zaidi
13a0d7a9e0 fix(cron): keep runner-owned delivery off message tool 2026-04-18 14:44:37 +05:30
Ayaan Zaidi
49ae60d6ca fix(cron): keep message tool without delivery 2026-04-18 14:44:37 +05:30
Ayaan Zaidi
31437b9e3b test(cron): cover message tool with no delivery 2026-04-18 14:44:37 +05:30
Mason Huang
3b9e0da02d docs(changelog): deduplicate #67679 entry (#68439)
PR #67679 landed a duplicate line under ### Changes in the Unreleased
block in addition to the detailed entry that was already present under
### Fixes. The short ### Changes line (auto-generated from the PR title
during merge) is a duplicate of the same PR's ### Fixes line and also
mis-categorizes a security redaction fix as a feature change.

Remove the duplicate and keep the ### Fixes entry, which is the right
section and carries the descriptive text.
2026-04-18 14:40:07 +08:00
Ziy
4b5987829d fix: redact credentials in browser.cdpUrl config paths (#67679)
Merged via squash.

Prepared head SHA: 77bc2c50ce
Co-authored-by: Ziy1-Tan <49604965+Ziy1-Tan@users.noreply.github.com>
Co-authored-by: hxy91819 <8814856+hxy91819@users.noreply.github.com>
Reviewed-by: @hxy91819
2026-04-18 14:22:58 +08:00
Viz
c778562379 ci(security): harden workflow steps against template-injection (#68431)
zizmor v1.24.1 reports 8 template-injection findings across three workflow files where GitHub Actions ${{ ... }} expressions are interpolated directly into shell run: blocks. Applies the canonical fix pattern: hoist every dynamic value into a step-level env: block and reference it as a shell variable ("${VAR}") from the script.

Files changed:

- control-ui-locale-refresh.yml: move matrix.locale into env as LOCALE (1 site)

- docker-release.yml: hoist steps.tags.outputs.{value,slim} plus the four needs.build-{amd64,arm64}.outputs.{digest,slim-digest} values into env for both manifest-creation steps (6 sites)

- openclaw-npm-release.yml: hoist steps.publish_tarball.outputs.path into env as PUBLISH_TARBALL_PATH in the Publish step (1 site)

Verified locally with zizmor --persona regular on the three files: 'No findings to report. Good job!'. pnpm format:check and pnpm lint pass.

Refs #68428. Complements #66884, which covers the remaining 12 sites in openclaw-cross-os-release-checks-reusable.yml.
2026-04-18 02:04:55 -04:00
Val Alexander
f45bc09206 [codex] fix(auth): harden OAuth refresh and Codex CLI bootstrap flows (#68396)
* Harden OAuth refresh and Codex CLI bootstrap flows

- Treat near-expiry OAuth credentials as unusable for bootstrap and refresh
- Add clearer timeout and callback validation handling for OpenAI Codex OAuth
- Tighten file lock retry behavior for stale OAuth refresh contention

* fix(auth): address PR review threads

* fix(auth): adopt fresher imported refresh tokens

* test(auth): align oauth expiry fixtures with refresh margin

* fix(auth): tighten Codex OAuth bootstrap and local fallback

* Keep explicit local auth over CLI bootstrap

- Preserve existing non-OAuth local profiles during external CLI OAuth sync
- Add regression coverage for OpenAI Codex and generic external OAuth overlays

* fix(auth): distinguish oauth lock timeout sources

* fix(auth): reject cross-account external oauth bootstrap

* fix(auth): narrow refresh contention classification
2026-04-18 01:02:29 -05:00
Gustavo Madeira Santana
18c4fd5678 Session: skip binding lookup for system events 2026-04-18 01:50:56 -04:00
Kagura
c2fb4007c2 Matrix: forward dangerouslyAllowPrivateNetwork config to client SSRF policy (#68332)
Merged via squash.

Prepared head SHA: d8733928eb
Co-authored-by: kagura-agent <268167063+kagura-agent@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-04-18 00:50:50 -04:00
Ayaan Zaidi
dc3b10285d fix(telegram): require authorized abort supersede 2026-04-18 10:14:08 +05:30
Marcus Castro
458a52610a fix(whatsapp): isolate multi-account inbound state and align shared defaults (#65700)
* refactor(whatsapp): centralize inbound policy resolution

* fix(whatsapp): scope named-account group session keys

* fix(whatsapp): preserve legacy group activation during scoped-key migration

* fix(whatsapp): wire shared defaults through accounts.default

* fix(whatsapp): align schema, helpers, and monitor behavior

* fix(whatsapp): restore verbose inbound diagnostics

* chore(config): refresh whatsapp changelog and baseline hashes
2026-04-18 01:37:38 -03:00
Rubén Cuevas
996eb9a024 fix: fence Telegram stale reply delivery after abort (#68100) (thanks @rubencu)
* fix(telegram): fence stale reply delivery after abort

* refactor(telegram): narrow abort fence scope

* fix(telegram): ignore stale reply finalization after abort

* fix(telegram): close abort supersession races

* fix(telegram): release abort fences on setup errors

* fix(telegram): discard superseded draft cleanup

* refactor(telegram): distill abort fence cleanup

* fix: fence Telegram stale reply delivery after abort (#68100) (thanks @rubencu)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-04-18 10:02:38 +05:30
Kagura
2c3542e315 fix: allow unknown properties in WakeParams schema (#68355) (thanks @kagura-agent)
* fix: allow unknown properties in WakeParams schema (#68347)

WakeParamsSchema used additionalProperties: false, rejecting unknown
properties like 'paperclip' from external tools. Changed to
additionalProperties: true for forward compatibility.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style: trim wake params schema comments

* fix: allow unknown properties in WakeParams schema (#68355) (thanks @kagura-agent)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-04-18 09:10:05 +05:30
Rubén Cuevas
a0dd5f7e8e Align documented bootstrap context defaults with runtime values (#67968)
* Fix bootstrap default limit docs to match runtime

* docs(context): fix stale bootstrap max/file example
2026-04-18 09:00:21 +05:30
Gustavo Madeira Santana
e910fe446a fix(install): omit checkout alias from dist inventory 2026-04-17 23:16:19 -04:00
Gustavo Madeira Santana
110f8bd2e1 fix(plugins): resolve checkout plugin sdk imports 2026-04-17 23:04:11 -04:00
Viz
dee99f27d1 fix(gateway): allow microphone access for same-origin in Permissions-Policy header (#68368)
* test(gateway): add full unit coverage for http-common.ts

Adds tests exercising every export in src/gateway/http-common.ts so the module reaches 100% line, branch, function and statement coverage (33 tests). Captures current default security headers (including the existing Permissions-Policy microphone=() deny-list) and exhaustively covers sendJson/sendText/sendMethodNotAllowed/sendUnauthorized/sendRateLimited (with and without Retry-After), sendGatewayAuthFailure (both branches), sendInvalidRequest, readJsonBodyOrError (413/408/400/success), writeDone, setSseHeaders (with and without flushHeaders) and watchClientDisconnect (empty/single/dedup/distinct sockets, abort logic and listener cleanup).

* fix(gateway): allow microphone access for same-origin in Permissions-Policy header

The gateway's default security headers set Permissions-Policy to microphone=(), which denies microphone access for every origin including the page itself. As a result, the control-ui chat mic button (ui/src/ui/chat/speech.ts) cannot start SpeechRecognition: the browser refuses with 'Permissions policy violation: microphone is not allowed in this document' and the button silently resets.

Relax microphone to the same-origin allowlist (self) so the dashboard page can use the Web Speech API while still blocking third-party frames. Camera and geolocation remain fully denied.

Fixes #51085

* test(gateway): add seeded property/fuzz tests for http-common.ts

Adds src/gateway/http-common.fuzz.test.ts with 13 property-style tests (200 iterations each) driven by an in-file deterministic mulberry32 PRNG. Covers every export with invariants rather than fixed examples: baseline security headers across all opts shapes, Strict-Transport-Security iff non-empty string, sendJson/sendText status + body round-trips across random codes and payloads, sendMethodNotAllowed with random Allow values, sendRateLimited Retry-After iff retryAfterMs>0 with ceil-seconds value (including fractional ms), sendGatewayAuthFailure delegation, sendInvalidRequest message echo, readJsonBodyOrError status/body mapping across random error texts, writeDone sentinel, setSseHeaders with/without flushHeaders, and watchClientDisconnect invariants across arbitrary socket/controller/callback combinations (empty, same, distinct, pre-aborted). Deterministic seeds keep failures reproducible without introducing a new dev dependency.
2026-04-17 23:03:49 -04:00
Gustavo Madeira Santana
a50ec27d3b Tests: speed up QA lab startup 2026-04-17 22:19:17 -04:00
Gustavo Madeira Santana
a09bf67fa5 Plugin SDK: preserve secret input runtime build 2026-04-17 22:15:00 -04:00
Onur
361750775d CI: stabilize live release lanes (#67838)
* CI: stabilize live release lanes

* CI: widen codex live exclusions

* Gateway: stop live config/auth lazy re-imports

* CI: mount writable live Docker homes

* Live: tighten retry and provider filter overrides

* CI: use API-key auth for codex live lanes

* CI: fix remaining live lanes

* CI: stop forwarding live OpenAI base URLs

* Gateway: fix live startup loader regression

* CI: stop expanding OpenAI keys in live Docker lanes

* CI: stop expanding installer secrets in Docker

* CI: tighten live secret boundaries

* Gateway: pin Codex harness base URL

* CI: fix reusable workflow runner label

* CI: avoid template expansion in live ref guard

* CI: tighten live trust gate

* Gateway: ignore empty Codex harness base URL

* CI: stabilize remaining live lanes

* CI: harden live retries and canvas auth test

* CI: extend cron live probe budget

* CI: keep codex harness lane on api-key auth

* CI: stage live Docker OpenAI auth via env files

* CI: bootstrap codex login for Docker API-key lanes

* CI: accept hosted-runner codex fallback responses

* CI: accept additional codex sandbox fallback text

* CI: accept hosted-runner live fallback variants

* CI: accept codex current-model fallback

* CI: broaden codex sandbox model fallbacks

* CI: cover extra codex sandbox wording

* CI: extend cli backend cron retry budget

* CI: match codex models fallbacks by predicate

* CI: accept configured-models live fallback

* CI: relax OpenAI websocket warmup timeout

* CI: accept extra codex model fallback wording

* CI: generalize codex model fallback matching

* CI: retry cron verify cancellation wording

* CI: accept interactive codex model entrypoint fallback

* Agents: stabilize Claude bundle skill command test

* CI: prestage live Docker auth homes

* Tests: accept current Codex models wording

* CI: stabilize remaining live lanes

* Tests: widen CLI backend live timeout

* Tests: accept current Codex model summary wording

* CI: disable codex-cli image probe in Docker lane

* Tests: respect CLI override for Codex Docker login

* Tests: accept current Codex session models header

* CI: stabilize remaining live validation lanes

* CI: preserve Gemini ACP coverage in auth fallback

* CI: fix final live validation blockers

* CI: restore Codex auth for CLI backend lane

* CI: drop local Codex config in live Docker lane

* Tests: tolerate Codex cron and model reply drift

* Tests: accept current Codex live replies

* Tests: retry more Codex cron retry wording

* Tests: accept environment-cancelled Codex cron retries

* Tests: retry blank Codex cron probe replies

* Tests: broaden Codex cron retry wording

* Tests: require explicit Codex cron retry replies

* Tests: accept current Codex models environment wording

* CI: restore trusted Codex config in live lane

* CI: bypass nested Codex sandbox in docker

* CI: instrument live codex cron lane

* CI: forward live CLI resume args

* Tests: accept interactive Codex model selection

* Tests: bound websocket warm-up live lane

* CI: close live lane review gaps

* Tests: lazy-load gateway live server

* Tests: avoid gateway live loader regression

* CI: scope reusable workflow secrets

* Tests: tighten codex models live assertion

* Tests: normalize OpenAI speech live text
2026-04-18 03:18:12 +02:00
Peter Steinberger
a22b789547 test: stabilize telegram status lane test 2026-04-18 02:13:11 +01:00
Peter Steinberger
36068281fb test: stabilize whatsapp pdf media test 2026-04-18 02:01:07 +01:00
Gustavo Madeira Santana
0e4ddf7b38 Tests: avoid bundled Discord runtime lookup 2026-04-17 20:57:27 -04:00
Peter Steinberger
c8d722d093 test: fix rebased local gates 2026-04-18 01:49:54 +01:00
Peter Steinberger
27f34f0491 test: merge provider contract wrappers 2026-04-18 01:36:33 +01:00
Peter Steinberger
6b99917d4e test: merge session binding contract flow 2026-04-18 01:36:33 +01:00
Peter Steinberger
3abb5fd291 test: slim channel contract hotspots 2026-04-18 01:36:33 +01:00
Peter Steinberger
569247cff8 test: speed channel contract hotspots 2026-04-18 01:36:15 +01:00
Peter Steinberger
576ce7c656 perf: slim zalo group access facade 2026-04-18 01:36:15 +01:00
Peter Steinberger
4143da0ffa test: use provider contract artifacts 2026-04-18 01:36:15 +01:00
Peter Steinberger
ac39cef969 test: use web fetch contract artifacts 2026-04-18 01:36:15 +01:00
Peter Steinberger
30cbfa3457 test: slim plugin shape contracts 2026-04-18 01:36:15 +01:00
Peter Steinberger
3213fcddbe test: use web search contract artifacts 2026-04-18 01:36:15 +01:00
Peter Steinberger
4c12ff6d23 test: merge provider web-search contracts 2026-04-18 01:36:15 +01:00
Peter Steinberger
ed65e8017d test: slim channel directory contracts 2026-04-18 01:36:15 +01:00
Peter Steinberger
7db9a53254 test: slim contract suite imports 2026-04-18 01:36:15 +01:00
Peter Steinberger
52b8e318bd test: collapse gateway node authz hotspots 2026-04-18 01:34:11 +01:00
Peter Steinberger
ca34c7cd7b test: merge device token authz cases 2026-04-18 01:34:11 +01:00
Peter Steinberger
5cf01ac7c1 test: keep gateway suites minimal 2026-04-18 01:33:37 +01:00
Peter Steinberger
e493d1d2fd test: keep twitch entry test lazy 2026-04-18 01:32:34 +01:00
Peter Steinberger
75ffa29054 test: trim browser bootstrap integration 2026-04-18 01:32:34 +01:00
chaoliang yan
4749993bb5 [AI-assisted] fix(agents): mark failed TTS tool synthesis as an error (#67980)
Merged via squash.

Prepared head SHA: fa12d93c79
Co-authored-by: lawrence3699 <247479654+lawrence3699@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-04-17 20:30:03 -04:00
Gustavo Madeira Santana
0266cf4d10 test: disable cron scheduler for manual runs 2026-04-17 19:46:38 -04:00
Gustavo Madeira Santana
b295f4afd8 test: skip throwaway device token auth clients 2026-04-17 19:37:12 -04:00
Gustavo Madeira Santana
e2351b5fdc test: skip throwaway control ui auth clients 2026-04-17 19:25:19 -04:00
Gustavo Madeira Santana
5d8dceb37f QA Matrix: add catchup incremental scenario 2026-04-17 19:16:58 -04:00
Gustavo Madeira Santana
5af1a51f8e test: reuse default gateway auth server 2026-04-17 19:14:54 -04:00
Gustavo Madeira Santana
8e0bcd0585 test: keep Zalo outbound contracts lightweight
Use shared SDK payload helpers directly in the outbound payload contract helper
and narrow ZaloUser target parsing to its session-route module. This preserves
the contract proof without loading broad extension runtime/test barrels.
2026-04-17 19:01:25 -04:00
Gustavo Madeira Santana
3ca8ad3845 test: avoid eager message action plugin discovery
Skip bundled channel discovery for plain message-action params and only resolve
plugin-owned media params when an extension field is actually present. This
keeps normal sends on the lightweight path while preserving plugin media-field
coverage.
2026-04-17 18:35:22 -04:00
Gustavo Madeira Santana
6f4d13f3bd test: narrow setup auto-enable probes
Run setup auto-enable probes only for plugin ids made relevant by the
current config instead of loading every setup API. This keeps provider
plugin auto-enable checks from paying unrelated setup registration cost.
2026-04-17 18:23:20 -04:00
Gustavo Madeira Santana
c54464a887 test: keep searxng web search contract light
Lazy-load the SearXNG web-search client from provider execution and reuse
the shared contract helper for credential and selection wiring. Keep the
shared fast-path contract focused on the single bundled manifest it checks.
2026-04-17 18:15:59 -04:00
Gustavo Madeira Santana
41ee813a45 test: lazy-load minimax web search runtime
Keep the Minimax web-search provider artifact metadata-only and move
execution, cache, endpoint, and test helpers behind a lazy runtime import.
This keeps contract metadata tests from importing the full runtime path.
2026-04-17 18:08:23 -04:00
Vincent Koc
8567dcfdd4 docs(changelog): add codex oauth pi entry 2026-04-17 15:08:01 -07:00
Vincent Koc
c756d61cdc ci(tests): rebalance extension shards by estimated cost 2026-04-17 15:05:41 -07:00
Gustavo Madeira Santana
b1c032245c test: lazy-load exa web search runtime
Keep Exa provider registration metadata-light and move request,
cache, validation, and test helpers behind a runtime seam.
2026-04-17 18:01:58 -04:00
Devin Robison
503b748a8e fix(exec-approvals): escape control characters in display sanitizers (#68198)
* fix(exec-approvals): escape control characters in display sanitizers

* docs(changelog): add exec approval control-char display sanitizer entry

* fix(exec-approvals): redact before escape, cover U+2028/U+2029 in display sanitizers

* fix(exec-approvals): strip invisibles before redaction and align forwarder test

* fix(exec-approvals): cover Zs bypass and preserve multi-line context on obfuscated secrets

* fix(exec-approvals): compare redaction outputs by content, not length

* fix(exec-approvals): suppress raw command on bypass; cover non-ASCII Zs in macOS sanitizer

* fix(exec-approvals): use position-bitmap bypass detection and bound input size

* style(exec-approvals): satisfy oxlint no-new-array-single-argument and SwiftFormat

* fix(exec-approvals): iterate by code point and redact before truncating
2026-04-17 15:59:08 -06:00
Gustavo Madeira Santana
cad1d04491 test: keep brave web search metadata light
Move Brave test helper exposure out of the provider artifact and
keep schema/config metadata free of runtime shared imports.
2026-04-17 17:54:57 -04:00
Gustavo Madeira Santana
c9dfb19001 test: lazy-load duckduckgo web search runtime
Keep DuckDuckGo provider metadata on the contract path and defer
client plus runtime argument helpers until search execution.
2026-04-17 17:49:17 -04:00
Gustavo Madeira Santana
5d6041de81 test: lazy-load moonshot web search runtime
Keep Kimi web-search provider metadata light and move setup,
execution, cache, and test helpers behind a runtime seam.
2026-04-17 17:44:32 -04:00
Vincent Koc
647c56ef66 test(boundary): allow contract public-surface helpers 2026-04-17 14:43:50 -07:00
Gustavo Madeira Santana
1da928211b test: lazy-load xai web search runtime
Keep xAI web-search provider registration metadata-light and move
setup, execution, cache, and test helpers behind runtime seams.
2026-04-17 17:37:48 -04:00
Vincent Koc
141c7f8eaa fix(plugins): keep contract vitest registries on public surfaces 2026-04-17 14:32:40 -07:00
Vincent Koc
d834d270df fix(test): preserve new module exports in mocks 2026-04-17 14:28:16 -07:00
Gustavo Madeira Santana
8a0977f405 test: lazy-load Tavily web search runtime
Keep Tavily provider registration on the lightweight contract path and
defer runtime client loading until generic search execution.
2026-04-17 17:26:47 -04:00
Gustavo Madeira Santana
c86beb237e test: lazy-load Perplexity web search runtime
Keep the Perplexity web-search public provider artifact metadata-only and move
execution, cache, HTTP, and runtime helper tests behind a lazy runtime seam.
This keeps bundled web-search contract checks from loading runtime-only code.
2026-04-17 17:26:47 -04:00
Gustavo Madeira Santana
2482e70fb8 test: narrow web search contract runtime loads
Honor targeted includes in the contracts Vitest lane and compare bundled
web-search fast-path artifacts against plugin-owned runtime artifacts instead
of loading whole plugin entries. Split Google and Firecrawl runtime-only work
behind lazy seams so provider registration stays metadata-light.

Also keep Perplexity contract metadata aligned by sharing its runtime transport
resolution with the contract artifact.
2026-04-17 17:26:46 -04:00
3537 changed files with 164744 additions and 83051 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

@@ -127,7 +127,7 @@ The `fetch-content` output for `discussion_comment` includes `comment_node_id` a
The recreated comment should follow this format:
```
> **Note from maintainer (@<LOGIN>):** The original comment by @<AUTHOR> has been removed due to secret leakage. Below is the redacted version of the original content.
> **Note:** The original comment by @<AUTHOR> has been removed due to secret leakage. Below is the redacted version of the original content.
---

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

@@ -0,0 +1,41 @@
---
name: optimizetests
description: Optimize OpenClaw test runtime end to end. Use when the user asks for /optimizetests, slow-test review, import optimization, deduping tests, moving misplaced core coverage to extensions, or reducing CI/test wall time without adding shards or dropping coverage.
---
# Optimize Tests
Goal: real OpenClaw test/runtime speedups with coverage intact. Do not add shards,
skip assertions, weaken gates, or tune runner flags as the main fix.
## Runbook
1. Read `docs/help/testing.md`, `docs/ci.md`, and the scoped `AGENTS.md` files
for any subtree you will edit.
2. Establish evidence before edits:
- Full ranking: `pnpm test:perf:groups --full-suite --allow-failures --output .artifacts/test-perf/<name>.json`
- Targeted file: `timeout 240 /usr/bin/time -l pnpm test <file> --maxWorkers=1 --reporter=verbose`
- Import suspicion: add `OPENCLAW_VITEST_IMPORT_DURATIONS=1 OPENCLAW_VITEST_PRINT_IMPORT_BREAKDOWN=1`
3. Attack highest-return hotspots first:
- broad barrels or `importActual()` in hot tests
- per-test `vi.resetModules()` plus fresh imports
- expensive gateway/server/client setup where reset/reuse proves same behavior
- core tests asserting extension-owned behavior
- duplicated fixture construction or contract assertions
4. Prefer production-quality fixes:
- narrow runtime seams over broad mocks
- pure helpers for static parsing/metadata
- injected deps over module resets
- extension-owned tests for bundled plugin/provider/channel behavior
5. After each change, rerun the same benchmark and the proving test lane. Record
before/after wall time, Vitest duration, and max RSS when available.
6. Run `pnpm check:changed`; run broader gates (`pnpm check`, `pnpm test`,
`pnpm build`) when touched surfaces require them.
7. Commit scoped changes with `scripts/committer "<conventional message>" <paths...>`.
Push when requested. If CI is red, inspect with `gh run list/view`, fix, push,
repeat until current CI is green or a blocker is proven unrelated.
## Output
End with the pushed commit(s), before/after timings, gates run, current CI state,
and any remaining tail lanes that need separate optimization.

View File

@@ -0,0 +1,6 @@
interface:
display_name: "Optimize Tests"
short_description: "Benchmark and speed up OpenClaw tests"
default_prompt: "Use $optimizetests to benchmark slow OpenClaw tests, optimize imports and duplicated setup, move misplaced core coverage to extensions, verify gates, commit scoped changes, push, and keep CI green without adding shards or dropping coverage."
policy:
allow_implicit_invocation: false

View File

@@ -0,0 +1,437 @@
---
name: tag-duplicate-prs-issues
description: Maintainer workflow for deciding whether an OpenClaw pull request or issue is a duplicate, gathering evidence with ghreplica and pr-search-cli, grouping related work in prtags, and syncing the duplicate grouping back to GitHub through prtags. Use when Codex needs to search for duplicate PRs or issues, create or reuse a duplicate group, enforce one-group-per-target discipline, save duplicate judgments in prtags, or prepare group state for comment sync.
---
# Tag Duplicate PRs and Issues
Use this skill when a maintainer needs to decide whether a pull request or issue is a duplicate of existing work.
This skill is for maintainer triage and grouping.
It is not for reviewing the implementation quality of a PR.
## Required Setup
Do not start duplicate triage until this setup is complete.
### Install the companion skills
Install these skills first because they teach the agent how to use the two main CLIs correctly:
- `ghreplica` skill from the `ghreplica` repo at `skills/ghreplica/SKILL.md`
- `prtags` skill from the `prtags` repo at `skills/prtags/SKILL.md`
This skill assumes those two skills are available and can be used during the same run.
### Install the CLIs
Install `ghreplica` and `prtags` from their latest GitHub releases.
Do not rely on an old local build unless the maintainer explicitly wants to test unreleased behavior.
`ghreplica` CLI install path:
```bash
curl -fsSL https://raw.githubusercontent.com/dutifuldev/ghreplica/main/scripts/install-ghr.sh | bash -s -- --bin-dir "$HOME/.local/bin"
```
`prtags` CLI install path:
```bash
curl -fsSL https://raw.githubusercontent.com/dutifuldev/prtags/main/scripts/install-prtags.sh | bash -s -- --bin-dir "$HOME/.local/bin"
```
Use the `pr-search-cli` project with `uvx`.
The command itself is `pr-search`.
Do not require a permanent install unless the maintainer explicitly wants one.
```bash
uvx --from pr-search-cli pr-search status
uvx --from pr-search-cli pr-search code similar 67144
```
### Authenticate prtags
`prtags` should be logged in with the maintainer's own GitHub account through OAuth device flow.
Do not use a shared maintainer token for interactive triage.
```bash
prtags auth login
prtags auth status
```
The expected outcome is that `prtags` stores the logged-in maintainer identity locally and uses that account for authenticated writes.
### Verify the tools before triage
Before using this skill, make sure all three tools are available:
```bash
ghr repo view openclaw/openclaw
prtags auth status
uvx --from pr-search-cli pr-search status
```
## Goal
For each target PR or issue:
1. gather duplicate evidence
2. decide whether it is a real duplicate
3. create or reuse one `prtags` group for that duplicate cluster
4. save the maintainer judgment in `prtags`
5. rely on normal `prtags` group writes to drive GitHub comment sync when that integration is configured
## Tool Roles
Use the tools with these boundaries:
- `ghreplica` is the raw evidence source
- use it for title/body/comment search, related PRs, overlapping files, overlapping ranges, and current PR or issue status
- `pr-search-cli` is candidate generation and ranking
- use it to suggest likely duplicate PRs or issue-cluster context
- do not treat it as final truth
- `prtags` is the maintainer curation layer
- use it to create or reuse one duplicate group
- use it to save the duplicate status, confidence, rationale, and group summary
- use it as the source of truth for the GitHub-facing group comment
## Working Rules
- Do not call something a duplicate only because the titles are similar.
- Do not call something a duplicate only because the same files changed.
- A duplicate cluster should be based on the same user-facing problem, the same intent, and substantially overlapping implementation or investigation context.
## One-Group Rule
Treat duplicate groups as exclusive.
A PR or issue should belong to at most one duplicate group at a time.
That means:
- before creating a new group, search for an existing group that already represents the same duplicate story
- if the target already appears to belong to a different duplicate group, stop and resolve that conflict first
- do not create a second group for the same target just because the wording is slightly different
- if two plausible existing groups overlap and you cannot safely merge the judgment, stop and ask the maintainer
This rule matters more than speed.
The skill should keep one coherent duplicate cluster per problem, not many near-duplicate clusters.
## What A Good Duplicate Group Represents
A duplicate group should describe the underlying problem and the intended fix direction.
Do not group items only because they share a keyword.
Good group shape:
- same user-facing bug or same maintainer-facing task
- same subsystem or code surface
- same intended change direction
- same likely duplicate-resolution path
Bad group shape:
- “all PRs that touch Slack”
- “all issues mentioning retry”
- “all auth-related items”
The group title should name the real problem.
The group description should summarize the intent and the code surface.
Examples:
- `gateway: startup regression from channel status bootstrap`
- `whatsapp: QR preflight timeout handling`
- `release: cross-OS validation handoff gaps`
## Evidence Checklist
Before declaring a duplicate, gather evidence from at least two categories.
For PRs:
- same or nearly same problem statement
- same changed files or overlapping file ranges
- same fix direction
- same subsystem and failure mode
- same linked issue or same user-visible symptom
For issues:
- same user-visible problem
- same reproduction story or same failure mode
- same likely fix area
- same PRs already linked or discussed
- same maintainers already steering toward the same duplicate grouping
If you only have wording similarity, that is not enough.
## Step 1: Read The Target
Start by reading the target itself.
For a PR:
```bash
ghr pr view -R openclaw/openclaw <number> --comments
ghr pr reviews -R openclaw/openclaw <number>
ghr pr comments -R openclaw/openclaw <number>
```
For an issue:
```bash
ghr issue view -R openclaw/openclaw <number> --comments
ghr issue comments -R openclaw/openclaw <number>
```
Record:
- target type and number
- title
- problem statement
- proposed intent
- subsystem
- whether it is open, closed, or merged
- whether there is already a likely duplicate thread mentioned by humans
## Step 2: Search Broadly With ghreplica
Use `ghreplica` first because it is the most direct evidence source.
### PR duplicate search
Run all of these when the target is a PR:
```bash
ghr search related-prs -R openclaw/openclaw <pr-number> --mode path_overlap --state all
ghr search related-prs -R openclaw/openclaw <pr-number> --mode range_overlap --state all
ghr search mentions -R openclaw/openclaw --query "<key phrase from title or body>" --mode fts --scope pull_requests --state all
ghr search mentions -R openclaw/openclaw --query "<subsystem or error phrase>" --mode fts --scope issues --state all
```
Use `prs-by-paths` or `prs-by-ranges` when the likely duplicate surface is already known:
```bash
ghr search prs-by-paths -R openclaw/openclaw --path src/example.ts --state all
ghr search prs-by-ranges -R openclaw/openclaw --path src/example.ts --start 20 --end 80 --state all
```
### Issue duplicate search
`ghreplica` does not have a special issue-to-issue “related issues” command.
For issues, search mirrored text and linked PR context instead.
Run targeted text searches:
```bash
ghr search mentions -R openclaw/openclaw --query "<issue title phrase>" --mode fts --scope issues --state all
ghr search mentions -R openclaw/openclaw --query "<error message or symptom>" --mode fts --scope issues --state all
ghr search mentions -R openclaw/openclaw --query "<subsystem phrase>" --mode fts --scope pull_requests --state all
```
Then inspect the candidate PRs or issues those searches uncover.
## Step 3: Use pr-search-cli As A Hint Layer
Use `pr-search-cli` after `ghreplica`.
It is good at surfacing candidates quickly, but it is not the final decision-maker.
Run it through the `pr-search` command.
For a PR:
```bash
uvx --from pr-search-cli pr-search -R openclaw/openclaw code similar <pr-number>
uvx --from pr-search-cli pr-search -R openclaw/openclaw code clusters for-pr <pr-number>
uvx --from pr-search-cli pr-search -R openclaw/openclaw issues for-pr <pr-number>
uvx --from pr-search-cli pr-search -R openclaw/openclaw issues duplicate-prs
```
Interpretation:
- `code similar` suggests PRs with similar change shape
- `code clusters for-pr` shows the PRs nearby code cluster
- `issues for-pr` shows which issue clusters the PR appears to belong to
- `issues duplicate-prs` is useful for spotting already-known duplicate PR patterns
For an issue:
- use `ghreplica` first to find candidate PRs or issue wording
- if the issue has linked PRs or a likely implementation PR, run `pr-search-cli` on those PRs
- treat issue-cluster output as supporting context, not as enough by itself to call the issue a duplicate
## Step 4: Decide The Outcome
Choose one of these outcomes:
- `not_duplicate`
- `duplicate_needs_judgment`
- `duplicate_confirmed`
Use `duplicate_confirmed` only when the evidence is strong enough that the maintainer could safely close or retag the duplicate item.
Use `duplicate_needs_judgment` when:
- the problem looks the same but the implementation goal differs
- the code overlap is weak
- the issue wording is ambiguous
- there may be two valid duplicate group interpretations
- the target appears to intersect two existing duplicate groups
## Step 5: Reuse Or Create One prtags Group
Before creating a group, search `prtags` for an existing one.
Start with text search over groups:
```bash
prtags search text -R openclaw/openclaw "<problem phrase>" --types group --limit 10
prtags search similar -R openclaw/openclaw "<problem summary>" --types group --limit 10
prtags group list -R openclaw/openclaw
```
Inspect likely groups:
```bash
prtags group get <group-id>
prtags group get <group-id> --include-metadata
```
Reuse an existing group when:
- it represents the same problem
- it already contains clearly related members
- adding the target would keep the group coherent
Create a new group only when no existing group clearly fits.
Create the group with a problem-based title and an intent-based description:
```bash
prtags group create -R openclaw/openclaw \
--kind mixed \
--title "<problem-centered title>" \
--description "<same intent, subsystem, and duplicate-resolution path>" \
--status open
```
Then attach the target and any known duplicate members:
```bash
prtags group add-pr <group-id> <pr-number>
prtags group add-issue <group-id> <issue-number>
```
If a target appears to already belong to another duplicate group and you cannot safely reuse that group, stop.
Do not create a second group.
## Step 6: Ensure The Annotation Fields Exist
Use `field ensure` so the skill is idempotent.
Recommended target-level fields:
```bash
prtags field ensure -R openclaw/openclaw --name duplicate_status --scope pull_request --type enum --enum-values not_duplicate,candidate,confirmed --filterable
prtags field ensure -R openclaw/openclaw --name duplicate_status --scope issue --type enum --enum-values not_duplicate,candidate,confirmed --filterable
prtags field ensure -R openclaw/openclaw --name duplicate_confidence --scope pull_request --type enum --enum-values low,medium,high --filterable
prtags field ensure -R openclaw/openclaw --name duplicate_confidence --scope issue --type enum --enum-values low,medium,high --filterable
prtags field ensure -R openclaw/openclaw --name duplicate_rationale --scope pull_request --type text --searchable
prtags field ensure -R openclaw/openclaw --name duplicate_rationale --scope issue --type text --searchable
```
Recommended group-level fields:
```bash
prtags field ensure -R openclaw/openclaw --name duplicate_confidence --scope group --type enum --enum-values low,medium,high --filterable
prtags field ensure -R openclaw/openclaw --name duplicate_rationale --scope group --type text --searchable
prtags field ensure -R openclaw/openclaw --name cluster_summary --scope group --type text --searchable
```
## Step 7: Save The Maintainer Judgment In prtags
For a PR:
```bash
prtags annotation pr set -R openclaw/openclaw <pr-number> \
duplicate_status=confirmed \
duplicate_confidence=high \
duplicate_rationale="<same problem, same fix direction, overlapping files and comments>"
```
For an issue:
```bash
prtags annotation issue set -R openclaw/openclaw <issue-number> \
duplicate_status=confirmed \
duplicate_confidence=high \
duplicate_rationale="<same user-visible problem and same intended fix path>"
```
For the group:
```bash
prtags annotation group set <group-id> \
duplicate_confidence=high \
cluster_summary="<one-sentence problem summary>" \
duplicate_rationale="<why these items belong in one duplicate cluster>"
```
When the evidence is incomplete, set `duplicate_status=candidate` and lower the confidence.
## Step 8: Let prtags Sync The Group Comment
Do not tell the agent to create a GitHub comment directly.
`prtags` owns the outbound GitHub comment as a derived projection of group state.
In the normal case, do not manually trigger comment sync.
When comment sync is configured, group writes already enqueue the derived comment projection automatically.
Use manual sync only as a repair or retry path:
```bash
prtags group sync-comments <group-id>
```
If the maintainer needs to see which groups still need attention, use:
```bash
prtags group list-comment-sync-targets -R openclaw/openclaw
```
The skill should treat the GitHub comment as a consequence of correct `prtags` group state.
It should not treat manual comment authoring as part of the normal duplicate workflow.
It should also not treat `sync-comments` as a required step for every duplicate decision.
## Output Format
Return a short maintainer report with these sections:
```text
Decision: duplicate_confirmed | duplicate_needs_judgment | not_duplicate
Target: PR #<n> | Issue #<n>
Confidence: high | medium | low
Evidence:
- ...
- ...
- ...
prtags actions:
- reused group <group-id> | created group <group-id>
- added members: ...
- annotations written: ...
- comment sync: automatic if configured | manual repair triggered for <group-id>
```
## Stop Conditions
Stop and escalate instead of forcing a duplicate decision when:
- the target appears to belong to two different duplicate groups
- the duplicate grouping is unclear
- the wording matches but the implementation goals differ
- two PRs touch the same files for different reasons
- two issues describe similar symptoms but likely different root causes
The maintainer should get one clean duplicate judgment or an explicit “needs judgment” result.
Do not blur the line.

View File

@@ -0,0 +1,4 @@
interface:
display_name: "Tag Duplicate PRs and Issues"
short_description: "Find duplicate PRs and issues, group them in prtags, and let prtags sync the GitHub comment"
default_prompt: "Use $tag-duplicate-prs-issues to decide whether an OpenClaw PR or issue is a duplicate, gather evidence with ghreplica and pr-search-cli, group related items in prtags, and save the duplicate judgment."

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`

1620
.github/workflows/ci.yml vendored

File diff suppressed because it is too large Load Diff

View File

@@ -62,7 +62,7 @@ jobs:
needs_autobuild: false
config_file: ""
- language: swift
runs_on: macos-latest
runs_on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-12vcpu-macos-latest' || 'macos-latest' }}
needs_node: false
needs_python: false
needs_java: false
@@ -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:
@@ -140,7 +139,8 @@ jobs:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
OPENCLAW_CONTROL_UI_I18N_MODEL: gpt-5.4
OPENCLAW_CONTROL_UI_I18N_THINKING: low
run: node --import tsx scripts/control-ui-i18n.ts sync --locale "${{ matrix.locale }}" --write
LOCALE: ${{ matrix.locale }}
run: node --import tsx scripts/control-ui-i18n.ts sync --locale "${LOCALE}" --write
- name: Commit and push locale updates
env:

View File

@@ -362,28 +362,36 @@ jobs:
- name: Create and push default manifest
shell: bash
env:
TAGS: ${{ steps.tags.outputs.value }}
AMD64_DIGEST: ${{ needs.build-amd64.outputs.digest }}
ARM64_DIGEST: ${{ needs.build-arm64.outputs.digest }}
run: |
set -euo pipefail
mapfile -t tags <<< "${{ steps.tags.outputs.value }}"
mapfile -t tags <<< "${TAGS}"
args=()
for tag in "${tags[@]}"; do
[ -z "$tag" ] && continue
args+=("-t" "$tag")
done
docker buildx imagetools create "${args[@]}" \
${{ needs.build-amd64.outputs.digest }} \
${{ needs.build-arm64.outputs.digest }}
"${AMD64_DIGEST}" \
"${ARM64_DIGEST}"
- name: Create and push slim manifest
shell: bash
env:
SLIM_TAGS: ${{ steps.tags.outputs.slim }}
AMD64_SLIM_DIGEST: ${{ needs.build-amd64.outputs.slim-digest }}
ARM64_SLIM_DIGEST: ${{ needs.build-arm64.outputs.slim-digest }}
run: |
set -euo pipefail
mapfile -t tags <<< "${{ steps.tags.outputs.slim }}"
mapfile -t tags <<< "${SLIM_TAGS}"
args=()
for tag in "${tags[@]}"; do
[ -z "$tag" ] && continue
args+=("-t" "$tag")
done
docker buildx imagetools create "${args[@]}" \
${{ needs.build-amd64.outputs.slim-digest }} \
${{ needs.build-arm64.outputs.slim-digest }}
"${AMD64_SLIM_DIGEST}" \
"${ARM64_SLIM_DIGEST}"

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:
@@ -67,12 +66,13 @@ jobs:
- name: Validate release tag and package metadata
env:
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
git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main
git fetch --no-tags origin "+refs/heads/${WORKFLOW_REF_NAME}:refs/remotes/origin/${WORKFLOW_REF_NAME}"
pnpm release:openclaw:npm:check
- name: Summarize next step

View File

@@ -144,6 +144,7 @@ on:
permissions:
contents: read
pull-requests: read
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
@@ -151,7 +152,63 @@ env:
PNPM_VERSION: "10.32.1"
jobs:
validate_selected_ref:
runs-on: blacksmith-8vcpu-ubuntu-2404
outputs:
selected_sha: ${{ steps.validate.outputs.selected_sha }}
trusted_reason: ${{ steps.validate.outputs.trusted_reason }}
steps:
- name: Checkout selected ref
uses: actions/checkout@v6
with:
ref: ${{ inputs.ref }}
fetch-depth: 0
- name: Validate selected ref
id: validate
env:
GH_TOKEN: ${{ github.token }}
INPUT_REF: ${{ inputs.ref }}
shell: bash
run: |
set -euo pipefail
selected_sha="$(git rev-parse HEAD)"
trusted_reason=""
git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main
if git merge-base --is-ancestor "$selected_sha" refs/remotes/origin/main; then
trusted_reason="main-ancestor"
elif git tag --points-at "$selected_sha" | grep -Eq '^v'; then
trusted_reason="release-tag"
else
pr_head_count="$(
gh api \
-H "Accept: application/vnd.github+json" \
"repos/${GITHUB_REPOSITORY}/commits/${selected_sha}/pulls" \
--jq '[.[] | select(.state == "open" and .head.repo.full_name == "'"${GITHUB_REPOSITORY}"'" and .head.sha == "'"${selected_sha}"'")] | length'
)"
if [[ "$pr_head_count" != "0" ]]; then
trusted_reason="open-pr-head"
fi
fi
if [[ -z "$trusted_reason" ]]; then
echo "Ref '${INPUT_REF}' resolved to $selected_sha, which is not trusted for secret-bearing live/E2E checks." >&2
echo "Allowed refs must be on main, point to a release tag, or match an open PR head in ${GITHUB_REPOSITORY}." >&2
exit 1
fi
echo "selected_sha=$selected_sha" >> "$GITHUB_OUTPUT"
echo "trusted_reason=$trusted_reason" >> "$GITHUB_OUTPUT"
{
echo "Validated ref: \`${INPUT_REF}\`"
echo "Resolved SHA: \`$selected_sha\`"
echo "Trust reason: \`$trusted_reason\`"
} >> "$GITHUB_STEP_SUMMARY"
validate_release_live_cache:
needs: validate_selected_ref
if: inputs.include_live_suites
runs-on: blacksmith-32vcpu-ubuntu-2404
timeout-minutes: 60
@@ -164,7 +221,7 @@ jobs:
- name: Checkout selected ref
uses: actions/checkout@v6
with:
ref: ${{ inputs.ref }}
ref: ${{ needs.validate_selected_ref.outputs.selected_sha }}
fetch-depth: 0
- name: Setup Node environment
@@ -173,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: |
@@ -191,6 +247,7 @@ jobs:
run: pnpm test:live:cache
validate_repo_e2e:
needs: validate_selected_ref
if: inputs.include_repo_e2e
runs-on: blacksmith-32vcpu-ubuntu-2404
timeout-minutes: 90
@@ -200,7 +257,7 @@ jobs:
- name: Checkout selected ref
uses: actions/checkout@v6
with:
ref: ${{ inputs.ref }}
ref: ${{ needs.validate_selected_ref.outputs.selected_sha }}
fetch-depth: 0
- name: Setup Node environment
@@ -209,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
@@ -218,6 +274,7 @@ jobs:
run: pnpm test:e2e
validate_special_e2e:
needs: validate_selected_ref
if: inputs.include_repo_e2e || inputs.include_live_suites
runs-on: blacksmith-32vcpu-ubuntu-2404
timeout-minutes: ${{ matrix.timeout_minutes }}
@@ -245,7 +302,7 @@ jobs:
- name: Checkout selected ref
uses: actions/checkout@v6
with:
ref: ${{ inputs.ref }}
ref: ${{ needs.validate_selected_ref.outputs.selected_sha }}
fetch-depth: 0
- name: Setup Node environment
@@ -254,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: |
@@ -293,6 +349,7 @@ jobs:
run: ${{ matrix.command }}
validate_docker_e2e:
needs: validate_selected_ref
if: inputs.include_release_path_suites || inputs.include_openwebui
runs-on: blacksmith-32vcpu-ubuntu-2404
timeout-minutes: ${{ matrix.timeout_minutes }}
@@ -324,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
@@ -396,7 +459,7 @@ jobs:
- name: Checkout selected ref
uses: actions/checkout@v6
with:
ref: ${{ inputs.ref }}
ref: ${{ needs.validate_selected_ref.outputs.selected_sha }}
fetch-depth: 0
- name: Setup Node environment
@@ -405,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
@@ -450,6 +512,7 @@ jobs:
run: ${{ matrix.command }}
validate_live_provider_suites:
needs: validate_selected_ref
if: inputs.include_live_suites
runs-on: blacksmith-32vcpu-ubuntu-2404
timeout-minutes: ${{ matrix.timeout_minutes }}
@@ -538,7 +601,7 @@ jobs:
- name: Checkout selected ref
uses: actions/checkout@v6
with:
ref: ${{ inputs.ref }}
ref: ${{ needs.validate_selected_ref.outputs.selected_sha }}
fetch-depth: 0
- name: Setup Node environment
@@ -547,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
@@ -562,9 +624,39 @@ jobs:
case "${{ matrix.suite_id }}" in
live-cli-backend-docker)
echo "OPENCLAW_LIVE_CLI_BACKEND_MODEL=codex-cli/gpt-5.4" >> "$GITHUB_ENV"
# The CLI backend Docker lane should exercise the same staged
# Codex auth path Peter uses locally so MCP cron creation and
# multimodal probes stay covered in CI. Replace the staged
# config.toml with a minimal CI-safe config so the repo stays
# trusted for MCP/tool use without inheriting maintainer-local
# provider/profile overrides that do not exist inside CI.
# Codex's workspace-write sandbox relies on user namespaces that
# this Docker lane does not provide, so run Codex unsandboxed
# inside the already-isolated container to keep MCP cron/tool
# execution representative instead of failing on nested sandbox
# setup.
echo 'OPENCLAW_LIVE_CLI_BACKEND_CLEAR_ENV=["OPENAI_API_KEY","OPENAI_BASE_URL"]' >> "$GITHUB_ENV"
echo 'OPENCLAW_LIVE_CLI_BACKEND_ARGS=["exec","--json","--color","never","--sandbox","danger-full-access","--skip-git-repo-check"]' >> "$GITHUB_ENV"
echo 'OPENCLAW_LIVE_CLI_BACKEND_RESUME_ARGS=["exec","resume","{sessionId}","-c","sandbox_mode=\"danger-full-access\"","--skip-git-repo-check"]' >> "$GITHUB_ENV"
echo "OPENCLAW_LIVE_CLI_BACKEND_DEBUG=1" >> "$GITHUB_ENV"
echo "OPENCLAW_CLI_BACKEND_LOG_OUTPUT=1" >> "$GITHUB_ENV"
echo "OPENCLAW_LIVE_CLI_BACKEND_USE_CI_SAFE_CODEX_CONFIG=1" >> "$GITHUB_ENV"
;;
live-codex-harness-docker)
# Keep CI on the API-key path for now. The staged Codex auth secret
# is currently stale, but the wrapper still supports codex-auth for
# local maintainer reruns without changing Peter's flow.
echo "OPENCLAW_LIVE_CODEX_HARNESS_AUTH=api-key" >> "$GITHUB_ENV"
;;
live-acp-bind-docker)
echo "OPENCLAW_LIVE_ACP_BIND_AGENTS=claude,codex,gemini" >> "$GITHUB_ENV"
if [[ -n "${GEMINI_API_KEY:-}" || -n "${GOOGLE_API_KEY:-}" ]]; then
echo "OPENCLAW_LIVE_ACP_BIND_AGENTS=claude,codex,gemini" >> "$GITHUB_ENV"
else
# The hydrated Gemini settings file only selects Gemini CLI auth
# mode. CI still needs a usable Gemini or Google API key before
# ACP bind can initialize a Gemini session.
echo "OPENCLAW_LIVE_ACP_BIND_AGENTS=claude,codex" >> "$GITHUB_ENV"
fi
;;
esac

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
@@ -397,9 +410,10 @@ jobs:
env:
OPENCLAW_PREPACK_PREPARED: "1"
OPENCLAW_NPM_PUBLISH_TAG: ${{ inputs.npm_dist_tag }}
PUBLISH_TARBALL_PATH: ${{ steps.publish_tarball.outputs.path }}
run: |
set -euo pipefail
publish_target="${{ steps.publish_tarball.outputs.path }}"
publish_target="${PUBLISH_TARBALL_PATH}"
if [[ -n "${publish_target}" ]]; then
publish_target="./${publish_target}"
fi

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
@@ -130,12 +132,19 @@ jobs:
ref: ${{ needs.resolve_target.outputs.ref }}
provider: ${{ needs.resolve_target.outputs.provider }}
mode: ${{ needs.resolve_target.outputs.mode }}
secrets: inherit
secrets:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }}
OPENCLAW_DISCORD_SMOKE_BOT_TOKEN: ${{ secrets.OPENCLAW_DISCORD_SMOKE_BOT_TOKEN }}
OPENCLAW_DISCORD_SMOKE_GUILD_ID: ${{ secrets.OPENCLAW_DISCORD_SMOKE_GUILD_ID }}
OPENCLAW_DISCORD_SMOKE_CHANNEL_ID: ${{ secrets.OPENCLAW_DISCORD_SMOKE_CHANNEL_ID }}
live_and_e2e_release_checks:
needs: [resolve_target]
permissions:
contents: read
pull-requests: read
uses: ./.github/workflows/openclaw-live-and-e2e-checks-reusable.yml
with:
ref: ${{ needs.resolve_target.outputs.ref }}
@@ -143,4 +152,47 @@ jobs:
include_release_path_suites: true
include_openwebui: true
include_live_suites: true
secrets: inherit
secrets:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
ANTHROPIC_API_KEY_OLD: ${{ secrets.ANTHROPIC_API_KEY_OLD }}
ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }}
BYTEPLUS_API_KEY: ${{ secrets.BYTEPLUS_API_KEY }}
CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}
DASHSCOPE_API_KEY: ${{ secrets.DASHSCOPE_API_KEY }}
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
KIMI_API_KEY: ${{ secrets.KIMI_API_KEY }}
MODELSTUDIO_API_KEY: ${{ secrets.MODELSTUDIO_API_KEY }}
MOONSHOT_API_KEY: ${{ secrets.MOONSHOT_API_KEY }}
MISTRAL_API_KEY: ${{ secrets.MISTRAL_API_KEY }}
MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }}
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
OPENCODE_ZEN_API_KEY: ${{ secrets.OPENCODE_ZEN_API_KEY }}
OPENCLAW_LIVE_BROWSER_CDP_URL: ${{ secrets.OPENCLAW_LIVE_BROWSER_CDP_URL }}
OPENCLAW_LIVE_SETUP_TOKEN: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN }}
OPENCLAW_LIVE_SETUP_TOKEN_MODEL: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN_MODEL }}
OPENCLAW_LIVE_SETUP_TOKEN_PROFILE: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN_PROFILE }}
OPENCLAW_LIVE_SETUP_TOKEN_VALUE: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN_VALUE }}
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
QWEN_API_KEY: ${{ secrets.QWEN_API_KEY }}
FAL_KEY: ${{ secrets.FAL_KEY }}
RUNWAY_API_KEY: ${{ secrets.RUNWAY_API_KEY }}
DEEPGRAM_API_KEY: ${{ secrets.DEEPGRAM_API_KEY }}
TOGETHER_API_KEY: ${{ secrets.TOGETHER_API_KEY }}
VYDRA_API_KEY: ${{ secrets.VYDRA_API_KEY }}
XAI_API_KEY: ${{ secrets.XAI_API_KEY }}
ZAI_API_KEY: ${{ secrets.ZAI_API_KEY }}
Z_AI_API_KEY: ${{ secrets.Z_AI_API_KEY }}
BYTEPLUS_ACCESS_KEY_ID: ${{ secrets.BYTEPLUS_ACCESS_KEY_ID }}
BYTEPLUS_SECRET_ACCESS_KEY: ${{ secrets.BYTEPLUS_SECRET_ACCESS_KEY }}
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
OPENCLAW_CODEX_AUTH_JSON: ${{ secrets.OPENCLAW_CODEX_AUTH_JSON }}
OPENCLAW_CODEX_CONFIG_TOML: ${{ secrets.OPENCLAW_CODEX_CONFIG_TOML }}
OPENCLAW_CLAUDE_JSON: ${{ secrets.OPENCLAW_CLAUDE_JSON }}
OPENCLAW_CLAUDE_CREDENTIALS_JSON: ${{ secrets.OPENCLAW_CLAUDE_CREDENTIALS_JSON }}
OPENCLAW_CLAUDE_SETTINGS_JSON: ${{ secrets.OPENCLAW_CLAUDE_SETTINGS_JSON }}
OPENCLAW_CLAUDE_SETTINGS_LOCAL_JSON: ${{ secrets.OPENCLAW_CLAUDE_SETTINGS_LOCAL_JSON }}
OPENCLAW_GEMINI_SETTINGS_JSON: ${{ secrets.OPENCLAW_GEMINI_SETTINGS_JSON }}

View File

@@ -7,6 +7,7 @@ on:
permissions:
contents: read
pull-requests: read
concurrency:
group: openclaw-scheduled-live-checks-${{ github.ref }}
@@ -19,6 +20,7 @@ jobs:
live_and_openwebui_checks:
permissions:
contents: read
pull-requests: read
uses: ./.github/workflows/openclaw-live-and-e2e-checks-reusable.yml
with:
ref: ${{ github.sha }}
@@ -26,4 +28,47 @@ jobs:
include_release_path_suites: false
include_openwebui: true
include_live_suites: true
secrets: inherit
secrets:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
ANTHROPIC_API_KEY_OLD: ${{ secrets.ANTHROPIC_API_KEY_OLD }}
ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }}
BYTEPLUS_API_KEY: ${{ secrets.BYTEPLUS_API_KEY }}
CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}
DASHSCOPE_API_KEY: ${{ secrets.DASHSCOPE_API_KEY }}
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
KIMI_API_KEY: ${{ secrets.KIMI_API_KEY }}
MODELSTUDIO_API_KEY: ${{ secrets.MODELSTUDIO_API_KEY }}
MOONSHOT_API_KEY: ${{ secrets.MOONSHOT_API_KEY }}
MISTRAL_API_KEY: ${{ secrets.MISTRAL_API_KEY }}
MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }}
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
OPENCODE_ZEN_API_KEY: ${{ secrets.OPENCODE_ZEN_API_KEY }}
OPENCLAW_LIVE_BROWSER_CDP_URL: ${{ secrets.OPENCLAW_LIVE_BROWSER_CDP_URL }}
OPENCLAW_LIVE_SETUP_TOKEN: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN }}
OPENCLAW_LIVE_SETUP_TOKEN_MODEL: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN_MODEL }}
OPENCLAW_LIVE_SETUP_TOKEN_PROFILE: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN_PROFILE }}
OPENCLAW_LIVE_SETUP_TOKEN_VALUE: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN_VALUE }}
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
QWEN_API_KEY: ${{ secrets.QWEN_API_KEY }}
FAL_KEY: ${{ secrets.FAL_KEY }}
RUNWAY_API_KEY: ${{ secrets.RUNWAY_API_KEY }}
DEEPGRAM_API_KEY: ${{ secrets.DEEPGRAM_API_KEY }}
TOGETHER_API_KEY: ${{ secrets.TOGETHER_API_KEY }}
VYDRA_API_KEY: ${{ secrets.VYDRA_API_KEY }}
XAI_API_KEY: ${{ secrets.XAI_API_KEY }}
ZAI_API_KEY: ${{ secrets.ZAI_API_KEY }}
Z_AI_API_KEY: ${{ secrets.Z_AI_API_KEY }}
BYTEPLUS_ACCESS_KEY_ID: ${{ secrets.BYTEPLUS_ACCESS_KEY_ID }}
BYTEPLUS_SECRET_ACCESS_KEY: ${{ secrets.BYTEPLUS_SECRET_ACCESS_KEY }}
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
OPENCLAW_CODEX_AUTH_JSON: ${{ secrets.OPENCLAW_CODEX_AUTH_JSON }}
OPENCLAW_CODEX_CONFIG_TOML: ${{ secrets.OPENCLAW_CODEX_CONFIG_TOML }}
OPENCLAW_CLAUDE_JSON: ${{ secrets.OPENCLAW_CLAUDE_JSON }}
OPENCLAW_CLAUDE_CREDENTIALS_JSON: ${{ secrets.OPENCLAW_CLAUDE_CREDENTIALS_JSON }}
OPENCLAW_CLAUDE_SETTINGS_JSON: ${{ secrets.OPENCLAW_CLAUDE_SETTINGS_JSON }}
OPENCLAW_CLAUDE_SETTINGS_LOCAL_JSON: ${{ secrets.OPENCLAW_CLAUDE_SETTINGS_LOCAL_JSON }}
OPENCLAW_GEMINI_SETTINGS_JSON: ${{ secrets.OPENCLAW_GEMINI_SETTINGS_JSON }}

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

2
.gitignore vendored
View File

@@ -36,6 +36,7 @@ apps/android/benchmark/results/
# Bun build artifacts
*.bun-build
apps/macos/.build/
apps/macos-mlx-tts/.build/
apps/shared/MoltbotKit/.build/
apps/shared/OpenClawKit/.build/
apps/shared/OpenClawKit/Package.resolved
@@ -57,6 +58,7 @@ vendor/
apps/ios/Clawdbot.xcodeproj/
apps/ios/Clawdbot.xcodeproj/**
apps/macos/.build/**
apps/macos-mlx-tts/.build/**
**/*.bun-build
apps/ios/*.xcfilelist

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 (`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

@@ -6,27 +6,216 @@ Docs: https://docs.openclaw.ai
### Changes
- CLI/doctor plugins: lazy-load doctor plugin paths and prefer installed plugin `dist/*` runtime entries over source-adjacent JavaScript fallbacks, reducing the measured `doctor --non-interactive` runtime by about 74% while keeping cold doctor startup on built plugin artifacts. (#69840) Thanks @gumadeiras.
- WhatsApp/groups+direct: forward per-group and per-direct `systemPrompt` config into inbound context `GroupSystemPrompt` so configured per-chat behavioral instructions are injected on every turn. Supports `"*"` wildcard fallback and account-scoped overrides under `channels.whatsapp.accounts.<id>.{groups,direct}`; account maps fully replace root maps (no deep merge), matching the existing `requireMention` pattern. Closes #7011. (#59553) Thanks @Bluetegu.
### Fixes
- Channels/preview streaming: centralize draft-preview finalization so Slack, Discord, Mattermost, and Matrix no longer flush temporary preview messages for media/error finals, and preserve first-reply threading for normal fallback delivery.
- Discord: keep slash command follow-up chunks ephemeral when the command is configured for ephemeral replies, so long `/status` output no longer leaks fallback model or runtime details into the public channel. (#69869) thanks @gumadeiras.
- Plugins/discovery: reject package plugin source entries that escape the package directory before explicit runtime entries or inferred built JavaScript peers can be used. (#69868) thanks @gumadeiras.
- CLI/channels: resolve channel presence through a shared policy that keeps ambient env vars and stale persisted auth from surfacing disabled bundled plugins in status, doctor, security audit, and cron delivery validation unless the channel or plugin is effectively enabled or explicitly configured. (#69862) Thanks @gumadeiras.
## 2026.4.21
### Changes
- OpenAI/images: default the bundled image-generation provider and live media smoke tests to `gpt-image-2`, and advertise the newer 2K/4K OpenAI size hints in image-generation docs and tool metadata.
- Plugins/skills: add the Skill Workshop plugin, which captures reusable workflow corrections as pending or auto-applied workspace skills, runs threshold-based reviewer passes for stronger completion bias on reusable procedures, quarantines unsafe proposals, and refreshes skill availability after safe writes.
- Plugin SDK/channels: add presentation and skills runtime contracts, decouple channel presentation rendering, and document message presentation cards so plugins can own richer interactive surfaces without channel-specific glue.
- Fireworks/models: add Kimi K2.6 (`fireworks/accounts/fireworks/models/kimi-k2p6`) to the bundled catalog and live-model priority list, while keeping Kimi thinking disabled for Fireworks K2.6 requests.
- Onboard/wizard: simplify the security disclaimer copy, and switch remaining onboarding pickers with long dynamic option lists to searchable autocompletes for search providers, plugin configuration, and model provider filtering.
- Channels/preview streaming: stream tool-progress updates into live preview edits for Discord, Slack, and Telegram so in-flight replies show incremental tool state in the same preview message before finalization. (#69611) Thanks @thewilloftheshadow.
- Ollama/onboard: populate the cloud-only model list from `ollama.com/api/tags`, cap the discovered list at 500, and fall back to static suggestions when ollama.com is unavailable. (#68463) Thanks @BruceMacD.
- QQBot: extract a self-contained engine architecture with QR-code onboarding, native approval handling via `/bot-approve`, per-account resource stacks, credential backup/restore, shared media storage, and unified API/bridge/gateway modules. (#67960) Thanks @cxyhhhhh.
- Matrix/startup: narrow Matrix runtime registration and defer setup/doctor surfaces so cold plugin registration spends about 1.8s less in `setChannelRuntime`. (#69782) Thanks @gumadeiras.
- Telegram/plugin startup: load Telegram's bundled runtime setter through a narrow sidecar and native built-sidecar loading, cutting measured setup-runtime registration by about 14s while preserving runtime API compatibility. (#69786) Thanks @gumadeiras.
- Discord/plugin startup: lazy-load the Carbon UI runtime and load Discord's bundled runtime setter through a narrow sidecar, cutting measured registration time by about 98% while keeping packaged installs off Carbon until the Discord UI surface is needed. (#69791) Thanks @gumadeiras.
### Fixes
- Agents/ACP: skip the `sessions_send` A2A ping-pong flow when a parent sends to its own background oneshot ACP child, preventing parent/child echo loops while preserving normal A2A delivery for non-parent senders. (#69817) Thanks @scotthuang.
- Image generation: log failed provider/model candidates at warn level before automatic provider fallback, so OpenAI image failures are visible in the gateway log even when a later provider succeeds.
- Agents/subagents: stop terminal failed subagent runs from freezing or announcing captured reply text, so failover-exhausted runs report a clean failure instead of replaying stale assistant/tool output.
- Security/external content: strip common self-hosted LLM chat-template special-token literals, including Qwen/ChatML, Llama, Gemma, Mistral, Phi, and GPT-OSS markers, from wrapped external content and metadata, preventing tokenizer-layer role-boundary spoofing against OpenAI-compatible backends that preserve special tokens in user text.
- npm/install: mirror the `node-domexception` alias into root `package.json` `overrides`, so npm installs stop surfacing the deprecated `google-auth-library -> gaxios -> node-fetch -> fetch-blob -> node-domexception` chain pulled through Pi/Google runtime deps. Thanks @vincentkoc.
- Auth/commands: require owner identity (an owner-candidate match or internal `operator.admin`) for owner-enforced commands instead of treating wildcard channel `allowFrom` or empty owner-candidate lists as sufficient, so non-owner senders can no longer reach owner-only commands through a permissive fallback when `enforceOwnerForCommands=true` and `commands.ownerAllowFrom` is unset. (#69774) Thanks @drobison00.
- Control UI/CSP: tighten `img-src` to `'self' data:` only, and make Control UI avatar helpers drop remote `http(s)` and protocol-relative URLs so the UI falls back to the built-in logo/badge instead of issuing arbitrary remote image fetches. Same-origin avatar routes (relative paths) and `data:image/...` avatars still render. (#69773)
- CLI/channels: keep `status`, `health`, `channels list`, and `channels status` on read-only channel metadata when Telegram, Slack, Discord, or third-party channel plugins are configured, avoiding full bundled plugin runtime imports on those cold paths. Fixes #69042. (#69479) Thanks @gumadeiras.
- Synology Chat: validate outbound webhook `file_url` values against the shared SSRF policy before forwarding to the NAS, rejecting malformed URLs, non-`http(s)` schemes, and private/blocked network targets so the NAS cannot be used as a confused deputy to fetch internal addresses. (#69784) Thanks @eleqtrizit.
- LINE: validate outbound media URLs against the shared public-network guard before handing them to LINE, preserving arbitrary public HTTPS media while rejecting loopback, link-local, and private-network targets.
- Gateway/Control UI: require gateway auth on the Control UI avatar route (`GET /avatar/<agentId>` and `?meta=1` metadata) when auth is configured, matching the sibling assistant-media route, and propagate the existing gateway token through the UI avatar fetch (bearer header + authenticated blob URL) so authenticated dashboards still load local avatars. (#69775)
- Exec/allowlist: reject POSIX parameter expansion forms such as `$VAR`, `$?`, `$$`, `$1`, and `$@` inside unquoted heredocs during shell approval analysis, so these heredocs no longer pass allowlist review as plain text. (#69795) Thanks @drobison00.
- Gateway/MCP loopback: derive owner-only tool visibility from distinct authenticated owner vs non-owner loopback bearers instead of the caller-controlled owner header, so non-owner MCP child processes cannot recover owner access by spoofing request metadata. (#69796)
- GitHub Copilot: update the default Opus model from `claude-opus-4.6` to `claude-opus-4.7` after GitHub removed Copilot support for 4.6. (#69818) Thanks @shakkernerd.
- OpenShell: pin host-side sandbox writes under the mounted root so symlink-parent rebinds cannot redirect `writeFile` outside the workspace during local mirror updates. (#69797) Thanks @drobison00.
- Ollama/media understanding: register Ollama as an image-capable media-understanding provider so `agents.defaults.imageModel.primary` values like `ollama/qwen2.5vl:7b` route through the Ollama plugin instead of failing as unknown models. (#69816) Thanks @soloclz.
- CLI/media understanding: make `openclaw infer image describe --model <provider/model>` execute the explicit image model instead of skipping description when that model supports native vision.
- Usage/providers: keep plugin-owned usage auth enabled when manifest-declared provider auth env vars such as `MINIMAX_CODE_PLAN_KEY` are present, so `/usage` can resolve MiniMax billing credentials through the provider plugin.
- Tlon/uploads: route both hosted Memex upload targets and custom-S3 presigned upload URLs through the shared SSRF guard so blocked private or loopback destinations fail before upload, while public upload URLs continue through the existing hosted upload flow. (#69794) Thanks @drobison00.
- Channels/thread routing: keep outbound replies in existing Slack, Mattermost, Matrix, Telegram, Discord, and QA-channel thread sessions by sharing the Plugin SDK thread-aware route builder across bundled plugins.
## 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.
- 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.
- 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.
## 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 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.
- 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.
- 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.
@@ -41,6 +230,29 @@ 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.
- Matrix: honor `channels.matrix.network.dangerouslyAllowPrivateNetwork` when creating clients for private-network homeservers. (#68332) Thanks @kagura-agent.
- Cron/message tool: keep cron-owned runs with `delivery.mode: "none"` on the normal message-tool path so they can still send explicit messages, create threads, and route conditionally when no runner-owned delivery target is active. (#68482) Thanks @obviyus.
- 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
@@ -148,6 +360,10 @@ Docs: https://docs.openclaw.ai
- Dreaming/memory-core: use the ingestion day, not the source file day, for daily recall dedupe so repeat sweeps of the same daily note can increment `dailyCount` across days instead of stalling at `1`. (#67091) Thanks @Bartok9.
- 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

@@ -2,6 +2,118 @@
<rss xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle" version="2.0">
<channel>
<title>OpenClaw</title>
<item>
<title>2026.4.20</title>
<pubDate>Tue, 21 Apr 2026 19:53:52 +0000</pubDate>
<link>https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml</link>
<sparkle:version>2026042090</sparkle:version>
<sparkle:shortVersionString>2026.4.20</sparkle:shortVersionString>
<sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion>
<description><![CDATA[<h2>OpenClaw 2026.4.20</h2>
<h3>Changes</h3>
<ul>
<li>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.</li>
<li>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.</li>
<li>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.</li>
<li>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.</li>
<li>Plugins/tests: reuse plugin loader alias and Jiti config resolution across repeated same-context loads, reducing import-heavy test overhead. (#69316) Thanks @amknight.</li>
<li>Cron: split runtime execution state into <code>jobs-state.json</code> so <code>jobs.json</code> stays stable for git-tracked job definitions. (#63105) Thanks @Feelw00.</li>
<li>Agents/compaction: send opt-in start and completion notices during context compaction. (#67830) Thanks @feniix.</li>
<li>Moonshot/Kimi: default bundled Moonshot setup, web search, and media-understanding surfaces to <code>kimi-k2.6</code> while keeping <code>kimi-k2.5</code> available for compatibility. (#69477) Thanks @scoootscooob.</li>
<li>Moonshot/Kimi: allow <code>thinking.keep = "all"</code> on <code>moonshot/kimi-k2.6</code>, and strip it for other Moonshot models or requests where pinned <code>tool_choice</code> disables thinking. (#68816) Thanks @aniaan.</li>
<li>BlueBubbles/groups: forward per-group <code>systemPrompt</code> config into inbound context <code>GroupSystemPrompt</code> so configured group-specific behavioral instructions (for example threaded-reply and tapback conventions) are injected on every turn. Supports <code>"*"</code> wildcard fallback matching the existing <code>requireMention</code> pattern. Closes #60665. (#69198) Thanks @omarshahine.</li>
<li>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.</li>
<li>Terminal/logging: optimize <code>sanitizeForLog()</code> by replacing the iterative control-character stripping loop with a single regex pass while preserving the existing ANSI-first sanitization behavior. (#67205) Thanks @bulutmuf.</li>
<li>QA/CI: make <code>openclaw qa suite</code> and <code>openclaw qa telegram</code> fail by default when scenarios fail, add <code>--allow-failures</code> for artifact-only runs, and tighten live-lane defaults for CI automation. (#69122) Thanks @joshavant.</li>
<li>Mattermost: stream thinking, tool activity, and partial reply text into a single draft preview post that finalizes in place when safe. (#47838) thanks @ninjaa.</li>
</ul>
<h3>Fixes</h3>
<ul>
<li>Exec/YOLO: stop rejecting gateway-host exec in <code>security=full</code> plus <code>ask=off</code> mode via the Python/Node script preflight hardening path, so promptless YOLO exec once again runs direct interpreter stdin and heredoc forms such as <code>node <<'NODE' ... NODE</code>.</li>
<li>OpenAI Codex: normalize legacy <code>openai-completions</code> 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.</li>
<li>Anthropic/plugins: scope Anthropic <code>api: "anthropic-messages"</code> defaulting to Anthropic-owned providers, so <code>openai-codex</code> and other providers without an explicit <code>api</code> no longer get rewritten to the wrong transport. Fixes #64534.</li>
<li>fix(qqbot): add SSRF guard to direct-upload URL paths in uploadC2CMedia and uploadGroupMedia [AI-assisted]. (#69595) Thanks @pgondhi987.</li>
<li>fix(gateway): enforce allowRequestSessionKey gate on template-rendered mapping sessionKeys. (#69381) Thanks @pgondhi987.</li>
<li>Browser/Chrome MCP: surface <code>DevToolsActivePort</code> attach failures as browser-connectivity errors instead of a generic "waiting for tabs" timeout, and point signed-out fallbacks toward the managed <code>openclaw</code> profile.</li>
<li>Webchat/images: treat inline image attachments as media for empty-turn gating while still ignoring metadata-only blank turns. (#69474) Thanks @Jaswir.</li>
<li>Discord/think: only show <code>adaptive</code> in <code>/think</code> autocomplete for provider/model pairs that actually support provider-managed adaptive thinking, so GPT/OpenAI models no longer advertise an Anthropic-only option.</li>
<li>Thinking: only expose <code>max</code> for models that explicitly support provider max reasoning, and remap stored <code>max</code> settings to the largest supported thinking mode when users switch to another model.</li>
<li>Gateway/usage: bound the cost usage cache with FIFO eviction so date/range lookups cannot grow unbounded. (#68842) Thanks @Feelw00.</li>
<li>OpenAI/Responses: resolve <code>/think</code> levels against each GPT model's supported reasoning efforts so <code>/think off</code> no longer becomes high reasoning or sends unsupported <code>reasoning.effort: "none"</code> payloads.</li>
<li>Lobster/TaskFlow: allow managed approval resumes to use <code>approvalId</code> without a resume token, and persist that id in approval wait state. (#69559) Thanks @kirkluokun.</li>
<li>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.</li>
<li>Plugins/startup: ignore pnpm's <code>npm_execpath</code> 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.</li>
<li>MCP: block interpreter-startup env keys such as <code>NODE_OPTIONS</code> for stdio servers while preserving ordinary credential and proxy env vars. (#69540) Thanks @drobison00.</li>
<li>Agents/shell: ignore non-interactive placeholder shells like <code>/usr/bin/false</code> and <code>/sbin/nologin</code>, falling back to <code>sh</code> so service-user exec runs no longer exit immediately. (#69308) Thanks @sk7n4k3d.</li>
<li>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.</li>
<li>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.</li>
<li>Sessions/reset: clear auto-sourced model, provider, and auth-profile overrides on <code>/new</code> and <code>/reset</code> while preserving explicit user selections, so channel sessions stop staying pinned to runtime fallback choices. (#69419) Thanks @sk7n4k3d.</li>
<li>Sessions/costs: snapshot <code>estimatedCostUsd</code> like token counters so repeated persist paths no longer compound the same run cost by up to dozens of times. (#69403) Thanks @MrMiaigi.</li>
<li>OpenAI Codex: route ChatGPT/Codex OAuth Responses requests through the <code>/backend-api/codex</code> endpoint so <code>openai-codex/gpt-5.4</code> no longer hits the removed <code>/backend-api/responses</code> alias. (#69336) Thanks @mzogithub.</li>
<li>OpenAI/Responses: omit disabled reasoning payloads when <code>/think off</code> is active, so GPT reasoning models no longer receive unsupported <code>reasoning.effort: "none"</code> requests. (#61982) Thanks @a-tokyo.</li>
<li>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 <code>pairing required</code>. (#69431) Thanks @SARAMALI15792.</li>
<li>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.</li>
<li>Ollama: add provider-policy defaults for <code>baseUrl</code> and <code>models</code> so implicit local discovery can run before config validation rejects a minimal Ollama provider config. (#69370) Thanks @PratikRai0101.</li>
<li>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.</li>
<li>Auto-reply: apply silent <code>NO_REPLY</code> policy per conversation type, so direct chats get a helpful rewritten reply while groups and internal deliveries can remain quiet. (#68644) Thanks @Takhoffman.</li>
<li>Telegram/status reactions: honor <code>messages.removeAckAfterReply</code> when lifecycle status reactions are enabled, clearing or restoring the reaction after success/error using the configured hold timings. (#68067) Thanks @poiskgit.</li>
<li>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.</li>
<li>Telegram/polling: raise the default polling watchdog threshold from 90s to 120s and add configurable <code>channels.telegram.pollingStallThresholdMs</code> (also per-account) so long-running Telegram work gets more room before polling is treated as stalled. (#57737) Thanks @Vitalcheffe.</li>
<li>Telegram/polling: bound the persisted-offset confirmation <code>getUpdates</code> probe with a client-side timeout so a zombie socket cannot hang polling recovery before the runner watchdog starts. (#50368) Thanks @boticlaw.</li>
<li>Agents/Pi runner: retry silent <code>stopReason=error</code> 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.</li>
<li>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.</li>
<li>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.</li>
<li>fix(security): block MINIMAX_API_HOST workspace env injection and remove env-driven URL routing [AI-assisted]. (#67300) Thanks @pgondhi987.</li>
<li>Cron/delivery: treat explicit <code>delivery.mode: "none"</code> runs as not requested even if the runner reports <code>delivered: false</code>, so no-delivery cron jobs no longer persist false delivery failures or errors. (#69285) Thanks @matsuri1987.</li>
<li>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.</li>
<li>BlueBubbles: raise the outbound <code>/api/v1/message/text</code> send timeout default from 10s to 30s, and add a configurable <code>channels.bluebubbles.sendTimeoutMs</code> (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.</li>
<li>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.</li>
<li>Context engine/plugins: stop rejecting third-party context engines whose <code>info.id</code> differs from the registered plugin slot id. The strict-match contract added in 2026.4.14 broke <code>lossless-claw</code> and other plugins whose internal engine id does not equal the slot id they are registered under, producing repeated <code>info.id must match registered id</code> lane failures on every turn. Fixes #66601. (#66678) Thanks @GodsBoy.</li>
<li>Agents/compaction: rename embedded Pi compaction lifecycle events to <code>compaction_start</code> / <code>compaction_end</code> so OpenClaw stays aligned with <code>pi-coding-agent</code> 0.66.1 event naming. (#67713) Thanks @mpz4life.</li>
<li>Security/dotenv: block all <code>OPENCLAW_*</code> keys from untrusted workspace <code>.env</code> files so workspace-local env loading fails closed for new runtime-control variables instead of silently inheriting them. (#473)</li>
<li>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.</li>
<li>Agents/gateway tool: extend the agent-facing <code>gateway</code> tool's config mutation guard so model-driven <code>config.patch</code> and <code>config.apply</code> 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 <code>agents.list[]</code>. (#69377) Thanks @eleqtrizit.</li>
<li>Gateway/websocket broadcasts: require <code>operator.read</code> (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 <code>plugin.*</code> broadcasts are scoped to operator.write/admin, and status/transport events (<code>heartbeat</code>, <code>presence</code>, <code>tick</code>, etc.) remain unrestricted. Per-client sequence numbers preserve per-connection monotonicity. (#69373) Thanks @eleqtrizit.</li>
<li>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.</li>
<li>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.</li>
<li>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.</li>
<li>Codex/app-server: release the session lane when a downstream consumer throws while draining the <code>turn/completed</code> notification, so follow-up messages after a Codex plugin reply stop queueing behind a stale lane lock. Fixes #67996. (#69072) Thanks @ayeshakhalid192007-dev.</li>
<li>Codex/app-server: default approval handling to <code>on-request</code> so Codex harness sessions do not start with overly permissive tool approvals. (#68721) Thanks @Lucenx9.</li>
<li>Cron/delivery: keep isolated cron chat delivery tools available, resolve <code>channel: "last"</code> targets from the gateway, show delivery previews in <code>cron list/show</code>, and avoid duplicate fallback sends after direct message-tool delivery. (#69587) Thanks @obviyus.</li>
<li>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.</li>
<li>Models/Kimi: default bundled Kimi thinking to off and normalize Anthropic-compatible <code>thinking</code> payloads so stale session <code>/think</code> state no longer silently re-enables reasoning on Kimi runs. (#68907) Thanks @frankekn.</li>
<li>Control UI/cron: keep the runtime-only <code>last</code> delivery sentinel from being materialized into persisted cron delivery and failure-alert channel configs when jobs are created or edited. (#68829) Thanks @tianhaocui.</li>
<li>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.</li>
<li>Cron/CLI: parse PowerShell-style <code>--tools</code> allow-lists the same way as comma-separated input, so <code>cron add</code> and <code>cron edit</code> no longer persist <code>exec read write</code> as one combined tool entry on Windows. (#68858) Thanks @chen-zhang-cs-code.</li>
<li>Browser/user-profile: let existing-session <code>profile="user"</code> tool calls auto-route to a connected browser node or use explicit <code>target="node"</code>, while still honoring explicit <code>target="host"</code> pinning. (#48677)</li>
<li>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.</li>
<li>BlueBubbles: consolidate outbound HTTP through a typed <code>BlueBubblesClient</code> 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.</li>
<li>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.</li>
<li>Cron/main-session delivery: preserve <code>heartbeat.target="last"</code> 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.</li>
<li>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.</li>
<li>Matrix/allowlists: hot-reload <code>dm.allowFrom</code> and <code>groupAllowFrom</code> 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.</li>
<li>BlueBubbles: always set <code>method</code> explicitly on outbound text sends (<code>"private-api"</code> when available, <code>"apple-script"</code> 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 <code>method</code> let BB Server fall back to version-dependent default behavior that silently drops the message (#64480), and the AppleScript <code>-1700</code> error on macOS 26 Tahoe plain text sends (#53159). (#69070) Thanks @xqing3.</li>
<li>Matrix/commands: recognize slash commands that are prefixed with the bot's Matrix mention, so room messages like <code>@bot:server /new</code> trigger the command path without requiring custom mention regexes. (#68570) Thanks @nightq and @johnlanni.</li>
<li>Gateway/pairing: return reason-specific <code>PAIRING_REQUIRED</code> 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.</li>
<li>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.</li>
<li>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.</li>
<li>Doctor/gateway: surface pending device pairing requests, scope-upgrade approval drift, and stale device-token mismatch repair steps so <code>openclaw doctor --fix</code> no longer leaves pairing/auth setup failures unexplained. (#69210) Thanks @obviyus.</li>
<li>Cron/isolated-agent: preserve explicit <code>delivery.mode: "none"</code> message targets for isolated runs without inheriting implicit <code>last</code> routing, so agent-initiated Telegram sends keep their authored destination while bare <code>mode:none</code> jobs stay targetless. (#69153) Thanks @obviyus.</li>
<li>Cron/isolated-agent: keep <code>delivery.mode: "none"</code> 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 <code>to</code> target. (#69163) Thanks @obviyus.</li>
<li>Gateway/TUI: retry session history while the local gateway is still finishing startup, so <code>openclaw tui</code> reconnects no longer fail on transient <code>chat.history unavailable during gateway startup</code> errors. (#69164) Thanks @shakkernerd.</li>
<li>BlueBubbles/reactions: fall back to <code>love</code> when an agent reacts with an emoji outside the iMessage tapback set (<code>love</code>/<code>like</code>/<code>dislike</code>/<code>laugh</code>/<code>emphasize</code>/<code>question</code>), so wider-vocabulary model reactions like <code>👀</code> still produce a visible tapback instead of failing the whole reaction request. Configured ack reactions still validate strictly via the new <code>normalizeBlueBubblesReactionInputStrict</code> path. (#64693) Thanks @zqchris.</li>
<li>BlueBubbles: prefer iMessage over SMS when both chats exist for the same handle, honor explicit <code>sms:</code> targets, and never silently downgrade iMessage-available recipients. (#61781) Thanks @rmartin.</li>
<li>Telegram/setup: require numeric <code>allowFrom</code> user IDs during setup instead of offering unsupported <code>@username</code> DM resolution, and point operators to <code>from.id</code>/<code>getUpdates</code> for discovery. (#69191) Thanks @obviyus.</li>
<li>GitHub Copilot/onboarding: default GitHub Copilot setup to <code>claude-opus-4.6</code> and keep the bundled default model list aligned, so new Copilot setups no longer start on the older <code>gpt-4o</code> default. (#69207) Thanks @obviyus.</li>
<li>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 <code>ssh user@host</code>. (#69215) Thanks @obviyus.</li>
<li>Slack: fix outbound replies failing with "unresolved SecretRef" for accounts configured via <code>file</code> or <code>exec</code> 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.</li>
<li>Control UI/device pairing: explain scope and role approval upgrades during reconnects, and show requested versus approved access in the Control UI and <code>openclaw devices</code> so broader reconnects no longer look like lost pairings. (#69221) Thanks @obviyus.</li>
<li>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.</li>
</ul>
<p><a href="https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md">View full changelog</a></p>
]]></description>
<enclosure url="https://github.com/openclaw/openclaw/releases/download/v2026.4.20/OpenClaw-2026.4.20.zip" length="47535600" type="application/octet-stream" sparkle:edSignature="D7XcNGxmc10IIayYY91RZBoascFSnXyd4dg6cSpC3+PTIwVrWYs/FwSBc/1J+1P53LlnTHKDGQYMkWVNMnRSAQ=="/>
</item>
<item>
<title>2026.4.15</title>
<pubDate>Thu, 16 Apr 2026 23:33:29 +0000</pubDate>
@@ -204,192 +316,5 @@
]]></description>
<enclosure url="https://github.com/openclaw/openclaw/releases/download/v2026.4.14/OpenClaw-2026.4.14.zip" length="47490719" type="application/octet-stream" sparkle:edSignature="KW4gq3qjhKPSQebRVL/mSgttTOhLVKtnWz7pNCZt29oEZ96yU14OnxxSsmtNHmDi4m7G7gfVOfndp80XKFQlCw=="/>
</item>
<item>
<title>2026.4.11</title>
<pubDate>Sun, 12 Apr 2026 00:37:09 +0000</pubDate>
<link>https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml</link>
<sparkle:version>2026041190</sparkle:version>
<sparkle:shortVersionString>2026.4.11</sparkle:shortVersionString>
<sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion>
<description><![CDATA[<h2>OpenClaw 2026.4.11</h2>
<h3>Changes</h3>
<ul>
<li>Dreaming/memory-wiki: add ChatGPT import ingestion plus new <code>Imported Insights</code> and <code>Memory Palace</code> diary subtabs so Dreaming can inspect imported source chats, compiled wiki pages, and full source pages directly from the UI. (#64505)</li>
<li>Control UI/webchat: render assistant media/reply/voice directives as structured chat bubbles, add the <code>[embed ...]</code> rich output tag, and gate external embed URLs behind config. (#64104)</li>
<li>Tools/video_generate: add URL-only generated asset delivery, typed <code>providerOptions</code>, reference audio inputs, per-asset role hints, <code>adaptive</code> aspect-ratio support, and a higher image-input cap so video providers can expose richer generation modes without forcing large files into memory. (#61987, #61988) Thanks @xieyongliang.</li>
<li>Feishu: improve document comment sessions with richer context parsing, comment reactions, and typing feedback so document-thread conversations behave more like chat conversations. (#63785)</li>
<li>Microsoft Teams: add reaction support, reaction listing, Graph pagination, and delegated OAuth setup for sending reactions while preserving application-auth read paths. (#51646)</li>
<li>Plugins: allow plugin manifests to declare activation and setup descriptors so plugin setup flows can describe required auth, pairing, and configuration steps without hardcoded core special cases. (#64780)</li>
<li>Ollama: cache <code>/api/show</code> context-window and capability metadata during model discovery so repeated picker refreshes stop refetching unchanged models, while still retrying after empty responses and invalidating on digest changes. (#64753) Thanks @ImLukeF.</li>
<li>Models/providers: surface how configured OpenAI-compatible endpoints are classified in embedded-agent debug logs, so local and proxy routing issues are easier to diagnose. (#64754) Thanks @ImLukeF.</li>
<li>QA/parity: add the GPT-5.4 vs Opus 4.6 agentic parity report gate with shared scenario coverage checks, stricter evidence heuristics, and skipped-scenario accounting for maintainer review. (#64441) Thanks @100yenadmin.</li>
</ul>
<h3>Fixes</h3>
<ul>
<li>OpenAI/Codex OAuth: stop rewriting the upstream authorize URL scopes so new Codex sign-ins do not fail with <code>invalid_scope</code> before returning an authorization code. (#64713) Thanks @fuller-stack-dev.</li>
<li>Audio transcription: disable pinned DNS only for OpenAI-compatible multipart requests, while still validating hostnames, so OpenAI, Groq, and Mistral transcription works again without weakening other request paths. (#64766) Thanks @GodsBoy.</li>
<li>macOS/Talk Mode: after granting microphone permission on first enable, continue starting Talk Mode instead of requiring a second toggle. (#62459) Thanks @ggarber.</li>
<li>Control UI/webchat: persist agent-run TTS audio replies into webchat history and preserve interleaved tool card pairing so generated audio and mixed tool output stay attached to the right messages. (#63514) Thanks @bittoby.</li>
<li>WhatsApp: honor the configured default account when the active listener helper is used without an explicit account id, so named default accounts do not get registered under <code>default</code>. (#53918) Thanks @yhyatt.</li>
<li>ACP/agents: suppress commentary-phase child assistant relay text in ACP parent stream updates, so spawned child runs stop leaking internal progress chatter into the parent session. Thanks @vincentkoc.</li>
<li>Agents/timeouts: honor explicit run timeouts in the LLM idle watchdog and align default timeout config so slow models can keep working until the configured limit instead of using the wrong idle window.</li>
<li>Config: include <code>asyncCompletion</code> in the generated zod schema so documented async completion config no longer fails with an unrecognized-key error. (#63618)</li>
<li>Google/Veo: stop sending the unsupported <code>numberOfVideos</code> request field so Gemini Developer API Veo runs do not fail before OpenClaw can complete the intended Google video generation path. (#64723) Thanks @velvet-shark.</li>
<li>QA/packaging: stop packaged CLI startup and completion cache generation from reading repo-only QA scenario markdown, ship the bundled QA scenario pack in npm releases, and keep <code>openclaw completion --write-state</code> working even if QA setup is broken. (#64648) Thanks @obviyus.</li>
<li>Codex/QA: keep Codex app-server coordination chatter out of visible replies, add a live QA leak scenario, and classify leaked harness meta text as a QA failure instead of a successful reply. Thanks @vincentkoc.</li>
<li>WhatsApp: route <code>message react</code> through the gateway-owned action path so reactions use the live WhatsApp listener in both DM and group chats, matching <code>message send</code> and <code>message poll</code>. Thanks @mcaxtr.</li>
<li>Auto-reply/WhatsApp: preserve inbound image attachment notes after media understanding so image edits keep the real saved media path instead of hallucinating a missing local path. (#64918) Thanks @ngutman.</li>
<li>Telegram/sessions: keep topic-scoped session initialization on the canonical topic transcript path when inbound turns omit <code>MessageThreadId</code>, so one topic session no longer alternates between bare and topic-qualified transcript files. (#64869) Thanks @jalehman.</li>
<li>Agents/failover: scope assistant-side fallback classification and surfaced provider errors to the current attempt instead of stale session history, so cross-provider fallback runs stop inheriting the previous provider's failure. (#62907) Thanks @stainlu.</li>
<li>MiniMax/OAuth: write <code>api: "anthropic-messages"</code> and <code>authHeader: true</code> into the <code>minimax-portal</code> config patch during <code>openclaw configure</code>, so re-authenticated portal setups keep Bearer auth routing working. (#64964) Thanks @ryanlee666.</li>
</ul>
<p><a href="https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md">View full changelog</a></p>
]]></description>
<enclosure url="https://github.com/openclaw/openclaw/releases/download/v2026.4.11/OpenClaw-2026.4.11.zip" length="47317969" type="application/octet-stream" sparkle:edSignature="v9bUsh1mBBPtpMn7kKYAvO8MNJHAeMj7UkmkkuDSC8NvwPx2Fo3+NEeyAyA9s9Vax6L7i+eHSpwzAmtwpnHcCA=="/>
</item>
<item>
<title>2026.4.10</title>
<pubDate>Sat, 11 Apr 2026 03:17:02 +0000</pubDate>
<link>https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml</link>
<sparkle:version>2026041090</sparkle:version>
<sparkle:shortVersionString>2026.4.10</sparkle:shortVersionString>
<sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion>
<description><![CDATA[<h2>OpenClaw 2026.4.10</h2>
<h3>Changes</h3>
<ul>
<li>Models/Codex: add the bundled Codex provider and plugin-owned app-server harness so <code>codex/gpt-*</code> models use Codex-managed auth, native threads, model discovery, and compaction while <code>openai/gpt-*</code> stays on the normal OpenAI provider path. (#64298)</li>
<li>Memory/Active Memory: add a new optional Active Memory plugin that gives OpenClaw a dedicated memory sub-agent right before the main reply, so ongoing chats can automatically pull in relevant preferences, context, and past details without making users remember to manually say "remember this" or "search memory" first. Includes configurable message/recent/full context modes, live <code>/verbose</code> inspection, advanced prompt/thinking overrides for tuning, and opt-in transcript persistence for debugging. Docs: https://docs.openclaw.ai/concepts/active-memory. (#63286) Thanks @Takhoffman.</li>
<li>macOS/Talk: add an experimental local MLX speech provider for Talk Mode, with explicit provider selection, local utterance playback, interruption handling, and system-voice fallback. (#63539) Thanks @ImLukeF.</li>
<li>Tools/video generation: add Seedance 2.0 model refs to the bundled fal provider and submit the provider-specific duration, resolution, audio, and seed metadata fields needed for live Seedance 2.0 runs.</li>
<li>Microsoft Teams: add message actions for pin, unpin, read, react, and listing reactions. (#53432) Thanks @sudie-codes.</li>
<li>QA/Matrix: add a live <code>openclaw qa matrix</code> lane backed by a disposable Matrix homeserver, shared live-transport seams, and Matrix-specific transport coverage for threading, reactions, restart, and allowlist behavior. (#64489) Thanks @gumadeiras.</li>
<li>QA/Telegram: add a live <code>openclaw qa telegram</code> lane for private-group bot-to-bot checks, harden its artifact handling, and preserve native Telegram command reply threading for QA verification. (#64303) Thanks @obviyus.</li>
<li>QA/testing: add a <code>--runner multipass</code> lane for <code>openclaw qa suite</code> so repo-backed QA scenarios can run inside a disposable Linux VM and write back the usual report, summary, and VM logs. (#63426) Thanks @shakkernerd.</li>
<li>CLI/exec policy: add a local <code>openclaw exec-policy</code> command with <code>show</code>, <code>preset</code>, and <code>set</code> subcommands for synchronizing requested <code>tools.exec.*</code> config with the local exec approvals file, plus follow-up hardening for node-host rejection, rollback safety, and sync conflict detection. (#64050)</li>
<li>Gateway: add a <code>commands.list</code> RPC so remote gateway clients can discover runtime-native, text, skill, and plugin commands with surface-aware naming and serialized argument metadata. (#62656) Thanks @samzong.</li>
<li>Models/providers: add per-provider <code>models.providers.*.request.allowPrivateNetwork</code> for trusted self-hosted OpenAI-compatible endpoints, keep the opt-in scoped to model request surfaces, and refresh cached WebSocket managers when request transport overrides change. (#63671) Thanks @qas.</li>
<li>Feishu: standardize request user agents and register the bot as an AI agent so Feishu deployments identify OpenClaw consistently. (#63835) Thanks @evandance.</li>
<li>Matrix/partial streaming: add MSC4357 live markers to draft preview sends and edits so supporting Matrix clients can render a live/typewriter animation and stop it when the final edit lands. (#63513) Thanks @TigerInYourDream.</li>
<li>Control UI/dreaming: simplify the Scene and Diary surfaces, preserve unknown phase state for partial status payloads, and stabilize waiting-entry recency ordering so Dreaming status and review lists stay clear and deterministic. (#64035) Thanks @davemorin.</li>
<li>Agents: add an opt-in strict-agentic embedded Pi execution contract for GPT-5-family runs so plan-only or filler turns keep acting until they hit a real blocker. (#64241) Thanks @100yenadmin.</li>
<li>Agents/OpenAI: add provider-owned OpenAI/Codex tool schema compatibility and surface embedded-run replay/liveness state for long-running runs. (#64300) Thanks @100yenadmin.</li>
<li>Docs i18n: chunk raw doc translation, reject truncated tagged outputs, avoid ambiguous body-only wrapper unwrapping, and recover from terminated Pi translation sessions without changing the default <code>openai/gpt-5.4</code> path. (#62969, #63808) Thanks @hxy91819.</li>
</ul>
<h3>Fixes</h3>
<ul>
<li>Browser/security: tighten browser and sandbox navigation defenses across strict SSRF defaults, hostname allowlists, interaction-driven redirects, subframes, CDP discovery, existing sessions, tab actions, noVNC, marker-span sanitization, and Docker CDP source-range enforcement. (#61404, #63332, #63882, #63885, #63889, #64367, #64370, #64371)</li>
<li>Security/tools: harden exec preflight reads, host env denylisting, node output boundaries, outbound host-media reads, profile-mutation authorization, plugin install dependency scanning, ACPX tool hooks, Gmail watcher token redaction, and oversized realtime WebSocket frame handling. (#62333, #62661, #62662, #63277, #63551, #63553, #63886, #63890, #63891, #64459)</li>
<li>OpenAI/Codex: add required Codex OAuth scopes, classify provider/runtime failures more clearly, stop suggesting <code>/elevated full</code> when auto-approved host exec is unavailable, add OpenAI/Codex tool-schema compatibility, and preserve embedded-run replay/liveness truth across compaction retries and mutating side effects. (#64300, #64439) Thanks @100yenadmin.</li>
<li>CLI/WhatsApp media sends: route gateway-mode outbound sends with <code>--media</code> through the channel <code>sendMedia</code> path and preserve media access context, so WhatsApp document and attachment sends stop silently dropping the file while still delivering the caption. (#64478, #64492) Thanks @ShionEria.</li>
<li>Microsoft Teams: restore media downloads for personal DMs, Bot Framework <code>a:</code> conversations, OneDrive/SharePoint shared files, and Graph-backed chat IDs; accept Bot Framework audience tokens; prevent feedback-learning filename collisions; keep long tool chains alive with typing indicators; add SSO sign-in callbacks; inject parent context for thread replies; and deliver cron announcements to Teams conversation IDs. (#54932, #55383, #55386, #58001, #58249, #58774, #59731, #60956, #62219, #62674, #63063, #63942, #63945, #63949, #63951, #63953, #64087, #64088, #64089)</li>
<li>Gateway/tailscale: start Tailscale exposure and the gateway update check before awaiting channel and plugin sidecar startup so remote operators are not locked out when startup sidecars stall.</li>
<li>Gateway/startup: keep WebSocket RPC available while channels and plugin sidecars start, hold <code>chat.history</code> unavailable until startup sidecars finish so synchronous history reads cannot stall startup (reported in #63450), refresh advertised gateway methods after deferred plugin reloads, and enforce the pre-auth WebSocket upgrade budget before the no-handler 503 path so upgrade floods cannot bypass connection limits during that window. (#63480) Thanks @neeravmakwana.</li>
<li>WhatsApp: keep inbound replies, media, composing indicators, and queued outbound deliveries attached to the current socket across reconnect gaps, including fresh retry-eligible sends after the listener comes back. (#30806, #46299, #62892, #63916) Thanks @mcaxtr.</li>
<li>Gateway/thread routing: preserve Slack, Telegram, Mattermost, Matrix, ACP, restart-sentinel, and agent announce delivery targets so subagent, cron, stream-relay, session fallback, and restart messages land back in the originating thread, topic, or room casing. (#54840, #57056, #63143, #63228, #63506, #64343, #64391)</li>
<li>Models/fallback: preserve <code>/models</code> selection across transient primary-model failures and config reloads, allow timeout cooldown probes, classify OpenRouter no-endpoints responses, detect llama.cpp context overflows, and keep provider/runtime context metadata stable through reloads. (#61472, #64196, #64471)</li>
<li>Agents/BTW: keep <code>/btw</code> side questions working after tool-use turns by stripping replayed tool blocks, hidden reasoning, and malformed image payloads, omitting empty tool arrays, allowing Bedrock <code>auth: "aws-sdk"</code>, and routing Feishu <code>/btw</code> plus <code>/stop</code> through bounded out-of-band lanes. (#64218, #64219, #64225, #64324) Thanks @ngutman.</li>
<li>Control UI/BTW: render <code>/btw</code> side results as dismissible ephemeral cards in the browser, send <code>/btw</code> immediately during active runs, and clear stale BTW cards on reset flows so webchat matches the intended detached side-question behavior. (#64290) Thanks @ngutman.</li>
<li>Commands/targeting: use the selected agent or session for command output, send policy, usage/cost, context reports, model lists, bash sandbox hints, BTW/compact working directories, plugin commands, and session exports so multi-agent commands describe and mutate the intended target instead of the requester.</li>
<li>Conversation bindings: normalize focused/current conversation ids, preserve binding metadata on account and Discord rebinds, avoid stale Discord lifecycle windows, and keep generic activity touches persisted so reply routing survives rebinds and restarts.</li>
<li>iMessage/self-chat: distinguish normal DM outbound rows from true self-chat using <code>destination_caller_id</code> plus chat participants, preserve multi-handle self-chat aliases, drop ambiguous reflected echoes, and strip wrapped imsg RPC text fields. (#61619, #63868, #63980, #63989, #64000) Thanks @neeravmakwana.</li>
<li>Matrix: keep multi-account room scoping consistent, keep packaged crypto migrations warning-only when appropriate, preserve ordered block streaming, add explicit Matrix block-streaming opt-in, and resolve verification/bootstrap from the packaged runtime entry. (#58449, #59249, #59266, #64373) Thanks @gumadeiras.</li>
<li>Telegram/security: tighten Telegram <code>allowFrom</code> sender validation and keep <code>/whoami</code> allowlist reporting in sync with command auth checks.</li>
<li>Agents/timeouts: extend the default LLM idle window to 120s and keep silent no-token idle timeouts on recovery paths, so slow models can retry or fall back before users see an error.</li>
<li>Gateway/agents: preserve configured model selection and richer <code>IDENTITY.md</code> content across agent create/update flows and workspace moves, and fail safely instead of silently overwriting unreadable identity files. (#61577) Thanks @samzong.</li>
<li>Skills/TaskFlow: restore valid frontmatter fences for the bundled <code>taskflow</code> and <code>taskflow-inbox-triage</code> skills and copy bundled <code>SKILL.md</code> files as hard dist-runtime copies so skills stay discoverable and loadable after updates. (#64166, #64469) Thanks @extrasmall0.</li>
<li>Skills: respect overridden home directories when loading personal skills so service, test, and custom launch environments read the intended user skill directory instead of the process home.</li>
<li>Windows/exec: settle supervisor waits from child exit state after stdout and stderr drain even when <code>close</code> never arrives, so CLI commands stop hanging or dying with forced <code>SIGKILL</code> on Windows. (#64072) Thanks @obviyus.</li>
<li>Browser/sandbox: prevent sandbox browser CDP startup hangs by recreating containers when the browser security hash changes and by waiting on the correct sandbox browser lifecycle. (#62873) Thanks @Syysean.</li>
<li>QQBot/streaming: make block streaming configurable per QQ bot account via <code>streaming.mode</code> (<code>"partial"</code> | <code>"off"</code>, default <code>"partial"</code>) instead of hardcoding it off, so responses can be delivered incrementally. (#63746)</li>
<li>QQBot/config: allow extra fields in <code>channels.qqbot</code> and <code>channels.qqbot.accounts.*</code> so extended qqbot builds can add new config options without gateway startup failing on schema validation. (#64075) Thanks @WideLee.</li>
<li>Dreaming/gateway: require <code>operator.admin</code> for persistent <code>/dreaming on|off</code> changes and treat missing gateway client scopes as unprivileged instead of silently allowing config writes. (#63872) Thanks @mbelinky.</li>
<li>Gateway/pairing: prefer explicit QR bootstrap auth over earlier Tailscale auth classification so iOS <code>/pair qr</code> silent bootstrap pairing does not fall through to <code>pairing required</code>. (#59232) Thanks @ngutman.</li>
<li>Browser/control: auto-generate browser-control auth tokens for <code>none</code> and <code>trusted-proxy</code> modes, and route browser auth/profile/doctor helpers through the public browser plugin facades. (#63280, #63957) Thanks @pgondhi987.</li>
<li>Browser/act: centralize <code>/act</code> request normalization and execution dispatch while adding stable machine-readable route-level error codes for invalid requests, selector misuse, evaluate-disabled gating, target mismatch, and existing-session unsupported actions. (#63977) Thanks @joshavant.</li>
<li>Security/QQBot: enforce media storage boundaries for all outbound local file paths and route image-size probes through SSRF-guarded media fetching instead of raw <code>fetch()</code>. (#63271, #63495) Thanks @pgondhi987.</li>
<li>Channel setup: ignore workspace plugin shadows when resolving trusted channel setup catalog entries so onboarding and setup flows keep using the bundled, trusted setup contract.</li>
<li>Gateway/memory startup: load the explicitly selected memory-slot plugin during gateway startup, while keeping restrictive allowlists and implicit default memory slots from auto-starting unrelated memory plugins. (#64423) Thanks @EronFan.</li>
<li>Config/plugins: let config writes keep disabled plugin entries without forcing required plugin config schemas or crashing raw plugin validation, and avoid re-activating plugin registry state during schema checks. (#54971, #63296) Thanks @fuller-stack-dev.</li>
<li>Config validation: surface the actual offending field for strict-schema union failures in bindings, including top-level unexpected keys on the matching ACP branch. (#40841) Thanks @Hollychou924.</li>
<li>Wizard/plugin config: coerce integer-typed plugin config fields from interactive text input so integer schema values persist as numbers instead of failing validation. (#63346) Thanks @jalehman.</li>
<li>Daemon/gateway install: preserve safe custom service env vars on forced reinstall, merge prior custom PATH segments behind the managed service PATH, and stop removed managed env keys from persisting as custom carryover. (#63136) Thanks @WarrenJones.</li>
<li>Cron/scheduling: treat <code>nextRunAtMs <= 0</code> as invalid across cron update, maintenance, timer, and stale-delivery paths so corrupted zero timestamps self-heal instead of causing immediate runs or skipped deliveries. (#63507) Thanks @WarrenJones.</li>
<li>Cron/auth: resolve auth profiles consistently for isolated cron jobs so scheduled runs use the same configured provider credentials as interactive sessions. (#62797) Thanks @neeravmakwana.</li>
<li>Tasks: let <code>openclaw tasks cancel</code> cancel stuck background tasks that never reached a normal terminal state. (#62506) Thanks @neeravmakwana.</li>
<li>Sessions/model selection: preserve catalog-backed session model labels, provider-qualified context limits, and already-qualified session model refs when catalog metadata is unavailable, so model selection and memory/context budgets survive reloads without bogus provider prefixes. (#61382, #62493) Thanks @Mule-ME.</li>
<li>Status: show configured fallback models in <code>/status</code> and shared session status cards so per-agent fallback configuration is visible before a live failover happens. (#33111) Thanks @AnCoSONG.</li>
<li><code>/context detail</code> now compares the tracked prompt estimate with cached context usage and surfaces untracked provider/runtime overhead when present. (#28391) Thanks @ImLukeF.</li>
<li>Gateway/sessions: scope bare <code>sessions.create</code> aliases like <code>main</code> to the requested agent while preserving the canonical <code>global</code> and <code>unknown</code> sentinel keys. (#58207) Thanks @jalehman.</li>
<li>Gateway/session reset: emit the typed <code>before_reset</code> hook for gateway <code>/new</code> and <code>/reset</code>, preserving reset-hook behavior even when the previous transcript has already been archived. (#53872) Thanks @VACInc.</li>
<li>Plugins/commands: pass the active host <code>sessionKey</code> into plugin command contexts, and include <code>sessionId</code> when it is already available from the active session entry, so bundled and third-party commands can resolve the current conversation reliably. (#59044) Thanks @jalehman.</li>
<li>Agents/auth: honor <code>models.providers.*.authHeader</code> for pi embedded runner model requests by injecting <code>Authorization: Bearer <apiKey></code> when requested. (#54390) Thanks @lndyzwdxhs.</li>
<li>Claude CLI: clear inherited Anthropic auth/header environment aliases before spawning Claude Code and add sanitized CLI backend auth-env diagnostics for debugging gateway-run provider selection.</li>
<li>Agents/failover: classify AbortError and stream-abort messages as timeout so Ollama NDJSON stream aborts stop showing <code>reason=unknown</code> in model fallback logs. (#58324) Thanks @yelog.</li>
<li>Fireworks/FirePass: disable Kimi K2.5 Turbo reasoning output by forcing thinking off on the FirePass path and hardening the provider wrapper so hidden reasoning no longer leaks into visible replies. (#63607) Thanks @frankekn.</li>
<li>Discord: update Carbon to v0.15.0. Thanks @thewilloftheshadow.</li>
<li>Config/Discord: coerce safe integer numeric Discord IDs to strings during config validation, keep unsafe or precision-losing numeric snowflakes rejected, and align <code>openclaw doctor</code> repair guidance with the same fail-closed behavior. (#45125) Thanks @moliendocode.</li>
<li>BlueBubbles/config: accept <code>enrichGroupParticipantsFromContacts</code> in the core strict config schema so gateways no longer fail validation or startup when the BlueBubbles plugin writes that field. (#56889) Thanks @zqchris.</li>
<li>Feishu/webhooks: read webhook bodies through the pre-auth guard so unauthenticated webhook traffic stays under the same body budget as other protected channel ingress paths.</li>
<li>Tools/web_fetch: add an opt-in <code>tools.web.fetch.ssrfPolicy.allowRfc2544BenchmarkRange</code> config so fake-IP proxy environments that resolve public sites into <code>198.18.0.0/15</code> can use <code>web_fetch</code> without weakening the default SSRF block. (#61830) Thanks @xing-xing-coder.</li>
<li>Dreaming/cron: reconcile managed dreaming cron from startup config and runtime lifecycle changes, but only recover managed dreaming cron state during heartbeat-triggered dreaming checks so ordinary chat traffic does not recreate removed jobs. (#63873, #63929, #63938) Thanks @mbelinky.</li>
<li>Memory/lancedb: accept <code>dreaming</code> config when <code>memory-lancedb</code> owns the memory slot so Dreaming surfaces can read slot-owner settings without schema rejection. (#63874) Thanks @mbelinky.</li>
<li>Control UI/dreaming: keep the Dreaming trace area contained and scrollable so overlays no longer cover tabs or blow out the page layout. (#63875) Thanks @mbelinky.</li>
<li>Dreaming/narrative: harden request-scoped diary fallback so scheduled dreaming only falls back on the dedicated subagent-runtime error, stop trusting spoofable raw error-code objects, and avoid leaking workspace paths when local fallback writes fail. (#64156) Thanks @mbelinky.</li>
<li>Dreaming/diary: add idempotent narrative subagent runs, preserve restrictive <code>DREAMS.md</code> permissions during atomic writes, and surface temp cleanup failures so repeated sweeps do not double-run the same narrative request or silently weaken diary safety. (#63876) Thanks @mbelinky.</li>
<li>Heartbeats/sessions: remove stale accumulated isolated heartbeat session keys when the next tick converges them back to the canonical sibling, so repaired sessions stop showing orphaned <code>:heartbeat:heartbeat</code> variants in session listings. (#59606) Thanks @rogerdigital.</li>
<li>Gateway/run cleanup: fix stale run-context TTL cleanup so the new maintenance sweep resets orphaned run sequence state and prevents unbounded run-context growth. (#52731) Thanks @artwalker.</li>
<li>UI/compaction: keep the compaction indicator in a retry-pending state until the run actually finishes, so the UI does not show <code>Context compacted</code> before compaction actually finishes. (#55132) Thanks @mpz4life.</li>
<li>Cron/tool schemas: keep cron tool schemas strict-model-friendly while still preserving <code>failureAlert=false</code>, nullable <code>agentId</code>/<code>sessionKey</code>, and flattened add/update recovery for the newly exposed cron job fields. (#55043) Thanks @brunolorente.</li>
<li>Git metadata: read commit ids from packed refs as well as loose refs so version and status metadata stay accurate after repository maintenance. (#63943)</li>
<li>Gateway: keep <code>commands.list</code> skill entries categorized under tools and include provider-aware plugin <code>nativeName</code> metadata even when <code>scope=text</code>, so remote clients can group skills correctly and map text-surface plugin commands back to native aliases. (#64147)</li>
<li>TUI: reset footer activity to idle when switching sessions so a stale streaming indicator cannot persist after the selection changes. (#63988) Thanks @neeravmakwana.</li>
<li>Claude CLI: stop marking spawned Claude Code runs as host-managed so they keep using normal CLI subscription behavior. (#64023) Thanks @Alex-Alaniz.</li>
<li>Codex auth: brand Codex OAuth flows as OpenClaw in user-visible auth prompts and diagnostics.</li>
<li>Gateway/pairing: fail closed for paired device records that have no device tokens, and reject pairing approvals whose requested scopes do not match the requested device roles.</li>
<li>ACP/gateway chat: classify lifecycle errors before forwarding them to ACP clients so refusals use ACP's refusal stop reason while transient backend errors continue to finish as normal turns.</li>
<li>Claude CLI/skills: pass eligible OpenClaw skills into CLI runs, including native Claude Code skill resolution via a temporary plugin plus per-run skill env/API key injection. (#62686, #62723) Thanks @zomars.</li>
<li>Discord: keep generated auto-thread names working with reasoning models by giving title generation enough output budget for thinking plus visible title text. (#64172) Thanks @hanamizuki.</li>
<li>Heartbeat: ignore doc-only Markdown fence markers in the default <code>HEARTBEAT.md</code> template so comment-only heartbeat scaffolds skip API calls again. (#61690, #63434) Thanks @ravyg.</li>
<li>Reply/skills: keep resolved skill and memory secret config stable through embedded reply runs so raw SecretRefs in secondary skill settings no longer crash replies when the gateway already has the live env. (#64249) Thanks @mbelinky.</li>
<li>Dreaming/startup: keep plugin-registered startup hooks alive across workspace hook reloads and include dreaming startup owners in the gateway startup plugin scope, so managed Dreaming cron registration comes back reliably after gateway boot. (#62327, #64258) Thanks @mbelinky.</li>
<li>Plugins: treat duplicate <code>registerService</code> calls from the same plugin id as idempotent so snapshot and activation loads no longer emit spurious <code>service already registered</code> diagnostics. (#62033, #64128) Thanks @ly85206559.</li>
<li>Discord/TTS: route auto voice replies through the native voice-note path so Discord receives Opus voice messages instead of regular audio attachments. (#64096) Thanks @LiuHuaize.</li>
<li>Config/plugins: use plugin-owned command alias metadata when <code>plugins.allow</code> contains runtime command names like <code>dreaming</code>, and point users at the owning plugin instead of stale plugin-not-found guidance. (#64191, #64242) Thanks @feiskyer.</li>
<li>Agents/Gemini: strip orphaned <code>required</code> entries from Gemini tool schemas so provider validation no longer rejects tools after schema cleanup or union flattening. (#64284) Thanks @xxxxxmax.</li>
<li>Assistant text: strip Qwen-style XML tool call payloads from visible replies so web and channel messages no longer show raw <code><tool_call><function=...></code> output. (#63999, #64214) Thanks @MoerAI.</li>
<li>Daemon/gateway: prevent systemd restart storms on configuration errors by exiting with <code>EX_CONFIG</code> and adding generated unit restart-prevention guards. (#63913) Thanks @neo1027144-creator.</li>
<li>Agents/exec: prevent gateway crash ("Agent listener invoked outside active run") when a subagent exec tool produces stdout/stderr after the agent run has ended or been aborted. (#62821) Thanks @openperf.</li>
<li>Gateway/OpenAI compat: return real <code>usage</code> for non-stream <code>/v1/chat/completions</code> responses, emit the final usage chunk when <code>stream_options.include_usage=true</code>, and bound usage-gated stream finalization after lifecycle end. (#62986) Thanks @Lellansin.</li>
<li>Matrix/migration: keep packaged warning-only crypto migrations from being misclassified as actionable when only helper chunks are present, so startup and doctor stay on the warning-only path instead of creating unnecessary migration snapshots. (#64373) Thanks @gumadeiras.</li>
<li>Matrix/ACP thread bindings: preserve canonical room casing and parent conversation routing during ACP session spawn so mixed-case room ids bind correctly from top-level rooms and existing Matrix threads. (#64343) Thanks @gumadeiras.</li>
<li>Agents/subagents: deduplicate delivered completion announces so retry or re-entry cleanup does not inject duplicate internal-context completion turns into the parent session. (#61525) Thanks @100yenadmin.</li>
<li>Agents/exec: keep sandboxed <code>tools.exec.host=auto</code> sessions from honoring per-call <code>host=node</code> or <code>host=gateway</code> overrides while a sandbox runtime is active, and stop advertising node routing in that state so exec stays on the sandbox host. (#63880)</li>
<li>Agents/subagents: preserve archived delete-mode runs until <code>sessions.delete</code> succeeds and prevent overlapping archive sweeps from duplicating in-flight cleanup attempts. (#61801) Thanks @100yenadmin.</li>
<li>Cron/isolated agent: run scheduled agent turns as non-owner senders so owner-only tools stay unavailable during cron execution. (#63878)</li>
<li>Discord/sandbox: include <code>image</code> in sandbox media param normalization so Discord event cover images cannot bypass sandbox path rewriting. (#64377) Thanks @mmaps.</li>
<li>Agents/exec: extend exec completion detection to cover local background exec formats so the owner-downgrade fires correctly for all exec paths. (#64376) Thanks @mmaps.</li>
<li>Security/dependencies: pin axios to 1.15.0 and add a plugin install dependency denylist that blocks known malicious packages before install. (#63891) Thanks @mmaps.</li>
<li>Browser/security: apply three-phase interaction navigation guard to pressKey and type(submit) so delayed JS redirects from keypress cannot bypass SSRF policy. (#63889) Thanks @mmaps.</li>
</ul>
<ul>
<li>Browser/security: guard existing-session Chrome MCP interaction routes with SSRF post-checks so delayed navigation from click, type, press, and evaluate cannot bypass the configured policy. (#64370) Thanks @eleqtrizit.</li>
<li>Browser/security: default browser SSRF policy to strict mode so unconfigured installs block private-network navigation, and align external-content marker span mapping so ZWS-injected boundary spoofs are fully sanitized. (#63885) Thanks @eleqtrizit.</li>
<li>Browser/security: apply SSRF navigation policy to subframe document navigations so iframe-targeted private-network hops are blocked without quarantining the parent page. (#64371) Thanks @eleqtrizit.</li>
<li>Hooks/security: mark agent hook system events as untrusted and sanitize hook display names before cron metadata reuse. (#64372) Thanks @eleqtrizit.</li>
<li>Daemon/launchd: keep <code>openclaw gateway stop</code> persistent without uninstalling the macOS LaunchAgent, re-enable it on explicit restart or repair, and harden launchd label handling. (#64447) Thanks @ngutman.</li>
<li>Plugins/context engines: preserve <code>plugins.slots.contextEngine</code> through normalization and keep explicitly selected workspace context-engine plugins enabled, so loader diagnostics and plugin activation stop dropping that slot selection. (#64192) Thanks @hclsys.</li>
<li>Heartbeat: stop top-level <code>interval:</code> and <code>prompt:</code> fields outside the <code>tasks:</code> block from bleeding into the last parsed heartbeat task. (#64488) Thanks @Rahulkumar070.</li>
<li>Agents/OpenAI replay: preserve malformed function-call arguments in stored assistant history, avoid double-encoding preserved raw strings on replay, and coerce replayed string args back to objects at Anthropic and Google provider boundaries. (#61956) Thanks @100yenadmin.</li>
<li>Heartbeat/config: accept and honor <code>agents.defaults.heartbeat.timeoutSeconds</code> and per-agent heartbeat timeout overrides for heartbeat agent turns. (#64491) Thanks @cedillarack.</li>
<li>CLI/devices: make implicit <code>openclaw devices approve</code> selection preview-only and require approving the exact request ID, preventing latest-request races during device pairing. (#64160) Thanks @coygeek.</li>
<li>Media/security: honor sender-scoped <code>toolsBySender</code> policy for outbound host-media reads so denied senders cannot trigger host file disclosure via attachment hydration. (#64459) Thanks @eleqtrizit.</li>
<li>Browser/security: reject strict-policy hostname navigation unless the hostname is an explicit allowlist exception or IP literal, and route CDP HTTP discovery through the pinned SSRF fetch path. (#64367) Thanks @eleqtrizit.</li>
<li>Models/vLLM: ignore empty <code>tool_calls</code> arrays from reasoning-model OpenAI-compatible replies, reset false <code>toolUse</code> stop reasons when no actual tool calls were parsed, and stop sending <code>tool_choice</code> unless tools are present so vLLM reasoning responses no longer hang indefinitely. (#61197, #61534) Thanks @balajisiva.</li>
<li>Heartbeat/scheduling: spread interval heartbeats across stable per-agent phases derived from gateway identity, so provider traffic is distributed more uniformly across the configured interval instead of clustering around startup-relative times. (#64560) Thanks @odysseus0.</li>
</ul>
<p><a href="https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md">View full changelog</a></p>
]]></description>
<enclosure url="https://github.com/openclaw/openclaw/releases/download/v2026.4.10/OpenClaw-2026.4.10.zip" length="47259509" type="application/octet-stream" sparkle:edSignature="XY9FHxx09r2O9rlFs3t5UV9Zk2rGXSpWw5InazJhb661kgp6OKiOrrNTV631b2StWze5tnSEPXakkOCXq7O6DQ=="/>
</item>
</channel>
</rss>

View File

@@ -65,8 +65,8 @@ android {
applicationId = "ai.openclaw.app"
minSdk = 31
targetSdk = 36
versionCode = 2026041690
versionName = "2026.4.16"
versionCode = 2026042100
versionName = "2026.4.21"
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,18 @@
# OpenClaw iOS Changelog
## 2026.4.16 - 2026-04-17
## 2026.4.21 - 2026-04-21
Maintenance update for the current OpenClaw development release.
## 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.21
OPENCLAW_MARKETING_VERSION = 2026.4.21
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 +1 @@
Maintenance update for the current OpenClaw release.
Maintenance update for the current OpenClaw development release.

View File

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

View File

@@ -0,0 +1,141 @@
{
"originHash" : "6b8aa02e612c43e309033a83de5f83b88d9c4267f124d1e062f66385dbbaa7ec",
"pins" : [
{
"identity" : "eventsource",
"kind" : "remoteSourceControl",
"location" : "https://github.com/mattt/EventSource.git",
"state" : {
"revision" : "a3a85a85214caf642abaa96ae664e4c772a59f6e",
"version" : "1.4.1"
}
},
{
"identity" : "mlx-audio-swift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/Blaizzy/mlx-audio-swift",
"state" : {
"revision" : "fcbd04daa1bfebe881932f630af2ba6ce9af3274",
"version" : "0.1.2"
}
},
{
"identity" : "mlx-swift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/ml-explore/mlx-swift.git",
"state" : {
"revision" : "61b9e011e09a62b489f6bd647958f1555bdf2896",
"version" : "0.31.3"
}
},
{
"identity" : "mlx-swift-lm",
"kind" : "remoteSourceControl",
"location" : "https://github.com/ml-explore/mlx-swift-lm.git",
"state" : {
"revision" : "25b00d4e22e61ec9c41efda47990cd2084ec87ff",
"version" : "2.31.3"
}
},
{
"identity" : "swift-asn1",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-asn1.git",
"state" : {
"revision" : "eb50cbd14606a9161cbc5d452f18797c90ef0bab",
"version" : "1.7.0"
}
},
{
"identity" : "swift-atomics",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-atomics.git",
"state" : {
"revision" : "b601256eab081c0f92f059e12818ac1d4f178ff7",
"version" : "1.3.0"
}
},
{
"identity" : "swift-collections",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-collections.git",
"state" : {
"revision" : "6675bc0ff86e61436e615df6fc5174e043e57924",
"version" : "1.4.1"
}
},
{
"identity" : "swift-crypto",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-crypto.git",
"state" : {
"revision" : "476538ccb827f2dd18efc5de754cc87d77127a47",
"version" : "4.4.0"
}
},
{
"identity" : "swift-huggingface",
"kind" : "remoteSourceControl",
"location" : "https://github.com/huggingface/swift-huggingface.git",
"state" : {
"revision" : "b721959445b617d0bf03910b2b4aced345fd93bf",
"version" : "0.9.0"
}
},
{
"identity" : "swift-jinja",
"kind" : "remoteSourceControl",
"location" : "https://github.com/huggingface/swift-jinja.git",
"state" : {
"revision" : "0aeefadec459ce8e11a333769950fb86183aca43",
"version" : "2.3.5"
}
},
{
"identity" : "swift-nio",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-nio.git",
"state" : {
"revision" : "cd6710454f25733900e133c6caf5188952763c36",
"version" : "2.98.0"
}
},
{
"identity" : "swift-numerics",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-numerics",
"state" : {
"revision" : "0c0290ff6b24942dadb83a929ffaaa1481df04a2",
"version" : "1.1.1"
}
},
{
"identity" : "swift-system",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-system.git",
"state" : {
"revision" : "7c6ad0fc39d0763e0b699210e4124afd5041c5df",
"version" : "1.6.4"
}
},
{
"identity" : "swift-transformers",
"kind" : "remoteSourceControl",
"location" : "https://github.com/huggingface/swift-transformers.git",
"state" : {
"revision" : "58c4bc11963a140358d791f678a60a2745a23146",
"version" : "1.2.1"
}
},
{
"identity" : "yyjson",
"kind" : "remoteSourceControl",
"location" : "https://github.com/ibireme/yyjson.git",
"state" : {
"revision" : "8b4a38dc994a110abaec8a400615567bd996105f",
"version" : "0.12.0"
}
}
],
"version" : 3
}

View File

@@ -0,0 +1,27 @@
// swift-tools-version: 6.2
// Isolated MLX TTS helper package. Keep this out of apps/macos/Package.swift so
// normal macOS app tests do not compile the full MLX audio stack.
import PackageDescription
let package = Package(
name: "OpenClawMLXTTS",
platforms: [
.macOS(.v15),
],
products: [
.executable(name: "openclaw-mlx-tts", targets: ["OpenClawMLXTTSHelper"]),
],
dependencies: [
.package(url: "https://github.com/Blaizzy/mlx-audio-swift", exact: "0.1.2"),
],
targets: [
.executableTarget(
name: "OpenClawMLXTTSHelper",
dependencies: [
.product(name: "MLXAudioTTS", package: "mlx-audio-swift"),
],
swiftSettings: [
.enableUpcomingFeature("StrictConcurrency"),
]),
])

View File

@@ -0,0 +1,182 @@
import Foundation
import MLXAudioTTS
// swiftformat:disable wrap wrapMultilineStatementBraces trailingCommas redundantSelf extensionAccessControl
@main
enum OpenClawMLXTTSHelper {
static func main() async {
do {
let options = try Options.parse(CommandLine.arguments.dropFirst())
let data = try await synthesize(options)
try data.write(to: options.outputURL, options: [.atomic])
} catch {
FileHandle.standardError.write(Data("openclaw-mlx-tts: \(error)\n".utf8))
exit(1)
}
}
private static func synthesize(_ options: Options) async throws -> Data {
let model = try await TTS.loadModel(modelRepo: options.modelRepo)
let audio = try await UncheckedSpeechModel(raw: model).generateAudio(
text: options.text,
voice: options.voice,
language: options.language)
return makeWavData(samples: audio, sampleRate: Double(model.sampleRate))
}
private struct Options {
let text: String
let modelRepo: String
let outputURL: URL
let language: String?
let voice: String?
static func parse(_ rawArguments: ArraySlice<String>) throws -> Options {
var text: String?
var modelRepo = "mlx-community/Soprano-80M-bf16"
var outputPath: String?
var language: String?
var voice: String?
var iterator = rawArguments.makeIterator()
while let argument = iterator.next() {
switch argument {
case "--text", "-t":
text = try nextValue(&iterator, argument)
case "--model":
modelRepo = try nextValue(&iterator, argument)
case "--output", "-o":
outputPath = try nextValue(&iterator, argument)
case "--language":
language = try nextValue(&iterator, argument)
case "--voice", "-v":
voice = try nextValue(&iterator, argument)
case "--help", "-h":
throw Usage.requested
default:
if text == nil, !argument.hasPrefix("-") {
text = argument
} else {
throw Usage.invalid("unknown option \(argument)")
}
}
}
guard let text = text?.trimmingCharacters(in: .whitespacesAndNewlines), !text.isEmpty else {
throw Usage.invalid("missing --text")
}
guard let outputPath, !outputPath.isEmpty else {
throw Usage.invalid("missing --output")
}
return Options(
text: text,
modelRepo: modelRepo,
outputURL: URL(fileURLWithPath: outputPath),
language: language?.nilIfBlank,
voice: voice?.nilIfBlank)
}
private static func nextValue(
_ iterator: inout ArraySlice<String>.Iterator,
_ option: String) throws -> String
{
guard let value = iterator.next(), !value.isEmpty else {
throw Usage.invalid("missing value for \(option)")
}
return value
}
}
private enum Usage: Error, CustomStringConvertible {
case requested
case invalid(String)
var description: String {
switch self {
case .requested:
"usage: openclaw-mlx-tts --text <text> --output <wav> [--model <hf-repo>] [--language <id>] [--voice <name>]"
case let .invalid(message):
"\(message)\nusage: openclaw-mlx-tts --text <text> --output <wav> [--model <hf-repo>] [--language <id>] [--voice <name>]"
}
}
}
private static func makeWavData(samples: [Float], sampleRate: Double) -> Data {
let channels: UInt16 = 1
let bitsPerSample: UInt16 = 16
let blockAlign = channels * (bitsPerSample / 8)
let sampleRateInt = UInt32(sampleRate.rounded())
let byteRate = sampleRateInt * UInt32(blockAlign)
let dataSize = UInt32(samples.count) * UInt32(blockAlign)
var data = Data(capacity: Int(44 + dataSize))
data.append(contentsOf: [0x52, 0x49, 0x46, 0x46]) // RIFF
data.appendLEUInt32(36 + dataSize)
data.append(contentsOf: [0x57, 0x41, 0x56, 0x45]) // WAVE
data.append(contentsOf: [0x66, 0x6D, 0x74, 0x20]) // fmt
data.appendLEUInt32(16)
data.appendLEUInt16(1)
data.appendLEUInt16(channels)
data.appendLEUInt32(sampleRateInt)
data.appendLEUInt32(byteRate)
data.appendLEUInt16(blockAlign)
data.appendLEUInt16(bitsPerSample)
data.append(contentsOf: [0x64, 0x61, 0x74, 0x61]) // data
data.appendLEUInt32(dataSize)
for sample in samples {
let clamped = max(-1.0, min(1.0, sample))
let scaled = Int16((clamped * Float(Int16.max)).rounded())
data.appendLEInt16(scaled)
}
return data
}
}
private struct UncheckedSpeechModel {
let raw: any SpeechGenerationModel
func generateAudio(
text: String,
voice: String?,
language: String?) async throws -> [Float] {
let generatedAudio = try await raw.generate(
text: text,
voice: voice,
refAudio: nil,
refText: nil,
language: language)
return generatedAudio.asArray(Float.self)
}
}
extension UncheckedSpeechModel: @unchecked Sendable {}
private extension String {
var nilIfBlank: String? {
let trimmed = self.trimmingCharacters(in: .whitespacesAndNewlines)
return trimmed.isEmpty ? nil : trimmed
}
}
private extension Data {
mutating func appendLEUInt16(_ value: UInt16) {
var littleEndian = value.littleEndian
Swift.withUnsafeBytes(of: &littleEndian) { append(contentsOf: $0) }
}
mutating func appendLEUInt32(_ value: UInt32) {
var littleEndian = value.littleEndian
Swift.withUnsafeBytes(of: &littleEndian) { append(contentsOf: $0) }
}
mutating func appendLEInt16(_ value: Int16) {
var littleEndian = value.littleEndian
Swift.withUnsafeBytes(of: &littleEndian) { append(contentsOf: $0) }
}
}
// swiftformat:enable wrap wrapMultilineStatementBraces trailingCommas redundantSelf extensionAccessControl

View File

@@ -1,5 +1,5 @@
{
"originHash" : "31972864afdac74537794e1a3b7bd22484c09ec1be8e3624fb9ea582e9222ad9",
"originHash" : "fb90e7b1977f43661ac91681d16da11f9ddd85630407ef170eaada0a6ee39972",
"pins" : [
{
"identity" : "axorcist",
@@ -28,15 +28,6 @@
"version" : "0.1.0"
}
},
{
"identity" : "eventsource",
"kind" : "remoteSourceControl",
"location" : "https://github.com/mattt/EventSource.git",
"state" : {
"revision" : "a3a85a85214caf642abaa96ae664e4c772a59f6e",
"version" : "1.4.1"
}
},
{
"identity" : "menubarextraaccess",
"kind" : "remoteSourceControl",
@@ -46,33 +37,6 @@
"version" : "1.2.2"
}
},
{
"identity" : "mlx-audio-swift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/Blaizzy/mlx-audio-swift",
"state" : {
"revision" : "fcbd04daa1bfebe881932f630af2ba6ce9af3274",
"version" : "0.1.2"
}
},
{
"identity" : "mlx-swift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/ml-explore/mlx-swift.git",
"state" : {
"revision" : "61b9e011e09a62b489f6bd647958f1555bdf2896",
"version" : "0.31.3"
}
},
{
"identity" : "mlx-swift-lm",
"kind" : "remoteSourceControl",
"location" : "https://github.com/ml-explore/mlx-swift-lm.git",
"state" : {
"revision" : "25b00d4e22e61ec9c41efda47990cd2084ec87ff",
"version" : "2.31.3"
}
},
{
"identity" : "peekaboo",
"kind" : "remoteSourceControl",
@@ -100,33 +64,6 @@
"version" : "1.2.1"
}
},
{
"identity" : "swift-asn1",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-asn1.git",
"state" : {
"revision" : "9f542610331815e29cc3821d3b6f488db8715517",
"version" : "1.6.0"
}
},
{
"identity" : "swift-atomics",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-atomics.git",
"state" : {
"revision" : "b601256eab081c0f92f059e12818ac1d4f178ff7",
"version" : "1.3.0"
}
},
{
"identity" : "swift-collections",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-collections.git",
"state" : {
"revision" : "6675bc0ff86e61436e615df6fc5174e043e57924",
"version" : "1.4.1"
}
},
{
"identity" : "swift-concurrency-extras",
"kind" : "remoteSourceControl",
@@ -136,33 +73,6 @@
"version" : "1.3.2"
}
},
{
"identity" : "swift-crypto",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-crypto.git",
"state" : {
"revision" : "bb4ba815dab96d4edc1e0b86d7b9acf9ff973a84",
"version" : "4.3.1"
}
},
{
"identity" : "swift-huggingface",
"kind" : "remoteSourceControl",
"location" : "https://github.com/huggingface/swift-huggingface.git",
"state" : {
"revision" : "b721959445b617d0bf03910b2b4aced345fd93bf",
"version" : "0.9.0"
}
},
{
"identity" : "swift-jinja",
"kind" : "remoteSourceControl",
"location" : "https://github.com/huggingface/swift-jinja.git",
"state" : {
"revision" : "0aeefadec459ce8e11a333769950fb86183aca43",
"version" : "2.3.5"
}
},
{
"identity" : "swift-log",
"kind" : "remoteSourceControl",
@@ -172,15 +82,6 @@
"version" : "1.10.1"
}
},
{
"identity" : "swift-nio",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-nio.git",
"state" : {
"revision" : "558f24a4647193b5a0e2104031b71c55d31ff83a",
"version" : "2.97.1"
}
},
{
"identity" : "swift-numerics",
"kind" : "remoteSourceControl",
@@ -208,15 +109,6 @@
"version" : "1.6.4"
}
},
{
"identity" : "swift-transformers",
"kind" : "remoteSourceControl",
"location" : "https://github.com/huggingface/swift-transformers.git",
"state" : {
"revision" : "58c4bc11963a140358d791f678a60a2745a23146",
"version" : "1.2.1"
}
},
{
"identity" : "swiftui-math",
"kind" : "remoteSourceControl",
@@ -234,15 +126,6 @@
"revision" : "5b06b811c0f5313b6b84bbef98c635a630638c38",
"version" : "0.3.1"
}
},
{
"identity" : "yyjson",
"kind" : "remoteSourceControl",
"location" : "https://github.com/ibireme/yyjson.git",
"state" : {
"revision" : "8b4a38dc994a110abaec8a400615567bd996105f",
"version" : "0.12.0"
}
}
],
"version" : 3

View File

@@ -20,7 +20,6 @@ let package = Package(
.package(url: "https://github.com/apple/swift-log.git", from: "1.10.1"),
.package(url: "https://github.com/sparkle-project/Sparkle", from: "2.9.0"),
.package(url: "https://github.com/steipete/Peekaboo.git", branch: "main"),
.package(url: "https://github.com/Blaizzy/mlx-audio-swift", exact: "0.1.2"),
.package(path: "../shared/OpenClawKit"),
.package(path: "../../Swabble"),
],
@@ -55,7 +54,6 @@ let package = Package(
.product(name: "Sparkle", package: "Sparkle"),
.product(name: "PeekabooBridge", package: "Peekaboo"),
.product(name: "PeekabooAutomationKit", package: "Peekaboo"),
.product(name: "MLXAudioTTS", package: "mlx-audio-swift"),
],
exclude: [
"Resources/Info.plist",

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

@@ -22,7 +22,21 @@ enum ExecApprovalCommandDisplaySanitizer {
}
private static func shouldEscape(_ scalar: UnicodeScalar) -> Bool {
scalar.properties.generalCategory == .format || self.invisibleCodePoints.contains(scalar.value)
let category = scalar.properties.generalCategory
if category == .control
|| category == .format
|| category == .lineSeparator
|| category == .paragraphSeparator
{
return true
}
// Escape non-ASCII space separators (NBSP, narrow NBSP, ideographic space, etc.) so
// attackers cannot spoof token boundaries in the approval UI with spaces that render
// like a plain space but are handled differently by shells/parsers.
if category == .spaceSeparator, scalar.value != 0x20 {
return true
}
return self.invisibleCodePoints.contains(scalar.value)
}
private static func escape(_ scalar: UnicodeScalar) -> String {

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.21</string>
<key>CFBundleVersion</key>
<string>2026041690</string>
<string>2026042100</string>
<key>CFBundleIconFile</key>
<string>OpenClaw</string>
<key>CFBundleURLTypes</key>

View File

@@ -1,5 +1,4 @@
import Foundation
import MLXAudioTTS
import OSLog
// swiftformat:disable wrap wrapMultilineStatementBraces trailingCommas redundantSelf extensionAccessControl
@@ -18,13 +17,14 @@ final class TalkMLXSpeechSynthesizer {
private let logger = Logger(subsystem: "ai.openclaw", category: "talk.mlx")
private var currentToken = UUID()
private var modelRepo: String?
private var model: (any SpeechGenerationModel)?
private var currentProcess: Process?
private init() {}
func stop() {
self.currentToken = UUID()
self.currentProcess?.terminate()
self.currentProcess = nil
}
func synthesize(
@@ -39,59 +39,93 @@ final class TalkMLXSpeechSynthesizer {
let token = UUID()
self.currentToken = token
let tempDir = FileManager.default.temporaryDirectory
.appendingPathComponent("openclaw-mlx-tts-\(token.uuidString)", isDirectory: true)
try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)
defer { try? FileManager.default.removeItem(at: tempDir) }
let outputURL = tempDir.appendingPathComponent("speech.wav")
let invocation = Self.helperInvocation()
let resolvedRepo = Self.resolvedModelRepo(modelRepo)
let rawModel = try await self.loadModel(
modelRepo: resolvedRepo,
token: token)
let model = UncheckedSpeechModel(raw: rawModel)
var arguments = invocation.argumentPrefix
arguments += [
"--text", trimmed,
"--model", resolvedRepo,
"--output", outputURL.path,
]
if let language = language?.trimmingCharacters(in: .whitespacesAndNewlines), !language.isEmpty {
arguments += ["--language", language]
}
if let voicePreset = voicePreset?.trimmingCharacters(in: .whitespacesAndNewlines), !voicePreset.isEmpty {
arguments += ["--voice", voicePreset]
}
self.logger.info("talk mlx helper start modelRepo=\(resolvedRepo, privacy: .public)")
let process = Process()
process.executableURL = invocation.executableURL
process.arguments = arguments
let stderr = Pipe()
process.standardError = stderr
process.standardOutput = Pipe()
self.currentProcess = process
let status: Int32
do {
status = try await Self.run(process)
} catch {
self.currentProcess = nil
self.logger.error("talk mlx helper launch failed: \(error.localizedDescription, privacy: .public)")
throw SynthesizeError.modelLoadFailed(invocation.displayName)
}
self.currentProcess = nil
guard self.currentToken == token else {
throw SynthesizeError.canceled
}
let audioData: Data
do {
let audio = try await model.generateAudio(
text: trimmed,
voice: voicePreset,
language: language)
audioData = Self.makeWavData(
samples: audio,
sampleRate: Double(model.sampleRateValue()))
} catch {
guard status == 0 else {
let errorText = Self.readPipe(stderr)
self.logger.error(
"talk mlx generation failed: \(error.localizedDescription, privacy: .public)")
"talk mlx helper failed status=\(status, privacy: .public): \(errorText, privacy: .public)")
throw SynthesizeError.audioGenerationFailed
}
guard self.currentToken == token else {
throw SynthesizeError.canceled
do {
return try Data(contentsOf: outputURL)
} catch {
self.logger.error("talk mlx helper output missing: \(error.localizedDescription, privacy: .public)")
throw SynthesizeError.audioGenerationFailed
}
return audioData
}
private func loadModel(
modelRepo: String,
token: UUID) async throws -> any SpeechGenerationModel {
if let model = self.model, self.modelRepo == modelRepo {
return model
private struct HelperInvocation {
let executableURL: URL
let argumentPrefix: [String]
let displayName: String
}
private static func helperInvocation() -> HelperInvocation {
let fileManager = FileManager.default
if let override = ProcessInfo.processInfo.environment["OPENCLAW_MLX_TTS_BIN"], !override.isEmpty {
return HelperInvocation(
executableURL: URL(fileURLWithPath: override),
argumentPrefix: [],
displayName: override)
}
self.logger.info("talk mlx loading modelRepo=\(modelRepo, privacy: .public)")
do {
let model = try await TTS.loadModel(modelRepo: modelRepo)
guard self.currentToken == token else {
throw SynthesizeError.canceled
if let executableDir = Bundle.main.executableURL?.deletingLastPathComponent() {
let bundled = executableDir.appendingPathComponent("openclaw-mlx-tts")
if fileManager.isExecutableFile(atPath: bundled.path) {
return HelperInvocation(
executableURL: bundled,
argumentPrefix: [],
displayName: bundled.path)
}
self.model = model
self.modelRepo = modelRepo
return model
} catch is CancellationError {
throw SynthesizeError.canceled
} catch {
self.logger.error(
"talk mlx load failed: \(error.localizedDescription, privacy: .public)")
throw SynthesizeError.modelLoadFailed(modelRepo)
}
return HelperInvocation(
executableURL: URL(fileURLWithPath: "/usr/bin/env"),
argumentPrefix: ["openclaw-mlx-tts"],
displayName: "openclaw-mlx-tts")
}
private static func resolvedModelRepo(_ modelRepo: String?) -> String {
@@ -99,80 +133,26 @@ final class TalkMLXSpeechSynthesizer {
return trimmed.isEmpty ? Self.defaultModelRepo : trimmed
}
private static func makeWavData(samples: [Float], sampleRate: Double) -> Data {
let channels: UInt16 = 1
let bitsPerSample: UInt16 = 16
let blockAlign = channels * (bitsPerSample / 8)
let sampleRateInt = UInt32(sampleRate.rounded())
let byteRate = sampleRateInt * UInt32(blockAlign)
let dataSize = UInt32(samples.count) * UInt32(blockAlign)
var data = Data(capacity: Int(44 + dataSize))
data.append(contentsOf: [0x52, 0x49, 0x46, 0x46]) // RIFF
data.appendLEUInt32(36 + dataSize)
data.append(contentsOf: [0x57, 0x41, 0x56, 0x45]) // WAVE
data.append(contentsOf: [0x66, 0x6D, 0x74, 0x20]) // fmt
data.appendLEUInt32(16)
data.appendLEUInt16(1)
data.appendLEUInt16(channels)
data.appendLEUInt32(sampleRateInt)
data.appendLEUInt32(byteRate)
data.appendLEUInt16(blockAlign)
data.appendLEUInt16(bitsPerSample)
data.append(contentsOf: [0x64, 0x61, 0x74, 0x61]) // data
data.appendLEUInt32(dataSize)
for sample in samples {
let clamped = max(-1.0, min(1.0, sample))
let scaled = Int16((clamped * Float(Int16.max)).rounded())
data.appendLEInt16(scaled)
private static func run(_ process: Process) async throws -> Int32 {
try await withCheckedThrowingContinuation { continuation in
process.terminationHandler = { process in
continuation.resume(returning: process.terminationStatus)
}
do {
try process.run()
} catch {
continuation.resume(throwing: error)
}
}
return data
}
private static func readPipe(_ pipe: Pipe) -> String {
let data = (try? pipe.fileHandleForReading.readToEnd()) ?? Data()
let text = String(data: data, encoding: .utf8) ?? ""
return text.trimmingCharacters(in: .whitespacesAndNewlines)
}
}
extension TalkMLXSpeechSynthesizer: @unchecked Sendable {}
private struct UncheckedSpeechModel {
let raw: any SpeechGenerationModel
func sampleRateValue() -> Int {
raw.sampleRate
}
func generateAudio(
text: String,
voice: String?,
language: String?) async throws -> [Float] {
let generatedAudio = try await raw.generate(
text: text,
voice: voice,
refAudio: nil,
refText: nil,
language: language)
return generatedAudio.asArray(Float.self)
}
}
extension UncheckedSpeechModel: @unchecked Sendable {}
extension Data {
fileprivate mutating func appendLEUInt16(_ value: UInt16) {
var littleEndian = value.littleEndian
Swift.withUnsafeBytes(of: &littleEndian) { append(contentsOf: $0) }
}
fileprivate mutating func appendLEUInt32(_ value: UInt32) {
var littleEndian = value.littleEndian
Swift.withUnsafeBytes(of: &littleEndian) { append(contentsOf: $0) }
}
fileprivate mutating func appendLEInt16(_ value: Int16) {
var littleEndian = value.littleEndian
Swift.withUnsafeBytes(of: &littleEndian) { append(contentsOf: $0) }
}
}
// swiftformat:enable wrap wrapMultilineStatementBraces trailingCommas redundantSelf extensionAccessControl

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

@@ -9,4 +9,37 @@ struct ExecApprovalCommandDisplaySanitizerTests {
ExecApprovalCommandDisplaySanitizer.sanitize(input) ==
"date\\u{200B}\\u{3164}\\u{FFA0}\\u{115F}\\u{1160}가")
}
@Test func `escapes control characters used to spoof line breaks`() {
let input = "echo safe\n\rcurl https://example.test"
#expect(
ExecApprovalCommandDisplaySanitizer.sanitize(input) ==
"echo safe\\u{A}\\u{D}curl https://example.test")
}
@Test func `escapes Unicode line and paragraph separators`() {
let lineInput = "echo ok\u{2028}curl https://example.test"
#expect(
ExecApprovalCommandDisplaySanitizer.sanitize(lineInput) ==
"echo ok\\u{2028}curl https://example.test")
let paragraphInput = "echo ok\u{2029}curl https://example.test"
#expect(
ExecApprovalCommandDisplaySanitizer.sanitize(paragraphInput) ==
"echo ok\\u{2029}curl https://example.test")
}
@Test func `escapes non-ASCII Unicode space separators while preserving ASCII space`() {
let nbspInput = "echo ok\u{00A0}curl"
#expect(
ExecApprovalCommandDisplaySanitizer.sanitize(nbspInput) == "echo ok\\u{A0}curl")
let narrowNbspInput = "echo ok\u{202F}curl"
#expect(
ExecApprovalCommandDisplaySanitizer.sanitize(narrowNbspInput) == "echo ok\\u{202F}curl")
let ideographicSpaceInput = "echo ok\u{3000}curl"
#expect(
ExecApprovalCommandDisplaySanitizer.sanitize(ideographicSpaceInput) ==
"echo ok\\u{3000}curl")
let asciiSpaceInput = "echo ok curl"
#expect(ExecApprovalCommandDisplaySanitizer.sanitize(asciiSpaceInput) == "echo ok curl")
}
}

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 @@
3c87ac2fc4c234348eb88812d1904724d7492890498f101d953bc761da8fdead config-baseline.json
eeed6fe659078632d9f95b3350b27103b4aba282d050ff38d3b0953a456d242d config-baseline.core.json
99bb34fcf83ba6bb50a3fc11f170bd379bee5728b0938707fc39ebd7638e12eb config-baseline.channel.json
5f5d4e850df6e9854a85b5d008236854ce185c707fdbb566efcf00f8c08b36e3 config-baseline.plugin.json
e93b2f54b4d46da18d853f548658ea4c1d84a9ed391f5e0b44673b43adcc4396 config-baseline.json
7956c319e82d288d496a51cb2ff4485ab72ef4900cb089f99e1df8b9ef3bfb73 config-baseline.core.json
cd467228990cdbdebde2fa87d8b1384b94c149e791f2e67250bf17b13162d4a1 config-baseline.channel.json
a7f297a3461e807fd15f8a7c8c68e41071dfc09af2118c24a26d5f534301a654 config-baseline.plugin.json

View File

@@ -1,2 +1,2 @@
052943a9f1eb82a49452b6715f4c08faeb650d16a36c150a3c726ff392ecad0d plugin-sdk-api-baseline.json
a5077395f009f5064331dc1c38bb2d6d2864299d3c1fbd9e40956c1700fa253c plugin-sdk-api-baseline.jsonl
d7f6e6ecdfb78c73760689af5a684c20ec7ca28509d4f63bf0d990a2d739c6ce plugin-sdk-api-baseline.json
584681e4436a4e84c2ff20196ff194a63915caf4dda70de9c27f34ab0d7bde0b 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

@@ -227,7 +227,7 @@ Completion cleanup is also runtime-aware:
- Isolated cron completion best-effort closes tracked browser tabs/processes for the cron session before the run fully tears down.
- Isolated cron delivery waits out descendant subagent follow-up when needed and
suppresses stale parent acknowledgement text instead of announcing it.
- Subagent completion delivery prefers the latest visible assistant text; if that is empty it falls back to sanitized latest tool/toolResult text, and timeout-only tool-call runs can collapse to a short partial-progress summary.
- Subagent completion delivery prefers the latest visible assistant text; if that is empty it falls back to sanitized latest tool/toolResult text, and timeout-only tool-call runs can collapse to a short partial-progress summary. Terminal failed runs announce failure status without replaying captured reply text.
- Cleanup failures do not mask the real task outcome.
### `tasks flow list|show|cancel`
@@ -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.
@@ -315,7 +363,7 @@ BlueBubbles supports advanced message actions when enabled in config:
Available actions:
- **react**: Add/remove tapback reactions (`messageId`, `emoji`, `remove`)
- **react**: Add/remove tapback reactions (`messageId`, `emoji`, `remove`). iMessage's native tapback set is `love`, `like`, `dislike`, `laugh`, `emphasize`, and `question`. When an agent picks an emoji outside that set (for example `👀`), the reaction tool falls back to `love` so the tapback still renders instead of failing the whole request. Configured ack reactions still validate strictly and error on unknown values.
- **edit**: Edit a sent message (`messageId`, `text`)
- **unsend**: Unsend a message (`messageId`)
- **reply**: Reply to a specific message (`messageId`, `text`, `to`)
@@ -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.
@@ -407,6 +554,10 @@ Prefer `chat_guid` for stable routing:
- Direct handles: `+15555550123`, `user@example.com`
- If a direct handle does not have an existing DM chat, OpenClaw will create one via `POST /api/v1/chat/new`. This requires the BlueBubbles Private API to be enabled.
### iMessage vs SMS routing
When the same handle has both an iMessage and an SMS chat on the Mac (for example a phone number that is iMessage-registered but has also received green-bubble fallbacks), OpenClaw prefers the iMessage chat and never silently downgrades to SMS. To force the SMS chat, use an explicit `sms:` target prefix (for example `sms:+15555550123`). Handles without a matching iMessage chat still send through whatever chat BlueBubbles reports.
## Security
- Webhook requests are authenticated by comparing `guid`/`password` query params or headers against `channels.bluebubbles.password`.
@@ -422,6 +573,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

@@ -593,6 +593,8 @@ Default slash command settings:
- `channels.discord.streamMode` is a legacy alias and is auto-migrated.
- `partial` edits a single preview message as tokens arrive.
- `block` emits draft-sized chunks (use `draftChunk` to tune size and breakpoints).
- Media, error, and explicit-reply finals cancel pending preview edits without flushing a temporary draft before normal delivery.
- `streaming.preview.toolProgress` controls whether tool/progress updates reuse the same draft preview message (default: `true`). Set `false` to keep separate tool/progress messages.
Example:
@@ -1237,7 +1239,7 @@ High-signal Discord fields:
- inbound worker: `inboundWorker.runTimeoutMs`
- reply/history: `replyToMode`, `historyLimit`, `dmHistoryLimit`, `dms.*.historyLimit`
- delivery: `textChunkLimit`, `chunkMode`, `maxLinesPerMessage`
- streaming: `streaming` (legacy alias: `streamMode`), `draftChunk`, `blockStreaming`, `blockStreamingCoalesce`
- streaming: `streaming` (legacy alias: `streamMode`), `streaming.preview.toolProgress`, `draftChunk`, `blockStreaming`, `blockStreamingCoalesce`
- media/retry: `mediaMaxMb`, `retry`
- `mediaMaxMb` caps outbound Discord uploads (default: `100MB`)
- actions: `actions.*`

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).
@@ -408,6 +408,10 @@ The agent system prompt includes a group intro on the first turn of a new group
- List chats: `imsg chats --limit 20`.
- Group replies always go back to the same `chat_id`.
## WhatsApp system prompts
See [WhatsApp](/channels/whatsapp#system-prompts) for the canonical WhatsApp system prompt rules, including group and direct prompt resolution, wildcard behavior, and account override semantics.
## WhatsApp specifics
See [Group messages](/channels/group-messages) for WhatsApp-only behavior (history injection, mention handling details).

View File

@@ -50,7 +50,7 @@ imsg rpc --help
imessage: {
enabled: true,
cliPath: "/usr/local/bin/imsg",
dbPath: "/Users/<you>/Library/Messages/chat.db",
dbPath: "/Users/user/Library/Messages/chat.db",
},
},
}

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

@@ -205,6 +205,8 @@ The LINE plugin supports sending images, videos, and audio files through the age
- **Videos**: sent with explicit preview and content-type handling.
- **Audio**: sent as LINE audio messages.
Outbound media URLs must be public HTTPS URLs. OpenClaw validates the target hostname before handing the URL to LINE and rejects loopback, link-local, and private-network targets.
Generic media sends fall back to the existing image-only route when a LINE-specific path is not available.
## Troubleshooting

View File

@@ -884,6 +884,12 @@ Per-account override:
Related docs: [Exec approvals](/tools/exec-approvals)
## Slash commands
Matrix slash commands (for example `/new`, `/reset`, `/model`) work directly in DMs. In rooms, OpenClaw also recognizes slash commands that are prefixed with the bot's own Matrix mention, so `@bot:server /new` triggers the command path without needing a custom mention regex. This keeps the bot responsive to room-style `@mention /command` posts that Element and similar clients emit when a user tab-completes the bot before typing the command.
Authorization rules still apply: command senders must satisfy DM or room allowlist/owner policies just like plain messages.
## Multi-account
```json5
@@ -1014,7 +1020,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 +1041,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

@@ -244,6 +244,31 @@ Notes:
- Retries apply to transient failures such as rate limits, 5xx responses, and network or timeout errors.
- 4xx client errors other than `429` are treated as permanent and are not retried.
## Preview streaming
Mattermost streams thinking, tool activity, and partial reply text into a single **draft preview post** that finalizes in place when the final answer is safe to send. The preview updates on the same post id instead of spamming the channel with per-chunk messages. Media/error finals cancel pending preview edits and use normal delivery instead of flushing a throwaway preview post.
Enable via `channels.mattermost.streaming`:
```json5
{
channels: {
mattermost: {
streaming: "partial", // off | partial | block | progress
},
},
}
```
Notes:
- `partial` is the usual choice: one preview post that is edited as the reply grows, then finalized with the complete answer.
- `block` uses append-style draft chunks inside the preview post.
- `progress` shows a status preview while generating and only posts the final answer at completion.
- `off` disables preview streaming.
- If the stream cannot be finalized in place (for example the post was deleted mid-stream), OpenClaw falls back to sending a fresh final post so the reply is never lost.
- See [Streaming](/concepts/streaming#preview-streaming-modes) for the channel-mapping matrix.
## Reactions (message tool)
- Use `message action=react` with `channel=mattermost`.

View File

@@ -9,8 +9,6 @@ title: "Microsoft Teams"
> "Abandon all hope, ye who enter here."
Updated: 2026-03-25
Status: text + DM attachments are supported; channel/group file sending requires `sharePointSiteId` + Graph permissions (see [Sending files in group chats](#sending-files-in-group-chats)). Polls are sent via Adaptive Cards. Message actions expose explicit `upload-file` for file-first sends.
## Bundled plugin
@@ -611,7 +609,7 @@ Teams markdown is more limited than Slack or Discord:
- Basic formatting works: **bold**, _italic_, `code`, links
- Complex markdown (tables, nested lists) may not render correctly
- Adaptive Cards are supported for polls and arbitrary card sends (see below)
- Adaptive Cards are supported for polls and semantic presentation sends (see below)
## Configuration
@@ -783,11 +781,11 @@ OpenClaw sends Teams polls as Adaptive Cards (there is no native Teams poll API)
- The gateway must stay online to record votes.
- Polls do not auto-post result summaries yet (inspect the store file if needed).
## Adaptive Cards (arbitrary)
## Presentation Cards
Send any Adaptive Card JSON to Teams users or conversations using the `message` tool or CLI.
Send semantic presentation payloads to Teams users or conversations using the `message` tool or CLI. OpenClaw renders them as Teams Adaptive Cards from the generic presentation contract.
The `card` parameter accepts an Adaptive Card JSON object. When `card` is provided, the message text is optional.
The `presentation` parameter accepts semantic blocks. When `presentation` is provided, the message text is optional.
**Agent tool:**
@@ -796,10 +794,9 @@ The `card` parameter accepts an Adaptive Card JSON object. When `card` is provid
action: "send",
channel: "msteams",
target: "user:<id>",
card: {
type: "AdaptiveCard",
version: "1.5",
body: [{ type: "TextBlock", text: "Hello!" }],
presentation: {
title: "Hello",
blocks: [{ type: "text", text: "Hello!" }],
},
}
```
@@ -809,10 +806,10 @@ The `card` parameter accepts an Adaptive Card JSON object. When `card` is provid
```bash
openclaw message send --channel msteams \
--target "conversation:19:abc...@thread.tacv2" \
--card '{"type":"AdaptiveCard","version":"1.5","body":[{"type":"TextBlock","text":"Hello!"}]}'
--presentation '{"title":"Hello","blocks":[{"type":"text","text":"Hello!"}]}'
```
See [Adaptive Cards documentation](https://adaptivecards.io/) for card schema and examples. For target format details, see [Target formats](#target-formats) below.
For target format details, see [Target formats](#target-formats) below.
## Target formats
@@ -837,9 +834,9 @@ openclaw message send --channel msteams --target "user:John Smith" --message "He
# Send to a group chat or channel
openclaw message send --channel msteams --target "conversation:19:abc...@thread.tacv2" --message "Hello"
# Send an Adaptive Card to a conversation
# Send a presentation card to a conversation
openclaw message send --channel msteams --target "conversation:19:abc...@thread.tacv2" \
--card '{"type":"AdaptiveCard","version":"1.5","body":[{"type":"TextBlock","text":"Hello"}]}'
--presentation '{"title":"Hello","blocks":[{"type":"text","text":"Hello"}]}'
```
**Agent tool examples:**
@@ -858,10 +855,9 @@ openclaw message send --channel msteams --target "conversation:19:abc...@thread.
action: "send",
channel: "msteams",
target: "conversation:19:abc...@thread.tacv2",
card: {
type: "AdaptiveCard",
version: "1.5",
body: [{ type: "TextBlock", text: "Hello" }],
presentation: {
title: "Hello",
blocks: [{ type: "text", text: "Hello" }],
},
}
```

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

@@ -171,16 +171,36 @@ Outbound audio upload/transcode behavior can also be tuned with
Built-in commands intercepted before the AI queue:
| Command | Description |
| -------------- | ------------------------------------ |
| `/bot-ping` | Latency test |
| `/bot-version` | Show the OpenClaw framework version |
| `/bot-help` | List all commands |
| `/bot-upgrade` | Show the QQBot upgrade guide link |
| `/bot-logs` | Export recent gateway logs as a file |
| Command | Description |
| -------------- | -------------------------------------------------------------------------------------------------------- |
| `/bot-ping` | Latency test |
| `/bot-version` | Show the OpenClaw framework version |
| `/bot-help` | List all commands |
| `/bot-upgrade` | Show the QQBot upgrade guide link |
| `/bot-logs` | Export recent gateway logs as a file |
| `/bot-approve` | Approve a pending QQ Bot action (for example, confirming a C2C or group upload) through the native flow. |
Append `?` to any command for usage help (for example `/bot-upgrade ?`).
## Engine architecture
QQ Bot ships as a self-contained engine inside the plugin:
- Each account owns an isolated resource stack (WebSocket connection, API client, token cache, media storage root) keyed by `appId`. Accounts never share inbound/outbound state.
- The multi-account logger tags log lines with the owning account so diagnostics stay separable when you run several bots under one gateway.
- Inbound, outbound, and gateway bridge paths share a single media payload root under `~/.openclaw/media`, so uploads, downloads, and transcode caches land under one guarded directory instead of a per-subsystem tree.
- Credentials can be backed up and restored as part of standard OpenClaw credential snapshots; the engine re-attaches each account's resource stack on restore without requiring a fresh QR-code pair.
## QR-code onboarding
As an alternative to pasting `AppID:AppSecret` manually, the engine supports a QR-code onboarding flow for linking a QQ Bot to OpenClaw:
1. Run the QQ Bot setup path (for example `openclaw channels add --channel qqbot`) and pick the QR-code flow when prompted.
2. Scan the generated QR code with the phone app tied to the target QQ Bot.
3. Approve the pairing on the phone. OpenClaw persists the returned credentials into `credentials/` under the right account scope.
Approval prompts generated by the bot itself (for example, "allow this action?" flows exposed by the QQ Bot API) surface as native OpenClaw prompts that you can accept with `/bot-approve` rather than replying through the raw QQ client.
## Troubleshooting
- **Bot replies "gone to Mars":** credentials not configured or Gateway not started.

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"
},
{
@@ -734,6 +734,7 @@ Notes:
- `partial` (default): replace preview text with the latest partial output.
- `block`: append chunked preview updates.
- `progress`: show progress status text while generating, then send final text.
- `streaming.preview.toolProgress`: when draft preview is active, route tool/progress updates into the same edited preview message (default: `true`). Set `false` to keep separate tool/progress messages.
`channels.slack.streaming.nativeTransport` controls Slack native text streaming when `channels.slack.streaming.mode` is `partial` (default: `true`).
@@ -741,6 +742,7 @@ Notes:
- Channel and group-chat roots can still use the normal draft preview when native streaming is unavailable.
- Top-level Slack DMs stay off-thread by default, so they do not show the thread-style preview; use thread replies or `typingReaction` if you want visible progress there.
- Media and non-text payloads fall back to normal delivery.
- Media/error finals cancel pending preview edits without flushing a temporary draft; eligible text/block finals flush only when they can edit the preview in place.
- If streaming fails mid-reply, OpenClaw falls back to normal delivery for remaining payloads.
Use draft preview instead of Slack native text streaming:
@@ -971,7 +973,7 @@ Primary reference:
- compatibility toggle: `dangerouslyAllowNameMatching` (break-glass; keep off unless needed)
- channel access: `groupPolicy`, `channels.*`, `channels.*.users`, `channels.*.requireMention`
- threading/history: `replyToMode`, `replyToModeByChatType`, `thread.*`, `historyLimit`, `dmHistoryLimit`, `dms.*.historyLimit`
- delivery: `textChunkLimit`, `chunkMode`, `mediaMaxMb`, `streaming`, `streaming.nativeTransport`
- delivery: `textChunkLimit`, `chunkMode`, `mediaMaxMb`, `streaming`, `streaming.nativeTransport`, `streaming.preview.toolProgress`
- ops/features: `configWrites`, `commands.native`, `slashCommand.*`, `actions.*`, `userToken`, `userTokenReadOnly`
## Troubleshooting

View File

@@ -113,6 +113,7 @@ openclaw message send --channel synology-chat --target synology-chat:123456 --te
```
Media sends are supported by URL-based file delivery.
Outbound file URLs must use `http` or `https`, and private or otherwise blocked network targets are rejected before OpenClaw forwards the URL to the NAS webhook.
## Multi-account

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
@@ -274,6 +275,7 @@ curl "https://api.telegram.org/bot<bot_token>/getUpdates"
- `channels.telegram.streaming` is `off | partial | block | progress` (default: `partial`)
- `progress` maps to `partial` on Telegram (compat with cross-channel naming)
- `streaming.preview.toolProgress` controls whether tool/progress updates reuse the same edited preview message (default: `true`). Set `false` to keep separate tool/progress messages.
- legacy `channels.telegram.streamMode` and boolean `streaming` values are auto-mapped
For text-only replies:
@@ -766,6 +768,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.
@@ -800,7 +803,8 @@ openclaw message poll --channel telegram --target -1001234567890:topic:42 \
Telegram send also supports:
- `--buttons` for inline keyboards when `channels.telegram.capabilities.inlineButtons` allows it
- `--presentation` with `buttons` blocks for inline keyboards when `channels.telegram.capabilities.inlineButtons` allows it
- `--pin` or `--delivery '{"pin":true}'` to request pinned delivery when the bot can pin in that chat
- `--force-document` to send outbound images and GIFs as documents instead of compressed photo or animated-media uploads
Action gating:
@@ -917,6 +921,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
@@ -1024,6 +1030,7 @@ Primary reference:
- `channels.telegram.chunkMode`: `length` (default) or `newline` to split on blank lines (paragraph boundaries) before length chunking.
- `channels.telegram.linkPreview`: toggle link previews for outbound messages (default: true).
- `channels.telegram.streaming`: `off | partial | block | progress` (live stream preview; default: `partial`; `progress` maps to `partial`; `block` is legacy preview mode compatibility). Telegram preview streaming uses a single preview message that is edited in place.
- `channels.telegram.streaming.preview.toolProgress`: reuse the live preview message for tool/progress updates when preview streaming is active (default: `true`). Set `false` to keep separate tool/progress messages.
- `channels.telegram.mediaMaxMb`: inbound/outbound Telegram media cap (MB, default: 100).
- `channels.telegram.retry`: retry policy for Telegram send helpers (CLI/tools/actions) on recoverable outbound API errors (attempts, minDelayMs, maxDelayMs, jitter).
- `channels.telegram.network.autoSelectFamily`: override Node autoSelectFamily (true=enable, false=disable). Defaults to enabled on Node 22+, with WSL2 defaulting to disabled.
@@ -1053,9 +1060,9 @@ Telegram-specific high-signal fields:
- exec approvals: `execApprovals`, `accounts.*.execApprovals`
- command/menu: `commands.native`, `commands.nativeSkills`, `customCommands`
- threading/replies: `replyToMode`
- streaming: `streaming` (preview), `blockStreaming`
- streaming: `streaming` (preview), `streaming.preview.toolProgress`, `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
@@ -38,21 +39,22 @@ Healthy baseline:
| Group messages ignored | Check `requireMention` + mention patterns in config | Mention the bot or relax mention policy for that group. |
| Random disconnect/relogin loops | `openclaw channels status --probe` + logs | Re-login and verify credentials directory is healthy. |
Full troubleshooting: [/channels/whatsapp#troubleshooting](/channels/whatsapp#troubleshooting)
Full troubleshooting: [WhatsApp troubleshooting](/channels/whatsapp#troubleshooting)
## Telegram
### 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)
Full troubleshooting: [Telegram troubleshooting](/channels/telegram#troubleshooting)
## Discord
@@ -64,7 +66,7 @@ Full troubleshooting: [/channels/telegram#troubleshooting](/channels/telegram#tr
| Group messages ignored | Check logs for mention gating drops | Mention bot or set guild/channel `requireMention: false`. |
| DM replies missing | `openclaw pairing list discord` | Approve DM pairing or adjust DM policy. |
Full troubleshooting: [/channels/discord#troubleshooting](/channels/discord#troubleshooting)
Full troubleshooting: [Discord troubleshooting](/channels/discord#troubleshooting)
## Slack
@@ -76,7 +78,7 @@ Full troubleshooting: [/channels/discord#troubleshooting](/channels/discord#trou
| DMs blocked | `openclaw pairing list slack` | Approve pairing or relax DM policy. |
| Channel message ignored | Check `groupPolicy` and channel allowlist | Allow the channel or switch policy to `open`. |
Full troubleshooting: [/channels/slack#troubleshooting](/channels/slack#troubleshooting)
Full troubleshooting: [Slack troubleshooting](/channels/slack#troubleshooting)
## iMessage and BlueBubbles
@@ -90,8 +92,8 @@ Full troubleshooting: [/channels/slack#troubleshooting](/channels/slack#troubles
Full troubleshooting:
- [/channels/imessage#troubleshooting](/channels/imessage#troubleshooting)
- [/channels/bluebubbles#troubleshooting](/channels/bluebubbles#troubleshooting)
- [iMessage troubleshooting](/channels/imessage#troubleshooting)
- [BlueBubbles troubleshooting](/channels/bluebubbles#troubleshooting)
## Signal
@@ -103,7 +105,7 @@ Full troubleshooting:
| DM blocked | `openclaw pairing list signal` | Approve sender or adjust DM policy. |
| Group replies do not trigger | Check group allowlist and mention patterns | Add sender/group or loosen gating. |
Full troubleshooting: [/channels/signal#troubleshooting](/channels/signal#troubleshooting)
Full troubleshooting: [Signal troubleshooting](/channels/signal#troubleshooting)
## QQ Bot
@@ -116,7 +118,7 @@ Full troubleshooting: [/channels/signal#troubleshooting](/channels/signal#troubl
| Voice not transcribed | Check STT provider config | Configure `channels.qqbot.stt` or `tools.media.audio`. |
| Proactive messages not arriving | Check QQ platform interaction requirements | QQ may block bot-initiated messages without recent interaction. |
Full troubleshooting: [/channels/qqbot#troubleshooting](/channels/qqbot#troubleshooting)
Full troubleshooting: [QQ Bot troubleshooting](/channels/qqbot#troubleshooting)
## Matrix

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

@@ -465,6 +465,75 @@ Behavior notes:
</Accordion>
</AccordionGroup>
## System prompts
WhatsApp supports Telegram-style system prompts for groups and direct chats via the `groups` and `direct` maps.
Resolution hierarchy for group messages:
The effective `groups` map is determined first: if the account defines its own `groups`, it fully replaces the root `groups` map (no deep merge). Prompt lookup then runs on the resulting single map:
1. **Group-specific system prompt** (`groups["<groupId>"].systemPrompt`): used if the specific group entry defines a `systemPrompt`.
2. **Group wildcard system prompt** (`groups["*"].systemPrompt`): used when the specific group entry is absent or defines no `systemPrompt`.
Resolution hierarchy for direct messages:
The effective `direct` map is determined first: if the account defines its own `direct`, it fully replaces the root `direct` map (no deep merge). Prompt lookup then runs on the resulting single map:
1. **Direct-specific system prompt** (`direct["<peerId>"].systemPrompt`): used if the specific peer entry defines a `systemPrompt`.
2. **Direct wildcard system prompt** (`direct["*"].systemPrompt`): used when the specific peer entry is absent or defines no `systemPrompt`.
Note: `dms` remains the lightweight per-DM history override bucket (`dms.<id>.historyLimit`); prompt overrides live under `direct`.
**Difference from Telegram multi-account behavior:** In Telegram, root `groups` is intentionally suppressed for all accounts in a multi-account setup — even accounts that define no `groups` of their own — to prevent a bot from receiving group messages for groups it does not belong to. WhatsApp does not apply this guard: root `groups` and root `direct` are always inherited by accounts that define no account-level override, regardless of how many accounts are configured. In a multi-account WhatsApp setup, if you want per-account group or direct prompts, define the full map under each account explicitly rather than relying on root-level defaults.
Important behavior:
- `channels.whatsapp.groups` is both a per-group config map and the chat-level group allowlist. At either the root or account scope, `groups["*"]` means "all groups are admitted" for that scope.
- Only add a wildcard group `systemPrompt` when you already want that scope to admit all groups. If you still want only a fixed set of group IDs to be eligible, do not use `groups["*"]` for the prompt default. Instead, repeat the prompt on each explicitly allowlisted group entry.
- Group admission and sender authorization are separate checks. `groups["*"]` widens the set of groups that can reach group handling, but it does not by itself authorize every sender in those groups. Sender access is still controlled separately by `channels.whatsapp.groupPolicy` and `channels.whatsapp.groupAllowFrom`.
- `channels.whatsapp.direct` does not have the same side effect for DMs. `direct["*"]` only provides a default direct-chat config after a DM is already admitted by `dmPolicy` plus `allowFrom` or pairing-store rules.
Example:
```json5
{
channels: {
whatsapp: {
groups: {
// Use only if all groups should be admitted at the root scope.
// Applies to all accounts that do not define their own groups map.
"*": { systemPrompt: "Default prompt for all groups." },
},
direct: {
// Applies to all accounts that do not define their own direct map.
"*": { systemPrompt: "Default prompt for all direct chats." },
},
accounts: {
work: {
groups: {
// This account defines its own groups, so root groups are fully
// replaced. To keep a wildcard, define "*" explicitly here too.
"120363406415684625@g.us": {
requireMention: false,
systemPrompt: "Focus on project management.",
},
// Use only if all groups should be admitted in this account.
"*": { systemPrompt: "Default prompt for work groups." },
},
direct: {
// This account defines its own direct map, so root direct entries are
// fully replaced. To keep a wildcard, define "*" explicitly here too.
"+15551234567": { systemPrompt: "Prompt for a specific work direct chat." },
"*": { systemPrompt: "Default prompt for work direct chats." },
},
},
},
},
},
}
```
## Configuration reference pointers
Primary reference:
@@ -478,6 +547,7 @@ High-signal WhatsApp fields:
- multi-account: `accounts.<id>.enabled`, `accounts.<id>.authDir`, account-level overrides
- operations: `configWrites`, `debounceMs`, `web.enabled`, `web.heartbeatSeconds`, `web.reconnect.*`
- session behavior: `session.dmScope`, `historyLimit`, `dmHistoryLimit`, `dms.<id>.historyLimit`
- prompts: `groups.<id>.systemPrompt`, `groups["*"].systemPrompt`, `direct.<id>.systemPrompt`, `direct["*"].systemPrompt`
## Related

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. Release metadata-only version bumps run targeted version/config/root-dependency checks. 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` |
| `blacksmith-12vcpu-macos-latest` | `macos-node`, `macos-swift` on `openclaw/openclaw`; forks fall back to `macos-latest` |
## 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)

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