Compare commits

..

2746 Commits

Author SHA1 Message Date
Josh Lehman
53ac244ec6 Merge branch 'main' into codex/plugin-command-scope-auth 2026-03-26 16:15:50 -07:00
Peter Steinberger
d1d0887932 refactor: remove legacy browser bridge entrypoints 2026-03-26 23:11:17 +00:00
Josh Lehman
c94f10b915 Plugins: harden dynamic scope resolution
Catch dynamic gateway-scope resolver failures in the dispatcher, narrow
forwarded gateway scope strings with an explicit operator-scope guard, add
regression coverage for admin bypass and resolver-throw behavior, and
refresh bundled plugin metadata after main-branch drift.

Regeneration-Prompt: |
  Follow up on review feedback for the centralized plugin command auth
  change. Keep the scope tightly limited to the three review items:
  catch exceptions from `resolveRequiredGatewayScopes`, replace the raw
  `GatewayClientScopes` cast with explicit operator-scope narrowing, and
  add dispatcher-level tests for the `operator.admin` bypass plus the safe
  failure path when dynamic scope resolution throws.

  While landing that patch, the repo hook may report stale bundled plugin
  metadata generated files because main advanced. Regenerate those standard
  outputs with the repo generator so the branch is consistent enough to
  rebase, but do not chase unrelated CI or Discord test failures here.
2026-03-26 16:11:09 -07:00
Josh Lehman
70b43319ff Plugin SDK: refresh API baselines for auth context change
Update the generated Plugin SDK API baseline files after extending plugin
command types for centralized owner and gateway-scope authorization.

Regeneration-Prompt: |
  The prior commit intentionally changed exported plugin SDK types in
  `src/plugins/types.ts` by adding richer plugin command auth context and
  declarative command requirement fields. CI reported plugin SDK API drift,
  which means the generated baseline files under `docs/.generated/` no
  longer matched the exported surface.

  Regenerate only the plugin SDK API baseline artifacts with the repo's
  standard generator, verify `pnpm plugin-sdk:api:check` passes, and keep
  this follow-up scoped to those generated files. Do not fold in unrelated
  failing tests from untouched surfaces.
2026-03-26 16:11:09 -07:00
Peter Steinberger
4b40d4dfa8 perf: optimize cold import paths 2026-03-26 23:11:00 +00:00
Josh Lehman
487f752754 Plugins: centralize plugin command auth requirements
Move plugin command authorization toward the GHSA's long-term model by
preserving richer auth context, supporting declarative owner and gateway
scope requirements, and enforcing them in the shared dispatcher. Convert
`/pair approve` to use the centralized requirement path and add regression
coverage for dispatcher-level auth behavior.

Regeneration-Prompt: |
  This follow-up hardening is for the plugin command auth gap described in
  GHSA-9gwp-pxfh-w6r5. The immediate exploit path was already fixed by
  plumbing gateway scopes into the device-pair plugin and checking `/pair
  approve` inline, but the longer-term goal is to stop relying on lossy,
  plugin-specific auth checks.

  Preserve the existing plugin command flow and keep the change additive.
  Carry richer authorization context into plugin execution, including owner
  status and command surface, and let commands declare owner or internal
  gateway-scope requirements that the central dispatcher enforces. Internal
  callers should fail closed when required scopes are missing, with admin
  scope still satisfying narrower operator requirements, while non-internal
  chat surfaces should keep their current auth behavior.

  Because `/pair` mixes low-risk actions like `qr` and `status` with the
  privileged `approve` action, use a context-sensitive requirement instead
  of making the whole command require pairing scope. Add focused regression
  tests around dispatcher enforcement and update any command-context test
  helpers that now need the richer fields.
2026-03-26 16:10:38 -07:00
Peter Steinberger
15181b3a77 docs(anthropic): dedupe config heading 2026-03-26 23:08:26 +00:00
Peter Steinberger
5f2876911a fix: harden discord gateway cleanup (#55373) (thanks @Takhoffman) 2026-03-26 16:07:13 -07:00
Tak Hoffman
a79c9d50f7 fix(discord): guard gateway cleanup races 2026-03-26 16:07:13 -07:00
Peter Steinberger
f406b20e50 chore(docs): refresh generated baselines 2026-03-26 23:05:59 +00:00
Peter Steinberger
eef27001de docs: explain anthropic claude cli migration 2026-03-26 23:04:47 +00:00
Peter Steinberger
ebf5bd75f4 feat: add anthropic claude cli migration 2026-03-26 23:04:47 +00:00
Peter Steinberger
b96fccadb9 refactor: clean memory plugin host boundary 2026-03-26 23:02:24 +00:00
Peter Steinberger
556ce5cdda test(browser): fix CI after compat re-exports 2026-03-26 22:59:50 +00:00
Peter Steinberger
09c186d5f9 refactor: remove browser compat shadow tree 2026-03-26 22:53:37 +00:00
Peter Steinberger
d72115c9df refactor: genericize speech provider config surface 2026-03-26 22:48:57 +00:00
Peter Steinberger
83ca6fbfc6 refactor: finish browser compat untangle 2026-03-26 22:42:41 +00:00
Peter Steinberger
8ee809f3cc refactor: share plugin entry exports 2026-03-26 22:38:13 +00:00
Peter Steinberger
8df6134a1b refactor: share usage metrics timeslice walker 2026-03-26 22:38:13 +00:00
Peter Steinberger
ff47ad58fc refactor: share config path traversal helper 2026-03-26 22:38:13 +00:00
Peter Steinberger
5445bc68b9 refactor: share tts auto mode normalization 2026-03-26 22:38:13 +00:00
Seungwoo hong
138a92373b fix(talk): prevent double TTS playback when system voice times out (#53511)
Merged via squash.

Prepared head SHA: 864d556fa6
Co-authored-by: hongsw <1100974+hongsw@users.noreply.github.com>
Co-authored-by: grp06 <1573959+grp06@users.noreply.github.com>
Reviewed-by: @grp06
2026-03-26 15:37:40 -07:00
Peter Steinberger
0f5a77d058 refactor: route memory runtime through memory plugin 2026-03-26 22:30:47 +00:00
Peter Steinberger
2c6d099b01 refactor: plugin-own speech provider config 2026-03-26 22:28:24 +00:00
Peter Steinberger
8eeb7f0829 refactor: switch browser ownership to bundled plugin 2026-03-26 22:20:40 +00:00
Peter Steinberger
197510f693 refactor: add browser plugin runtime package 2026-03-26 22:20:39 +00:00
Peter Steinberger
1619090693 refactor: move memory plugin state into plugin host 2026-03-26 22:15:49 +00:00
Peter Steinberger
00aedb3414 refactor: split claude cli history import pipeline 2026-03-26 22:12:16 +00:00
Peter Steinberger
d0ce2d1044 refactor: split memory-core plugin helpers 2026-03-26 22:06:06 +00:00
Peter Steinberger
9dea807b28 test: share planner and sandbox test helpers 2026-03-26 22:03:18 +00:00
Peter Steinberger
672a24cbde fix: unify claude cli imported tool messages 2026-03-26 22:02:26 +00:00
Peter Steinberger
3d0050c306 refactor: add memory-core extension sources 2026-03-26 22:00:13 +00:00
Peter Steinberger
e955d574b2 refactor: move memory tooling into memory-core extension 2026-03-26 22:00:13 +00:00
Peter Steinberger
e0dfc776bb refactor: move memory flush ownership into memory plugin 2026-03-26 22:00:13 +00:00
Peter Steinberger
48a65f7749 refactor: simplify bundled plugin contracts 2026-03-26 21:55:41 +00:00
Peter Steinberger
8b42ad08e5 perf: speed up shared extension test batches 2026-03-26 21:51:25 +00:00
Peter Steinberger
29069bd250 refactor: share speech normalization helpers 2026-03-26 21:49:20 +00:00
Peter Steinberger
ce9dff1458 refactor: clean plugin capability boundaries 2026-03-26 21:41:49 +00:00
Tak Hoffman
d00dc5f46b fix(ci): repair discord and telegram follow-ups 2026-03-26 16:33:05 -05:00
Peter Steinberger
53f90af990 test: dedupe telegram polling session harness 2026-03-26 21:30:28 +00:00
Peter Steinberger
2a04053854 fix: skip cli backends in models auth warnings 2026-03-26 21:28:28 +00:00
Peter Steinberger
98ea8e244f fix: backfill claude cli chat history 2026-03-26 21:25:35 +00:00
Peter Steinberger
6aa9bec8d7 fix: export shared channel action enum helpers 2026-03-26 21:21:44 +00:00
Peter Steinberger
0c0f1e34cb refactor: split telegram polling and sdk surfaces 2026-03-26 21:13:16 +00:00
Tak Hoffman
0805078118 fix(ci): format discord provider follow-up 2026-03-26 16:05:25 -05:00
Tak Hoffman
aeee72426d fix(ci): restore discord provider test seams 2026-03-26 15:59:51 -05:00
Peter Steinberger
37894d0f1a test: dedupe discord provider proxy overrides 2026-03-26 20:43:02 +00:00
Lyle Hopkins
eb328a85e3 fix(agents): classify "Failed to extract accountId from token" as auth error for failover (#27055) (#55206)
Co-authored-by: Lyle Hopkins <55105+cosmicnet@users.noreply.github.com>
2026-03-26 23:42:38 +03:00
Tak Hoffman
53d3b8e92d fix(ci): clean up discord harness types 2026-03-26 15:39:20 -05:00
Tak Hoffman
a39e57a1bd fix(ci): repair discord harness regressions 2026-03-26 15:39:20 -05:00
Peter Steinberger
3f54076d37 refactor: dedupe cli runner session reuse 2026-03-26 20:35:19 +00:00
Peter Steinberger
61d29efc04 test: eliminate remaining clone seams 2026-03-26 20:28:36 +00:00
Peter Steinberger
5841e5fdf8 test: split cli agent command coverage 2026-03-26 20:25:20 +00:00
Peter Steinberger
12100719b8 fix: preserve cli sessions across model changes 2026-03-26 20:25:20 +00:00
Peter Steinberger
236e041ef9 test: share discord monitor fixtures 2026-03-26 20:12:21 +00:00
pkuGeo
e035a0d98c telegram: rebuild transport after stalled polling cycles 2026-03-26 13:11:15 -07:00
Peter Steinberger
663ba5a3cd perf: speed up test parallelism 2026-03-26 20:09:40 +00:00
Peter Steinberger
2fc017788c test: reduce remaining clone seams 2026-03-26 20:01:01 +00:00
Tak Hoffman
b20ae13c6b fix(ci): repair discord message handler tests 2026-03-26 14:49:06 -05:00
Peter Steinberger
be328e6cd1 test: dedupe extension channel fixtures 2026-03-26 19:47:27 +00:00
Peter Steinberger
e8f9d68bec test: share cli command and discord test helpers 2026-03-26 19:37:14 +00:00
Peter Steinberger
b48df79c0a test(gateway): strip MiniMax live scaffolding 2026-03-26 19:35:03 +00:00
Tak Hoffman
53f15afade fix(ci): repair discord regression tests 2026-03-26 14:23:57 -05:00
Peter Steinberger
ef381743d8 test: share cli and doctor test helpers 2026-03-26 19:16:43 +00:00
Peter Steinberger
ab4de18982 fix: auto-load bundled plugin capabilities from config refs 2026-03-26 19:15:56 +00:00
Peter Steinberger
8f1716ae5a refactor: share slack and telegram action helpers 2026-03-26 19:07:35 +00:00
Peter Steinberger
a1a9819be8 refactor: dedupe gateway session resolve visibility 2026-03-26 18:56:55 +00:00
Peter Steinberger
4069844795 refactor: share discord outbound session routing 2026-03-26 18:51:02 +00:00
Peter Steinberger
e774fe1286 refactor: share browser and sandbox helpers 2026-03-26 18:43:57 +00:00
Tak Hoffman
2b6375faf9 fix: keep spawned session owners in live events 2026-03-26 13:41:46 -05:00
Tak Hoffman
1062a048eb fix: expose spawned session owners in sessions list 2026-03-26 13:41:46 -05:00
Tak Hoffman
c041fcc04d fix: expose parent session keys in sessions list 2026-03-26 13:41:46 -05:00
Tak Hoffman
cb46b08efc fix: include dashboard children in owner filters 2026-03-26 13:41:46 -05:00
Tak Hoffman
c48a3e4fc9 ci: optimize windows test shard fanout (#55261)
* ci: reduce windows test shard fanout

* ci: tighten windows shard target

* ci: back off windows shard target

* ci: restore windows shard cap
2026-03-26 13:40:28 -05:00
Peter Steinberger
cca577a0cc refactor: share plugin setup helpers 2026-03-26 18:34:51 +00:00
Peter Steinberger
c98addeadd test: share auto-reply typing helpers 2026-03-26 18:27:13 +00:00
Peter Steinberger
1f740ff099 test: share cli and channel setup fixtures 2026-03-26 18:14:44 +00:00
Jacob Tomlinson
02cf12371f Gateway: require requester ownership for HTTP session kills (#55308) 2026-03-26 18:13:36 +00:00
Peter Steinberger
f29c1206cd test: dedupe extension channel fixtures 2026-03-26 17:59:05 +00:00
Peter Steinberger
48167a69b9 refactor: dedupe gateway and binding helpers 2026-03-26 17:49:19 +00:00
Jacob Tomlinson
1c45123231 Gateway: align HTTP session history scopes (#55285)
* Gateway: require scopes for HTTP session history

* Gateway: cover missing HTTP history scope header
2026-03-26 17:43:57 +00:00
Jacob Tomlinson
f8c9863078 bluebubbles: honor reaction mention gating (#55283) 2026-03-26 17:42:19 +00:00
Peter Steinberger
e7e4fbcab9 test: dedupe secrets and guardrail fixtures 2026-03-26 17:39:58 +00:00
Jacob Tomlinson
d3d8e316bd gateway: require pairing for backend scope upgrades (#55286) 2026-03-26 17:36:44 +00:00
Jacob Tomlinson
b5d785f1a5 Gateway: require caller scope for subagent session deletion (#55281) 2026-03-26 17:34:09 +00:00
Jacob Tomlinson
ec2dbcff9a fix: keep plugin HTTP runtime scopes least-privileged (#55284) 2026-03-26 17:28:30 +00:00
Tak Hoffman
21a679e567 fix(ci): refresh plugin sdk api baseline 2026-03-26 12:18:26 -05:00
Peter Steinberger
07c41301e3 style: normalize ui slash executor formatting 2026-03-26 17:09:21 +00:00
Peter Steinberger
d6f7de392c test: dedupe ui chat seams 2026-03-26 17:07:27 +00:00
Peter Steinberger
7bb95354c4 test: dedupe matrix setup seams 2026-03-26 17:04:23 +00:00
Peter Steinberger
c12623a857 test: share plugin auth and ui storage fixtures 2026-03-26 16:55:20 +00:00
Peter Steinberger
d748ea9361 docs: note guest openclaw shim in parallels skill 2026-03-26 16:49:52 +00:00
Peter Steinberger
f0991aab57 test: add docker cli-backend smoke 2026-03-26 16:49:52 +00:00
Peter Steinberger
e1f0a85128 refactor: share auto-reply reply helpers 2026-03-26 16:48:34 +00:00
Tak Hoffman
615fe4a06b fix: preserve reset cli session linkage 2026-03-26 11:46:47 -05:00
Tak Hoffman
22f9c19a39 fix: preserve reset acp session metadata 2026-03-26 11:46:47 -05:00
Tak Hoffman
74b0a948e3 fix: preserve reset channel identity 2026-03-26 11:46:47 -05:00
Tak Hoffman
cb0a752156 fix: preserve reset session behavior config 2026-03-26 11:46:47 -05:00
Peter Steinberger
99d052a203 perf: overlap isolated channel runs with shared lane 2026-03-26 16:45:08 +00:00
Peter Steinberger
d5acd7dee5 test: share ui reconnect and storage helpers 2026-03-26 16:41:51 +00:00
Peter Steinberger
03ea6953e0 test: share gateway authz and watchdog fixtures 2026-03-26 16:36:03 +00:00
Peter Steinberger
d9a7dcec4b test: share matrix migration fixtures 2026-03-26 16:25:23 +00:00
Jacob Tomlinson
c2c136ae95 telegram: throttle repeated webhook auth guesses (#55142)
* telegram: throttle repeated webhook auth guesses

* telegram: use per-listener webhook rate limits

* config: stabilize doc baseline ordering
2026-03-26 16:19:31 +00:00
Peter Steinberger
a92fbf7d40 test: dedupe remaining agent test seams 2026-03-26 16:14:45 +00:00
Peter Steinberger
880b2fb7fd perf: enable local channel planner parallelism on node 25 2026-03-26 16:06:09 +00:00
Peter Steinberger
bac603a63e test: share subagent and policy test fixtures 2026-03-26 16:04:34 +00:00
Tak Hoffman
22520a2058 fix: preserve reset spawn context 2026-03-26 10:57:42 -05:00
Tak Hoffman
8c6be29454 fix: preserve reset elevated level 2026-03-26 10:51:01 -05:00
Tak Hoffman
b04ec4bada ci: make docker release tag-driven 2026-03-26 10:47:01 -05:00
Peter Steinberger
4ed5895637 test: dedupe config compatibility fixtures 2026-03-26 15:45:14 +00:00
Tak Hoffman
6bdf5e5634 fix: preserve reset spawn depth 2026-03-26 10:42:12 -05:00
Peter Steinberger
c4048aea41 test: share msteams monitor and pi runner fixtures 2026-03-26 15:40:51 +00:00
Peter Steinberger
339cc33cf8 perf: speed up channel test runs 2026-03-26 15:40:01 +00:00
Tak Hoffman
06b4a0a1f2 test: improve test runner help text (#55227)
* test: improve test runner help text

* test: print extension help to stdout

* test: leave extension help passthrough alone

* test: parse timing update flags in one pass
2026-03-26 10:34:14 -05:00
Tak Hoffman
471da49c59 fix: preserve reset ownership metadata 2026-03-26 10:32:09 -05:00
Jacob Tomlinson
0b4d073374 synology-chat: throttle webhook token guesses (#55141)
* synology-chat: throttle webhook token guesses

* synology-chat: keep valid webhook traffic within configured limits

* docs: refresh generated config baseline

* synology-chat: enforce lockout after repeated token failures
2026-03-26 15:30:06 +00:00
Peter Steinberger
9bc3d33b53 test: dedupe web search provider fixtures 2026-03-26 15:26:11 +00:00
Tak Hoffman
df04ca7da3 fix: preserve metadata on voice session touches 2026-03-26 10:25:18 -05:00
Peter Steinberger
65a1afb9df test: share redact and approval fixtures 2026-03-26 15:23:12 +00:00
Peter Steinberger
5e78232bc5 test: share pi compaction fixtures 2026-03-26 15:19:32 +00:00
Tak Hoffman
d69ff3c022 fix(whatsapp): unwrap quoted wrapper messages 2026-03-26 10:16:33 -05:00
Peter Steinberger
f56a25a596 test: dedupe foundry auth fixtures 2026-03-26 15:14:03 +00:00
Peter Steinberger
a4a00aa1da feat: pluginize cli inference backends 2026-03-26 15:11:15 +00:00
Tak Hoffman
24dd7aec90 fix: prefer freshest duplicate store matches 2026-03-26 10:10:05 -05:00
Peter Steinberger
5f9f08394a refactor: share matrix and telegram dedupe helpers 2026-03-26 15:08:45 +00:00
Ayaan Zaidi
4b1c37a152 fix: avoid duplicate ACP Telegram finals (#55173)
* fix: avoid duplicate final ACP text on telegram

* fix: keep ACP final fallback for non-telegram blocks

* fix: count telegram ACP block replies as success

* fix: recover ACP final fallback after block failures

* fix: settle telegram ACP block delivery before fallback

* test: isolate ACP dispatch mocks under shared workers

* fix: prefer telegram provider for ACP visibility
2026-03-26 20:37:21 +05:30
Peter Steinberger
2ed11a375a refactor: share web media loader 2026-03-26 14:55:32 +00:00
Jacob Tomlinson
5e08ce36d5 fix(bluebubbles): throttle webhook auth guesses (#55133)
* fix(bluebubbles): throttle webhook auth guesses

* test(bluebubbles): isolate attachment ssrf config

* test(bluebubbles): hoist attachment mocks

* docs: refresh bluebubbles config baseline

* fix(bluebubbles): trust proxied webhook client IPs

* fix(bluebubbles): honor trusted proxy webhook IPs

* fix(bluebubbles): honor real-ip fallback for webhooks
2026-03-26 14:54:03 +00:00
Peter Steinberger
5c3e018492 refactor: dedupe msteams graph actions 2026-03-26 14:45:53 +00:00
Tak Hoffman
a4e5b23dc3 docs: update PR template review guidance 2026-03-26 09:36:36 -05:00
Tak Hoffman
9f0305420a docs: add beta blocker contributor guidance (#55199)
* docs: add beta blocker contributor guidance

* fix: tighten beta blocker labeling and flaky config test
2026-03-26 09:31:59 -05:00
Tak Hoffman
e403899cc1 test: fix portable stderr capture and env leakage (#55184) 2026-03-26 09:31:08 -05:00
Tak Hoffman
dd46c3d75b test(memory): initialize providers in lazy manager tests 2026-03-26 09:29:07 -05:00
Tyler Yust
2513a8d852 fix(bluebubbles): refactor sendMessageBlueBubbles to use resolveBlueBubblesServerAccount and enhance private network handling in tests 2026-03-26 07:21:48 -07:00
Jacob Tomlinson
81c45976db Feishu: reject legacy raw card command payloads (#55130)
* Feishu: reject legacy raw card callbacks

* Feishu: cover legacy text card payloads

* Docs: refresh config baseline

* CI: refresh PR checks

* Feishu: limit legacy card guard scope
2026-03-26 14:17:45 +00:00
Jacob Tomlinson
11ea1f6786 Google Chat: require stable group ids (#55131)
* Google Chat: require stable group ids

* Google Chat: fail closed on deprecated room keys
2026-03-26 14:15:51 +00:00
Jacob Tomlinson
464e2c10a5 ACP: sanitize terminal tool titles (#55137)
* ACP: sanitize terminal tool titles

Co-authored-by: nexrin <268879349+nexrin@users.noreply.github.com>

* Config: refresh config baseline and stabilize restart pid test

---------

Co-authored-by: nexrin <268879349+nexrin@users.noreply.github.com>
2026-03-26 14:12:24 +00:00
Peter Steinberger
883239a560 build: prepare 2026.3.25 unreleased 2026-03-26 13:57:45 +00:00
Shakker
e3660f265c docs: sync config baseline 2026-03-26 13:35:48 +00:00
Tak Hoffman
cc7f18d6c2 fix: replace stale canonical duplicate rows 2026-03-26 08:03:24 -05:00
Tak Hoffman
fde3871ee7 fix: prefer freshest duplicate row promotion 2026-03-26 07:54:43 -05:00
Tyler Yust
cc077ef1ef fix(bluebubbles): enable group participant enrichment by default, add fallback fetch and handle field aliases 2026-03-26 05:45:41 -07:00
Tak Hoffman
68c6abe32b docs: add beta release testing guidance 2026-03-26 07:34:08 -05:00
Tak Hoffman
b529d13477 test: fix bluebubbles attachment ssrf expectations 2026-03-26 07:25:14 -05:00
Saurabh Mishra
6fbe9dd935 fix: surface provider-specific rate limit error message (#54433) (#54512)
Merged via squash.

Prepared head SHA: 755cff833c
Co-authored-by: bugkill3r <2924124+bugkill3r@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-26 15:16:06 +03:00
Jacob Tomlinson
2383daf5c4 Matrix: gate verification notices on DM access (#55122) 2026-03-26 11:59:20 +00:00
Tyler Yust
e43600c9e5 fix(bluebubbles): auto-allow private network for local serverUrl and add allowPrivateNetwork to channel schema 2026-03-26 04:55:46 -07:00
Jacob Tomlinson
c5415a474b fix(msteams): align feedback invoke authorization (#55108)
* msteams: align feedback invoke authorization

* msteams: fix feedback allowlist regressions

* msteams: tighten feedback group authorization
2026-03-26 11:51:43 +00:00
Jacob Tomlinson
269282ac69 Telegram: enforce DM auth for callbacks (#55112) 2026-03-26 11:42:27 +00:00
Jacob Tomlinson
d9810811b6 fix(agents): enforce session_status guard after sessionId resolution (#55105)
* fix(agents): enforce visibility guard after sessionId resolution in session_status

When a sessionId (rather than an explicit agent key) is passed to the
session_status tool, the sessionId resolution block rewrites
requestedKeyRaw to an explicit "agent:..." key.  The subsequent
visibility guard check at line 375 tested
`!requestedKeyRaw.startsWith("agent:")`, which was now always false
after resolution — skipping the visibility check entirely.

This meant a sandboxed agent could bypass visibility restrictions by
providing a sessionId instead of an explicit session key.

Fix: use the original `isExplicitAgentKey` flag (captured before
resolution) instead of re-checking the dynamic requestedKeyRaw.
This ensures the visibility guard runs for sessionId inputs while
still skipping the redundant check for inputs that were already
validated at the earlier explicit-key check (lines 281-286).

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

* test: cover session status sessionId guard

* test: align parent sessionId guard coverage

---------

Co-authored-by: Kevin Sheng <shenghuikevin@github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:34:22 +00:00
Jacob Tomlinson
5e8cb22176 Feishu: validate webhook signatures before parsing (#55083)
* Feishu: validate webhook signatures before parsing

* Scripts: allow Feishu raw body guard callsite
2026-03-26 10:29:22 +00:00
Nimrod Gutman
a3b85e1583 fix(discord): force fresh gateway reconnects (#54697)
* fix(discord): force fresh gateway reconnects

* fix(discord): harden forced reconnect teardown

* fix(discord): retry after socket drain timeouts

* fix(discord): guard forced socket teardown

* fix(discord): stop cleanly during reconnect drain
2026-03-26 12:05:00 +02:00
Altay
8564480f3e chore: add lockfile entry for extensions/microsoft-foundry 2026-03-26 12:50:33 +03:00
Tyler Yust
4c85fd8569 BlueBubbles: enrich group participants with local Contacts names (#54984)
* BlueBubbles: enrich group participants with Contacts names

* BlueBubbles: gate contact enrichment behind opt in config
2026-03-26 18:38:37 +09:00
Jacob Tomlinson
f92c92515b fix(extensions): route fetch calls through fetchWithSsrFGuard (#53929)
* fix(extensions): route fetch calls through fetchWithSsrFGuard

Replace raw fetch() with fetchWithSsrFGuard in BlueBubbles, Mattermost,
Nextcloud Talk, and Thread Ownership extensions so outbound requests go
through the shared DNS-pinning and network-policy layer.

BlueBubbles: thread allowPrivateNetwork from account config through all
fetch call sites (send, chat, reactions, history, probe, attachments,
multipart). Add _setFetchGuardForTesting hook for test overrides.

Mattermost: add guardedFetchImpl wrapper in createMattermostClient that
buffers the response body before releasing the dispatcher. Handle
null-body status codes (204/304).

Nextcloud Talk: wrap both sendMessage and sendReaction with
fetchWithSsrFGuard and try/finally release.

Thread Ownership: add fetchWithSsrFGuard and ssrfPolicyFromAllowPrivateNetwork
to the plugin SDK surface; use allowPrivateNetwork:true for the
Docker-internal forwarder.

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

* fix(extensions): improve null-body handling and test harness cleanup

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

* fix(bluebubbles): default to strict SSRF policy when allowPrivateNetwork is unset

Callers that omit allowPrivateNetwork previously got undefined policy,
which caused blueBubblesFetchWithTimeout to fall through to raw fetch
and bypass the SSRF guard entirely.

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

* fix(bluebubbles): thread allowPrivateNetwork through action and monitor call sites

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

* fix(mattermost,nextcloud-talk): add allowPrivateNetwork config for self-hosted/LAN deployments

* fix: regenerate config docs baseline for new allowPrivateNetwork fields

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 02:04:54 -07:00
pomelo
dad68d319b Remove Qwen OAuth integration (qwen-portal-auth) (#52709)
* Remove Qwen OAuth integration (qwen-portal-auth)

Qwen OAuth via portal.qwen.ai is being deprecated by the Qwen team due
to traffic impact on their primary Qwen Code user base. Users should
migrate to the officially supported Model Studio (Alibaba Cloud Coding
Plan) provider instead.

Ref: https://github.com/openclaw/openclaw/issues/49557

- Delete extensions/qwen-portal-auth/ plugin entirely
- Remove qwen-portal from onboarding auth choices, provider aliases,
  auto-enable list, bundled plugin defaults, and pricing cache
- Remove Qwen CLI credential sync (external-cli-sync, cli-credentials)
- Remove QWEN_OAUTH_MARKER from model auth markers
- Update docs/providers/qwen.md to redirect to Model Studio
- Update model-providers docs (EN + zh-CN) to remove Qwen OAuth section
- Regenerate config and plugin-sdk baselines
- Update all affected tests

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* Clean up residual qwen-portal references after OAuth removal

* Add migration hint for deprecated qwen-portal OAuth provider

* fix: finish qwen oauth removal follow-up

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Frank Yang <frank.ekn@gmail.com>
2026-03-26 16:32:34 +08:00
Ayaan Zaidi
83e6c12f15 build: update plugin sdk api baseline 2026-03-26 13:52:52 +05:30
kevinlin-openai
432d5f863c fix: add slack upload-file action (#54987) (thanks @kevinlin-openai)
* feat(slack): add upload-file action

Co-authored-by: Codex <noreply@openai.com>

* fix(slack): guard upload-file routing

Co-authored-by: Codex <noreply@openai.com>

* fix(slack): tighten upload-file validation

---------

Co-authored-by: kevinlin-openai <kevin@dendron.so>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-26 13:37:58 +05:30
Ayaan Zaidi
78584413ec docs: refresh config baseline for microsoft foundry 2026-03-26 12:45:47 +05:30
Ayaan Zaidi
7ea17963b0 fix: wire microsoft foundry into contract registry 2026-03-26 12:43:03 +05:30
wenmeng zhou
143275687a Docs: rename modelstudio.md to qwen_modelstudio.md, add Standard API endpoints (#54407)
* Docs: rename modelstudio.md to qwen_modelstudio.md, add Standard API endpoints

* refine docs

* Docs: fix broken link in providers/index.md after modelstudio rename

* Docs: add redirect from /providers/modelstudio to /providers/qwen_modelstudio

* Docs: adjust the order in index.md

* docs: rename modelstudio to qwen_modelstudio, add Standard API endpoints (#54407) (thanks @wenmengzhou)

---------

Co-authored-by: George Zhang <georgezhangtj97@gmail.com>
2026-03-26 00:11:28 -07:00
Nyanako
d72cc7a380 fix: route codex responses over websocket and preserve tool warnings (#53702) (thanks @Nanako0129)
* fix: route codex responses over websocket and suppress gated core tool warnings

* fix: rebase codex websocket patch onto main

* fix: preserve explicit alsoAllow warnings (#53702) (thanks @Nanako0129)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-26 12:28:17 +05:30
Tyler Yust
00e932a83c fix: restore inbound image embedding for CLI routed BlueBubbles turns (#51373)
* fix(cli): hydrate prompt image refs for inbound media

* Agents: harden CLI prompt image hydration (#51373)

* test: fix CLI prompt image hydration helper mocks
2026-03-26 15:47:44 +09:00
MetaX e|acc
a16dd967da feat: Add Microsoft Foundry provider with Entra ID authentication (#51973)
* Microsoft Foundry: add native provider

* Microsoft Foundry: tighten review fixes

* Microsoft Foundry: enable by default

* Microsoft Foundry: stabilize API routing
2026-03-26 01:33:14 -05:00
Ayaan Zaidi
06de515b6c fix(plugins): skip allowlist warning for config paths 2026-03-26 11:44:23 +05:30
sudie-codes
6329edfb8d msteams: add search message action (#54832)
* msteams: add pin/unpin, list-pins, and read message actions

Wire up Graph API endpoints for message read, pin, unpin, and list-pins
in the MS Teams extension, following the same patterns as edit/delete.

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

* msteams: address PR review comments for pin/unpin/read actions

- Handle 204 No Content in postGraphJson (Graph mutations may return empty body)
- Strip conversation:/user: prefixes in resolveConversationPath to avoid Graph 404s
- Remove dead variable in channel pin branch
- Rename unpin param from messageId to pinnedMessageId for semantic clarity
- Accept both pinnedMessageId and messageId in unpin action handler for compat

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

* msteams: resolve user targets + add User-Agent to Graph helpers

- Resolve user:<aadId> targets to actual conversation IDs via conversation
  store before Graph API calls (fixes 404 for DM-context actions)
- Add User-Agent header to postGraphJson/deleteGraphRequest for consistency
  with fetchGraphJson after rebase onto main

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

* msteams: resolve DM targets to Graph chat IDs + expose pin IDs

- Prefer cached graphChatId over Bot Framework conversation IDs for user
  targets; throw descriptive error when no Graph-compatible ID is available
- Add `id` field to list-pins rows so default formatters surface the pinned
  resource ID needed for the unpin flow

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

* msteams: add react and reactions (list) message actions

* msteams: add search message action via Graph API

* msteams: fix search query injection, add ConsistencyLevel header, use manual query string

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 01:09:53 -05:00
sudie-codes
8c852d86f7 msteams: fetch thread history via Graph API for channel replies (#51643)
* msteams: fetch thread history via Graph API for channel replies

* msteams: address PR #51643 review feedback

- Wrap resolveTeamGroupId Graph call in try/catch, fall back to raw
  conversationTeamId when Team.ReadBasic.All permission is missing
- Remove dead fetchChatMessages function (exported but never called)
- Add JSDoc documenting oldest-50-replies Graph API limitation

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

* msteams: address thread history PR review comments

* msteams: only cache team group IDs on successful Graph lookup

Avoid caching raw conversationTeamId as a Graph team GUID when the
/teams/{id} lookup fails — the raw ID may be a Bot Framework conversation
key, not a valid GUID, causing silent thread-history failures for the
entire cache TTL.

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 01:09:33 -05:00
George Zhang
6cbd2d36f8 Revert "feat: add video generation core infrastructure and extend image generation parameters (#53681)" (#54943)
This reverts commit 4cb8dde894.
2026-03-25 23:00:14 -07:00
OfflynAI
e45533d568 fix(whatsapp): drop fromMe echoes in self-chat DMs using outbound ID tracking (#54570)
Merged via squash.

Prepared head SHA: dad53caf39
Co-authored-by: joelnishanth <140015627+joelnishanth@users.noreply.github.com>
Co-authored-by: mcaxtr <7562095+mcaxtr@users.noreply.github.com>
Reviewed-by: @mcaxtr
2026-03-26 02:24:24 -03:00
Neerav Makwana
6fd9d2ff38 fix: support OpenAI Codex media understanding (#54829) (thanks @neeravmakwana)
* OpenAI: register Codex media understanding provider

* fix: route codex image prompts through system instructions

* fix: add changelog for codex image tool fix (#54829) (thanks @neeravmakwana)

* fix: remove any from provider registration tests (#54829) (thanks @neeravmakwana)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-26 10:10:11 +05:30
Ted Li
76ff0d9298 fix: restore image-tool generic provider fallback (#54858) (thanks @MonkeyLeeT)
* Image tool: restore generic provider fallback

* Image tool: cover multi-image generic fallback

* test: tighten minimax-portal image fallback coverage

* fix: restore image-tool generic provider fallback (#54858) (thanks @MonkeyLeeT)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-26 10:07:43 +05:30
Neerav Makwana
8efc6e001e fix: auto-enable configured channel plugins in routed CLI commands (#54809) (thanks @neeravmakwana)
* CLI: auto-enable configured channel plugins in routed commands

* fix: auto-enable configured channel plugins in routed CLI commands (#54809) (thanks @neeravmakwana)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-26 10:06:16 +05:30
sparkyrider
1bc30b7fb9 fix: restore Kimi Code under Moonshot setup (#54619) (thanks @sparkyrider)
* Onboarding: restore Kimi Code under Moonshot setup

* Update extensions/kimi-coding/index.ts

Fix naming convention in metadata

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-03-26 09:46:40 +05:30
Kevin Boyle
99deba798c fix: restore CLI message transcript mirroring (#54187) (thanks @KevInTheCloud5617)
* fix: pass agentId in CLI message command to enable session transcript writes

The CLI `openclaw message send` command was not passing `agentId` to
`runMessageAction()`, causing the outbound session route resolution to
be skipped (it's gated on `agentId && !dryRun`). Without a route, the
`mirror` object is never constructed, and `appendAssistantMessageToSessionTranscript()`
is never called.

This fix resolves the agent ID from the config (defaulting to "main")
and passes it through, enabling transcript mirroring for all channels
when using the CLI.

Closes #54186

* fix: format message.ts with oxfmt

* fix: use resolveDefaultAgentId instead of cfg.agent

* fix: restore CLI message transcript mirroring (#54187) (thanks @KevInTheCloud5617)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-26 09:32:43 +05:30
Neerav Makwana
68d854cb9c fix: use provider-aware context window lookup (#54796) (thanks @neeravmakwana)
* fix(status): use provider-aware context window lookup

* test(status): cover provider-aware context lookup

* fix: use provider-aware context window lookup (#54796) (thanks @neeravmakwana)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-26 09:28:20 +05:30
Greg Retkowski
14430ade57 fix: tighten systemd duplicate gateway detection (#45328) (thanks @gregretkowski)
* daemon: tighten systemd duplicate gateway detection (#15849)

* fix three issues from PR review

* fix windows unit tests due to posix/windows path differences
* ensure line continuations are handled in systemd units
* fix misleading test name

* attempt fix windows test due to fs path separator

* fix system_dir separator, fix platform side-effect

* change approach for mocking systemd filesystem test

* normalize systemd paths to linux style

* revert to vers that didnt impact win32 tests

* back out all systemd inspect tests

* change test approach to avoid other tests issues

* fix: tighten systemd duplicate gateway detection (#45328) (thanks @gregretkowski)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-26 09:20:10 +05:30
wangchunyue
ebad7490b4 fix: resolve telegram token fallback for binding-created accounts (#54362) (thanks @openperf)
* fix(telegram): resolve channel-level token fallthrough for binding-created accountIds

Fixes #53876

* fix(telegram): align isConfigured with resolveTelegramToken multi-bot guard

* fix(telegram): use normalized account lookup and require available token
2026-03-26 09:16:15 +05:30
Marcus Castro
bc1c308383 fix(whatsapp): clarify allowFrom policy error (#54850) 2026-03-26 00:44:10 -03:00
Tak Hoffman
5b68e52894 ci: collapse preflight manifest routing (#54773)
* ci: collapse preflight manifest routing

* ci: fix preflight workflow outputs

* ci: restore compat workflow tasks

* ci: match macos shards to windows

* ci: collapse macos swift jobs

* ci: skip empty submodule setup

* ci: drop submodule setup from node env
2026-03-25 22:38:30 -05:00
Ted Li
4f297a094a docs: add WeChat channel via official Tencent iLink Bot plugin (#52131) (thanks @MonkeyLeeT)
* docs: add WeChat channel via official Tencent iLink Bot plugin

Add WeChat to the README channel lists and setup section.

Uses the official Tencent-published plugin @tencent-weixin/openclaw-weixin
which connects via the iLink Bot API (QR code login, long-poll).
Requires WeChat 8.0.70+ with the ClawBot plugin enabled; the plugin
is being rolled out gradually by Tencent.

Covers: setup steps, capabilities (DM-only, media up to 100 MB,
multi-account, pairing authorization, typing indicators, config path),
and the context token restart caveat.

* docs: update WeChat plugin install for v2.0 compatibility

- Add version compatibility note (v2.x requires OpenClaw >= 2026.3.22,
  @legacy tag for older hosts)
- Add plugins.allow step (required since plugins.allow was introduced)

* docs: drop manual plugins.allow/enable steps (handled by plugins install)

* docs: fix multi-account instruction to require explicit --account id

* docs: trim WeChat section to match neighboring channels, fix pairing link

* docs: sync WeChat channel docs

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-26 09:07:01 +05:30
Frank the Builder
74ed75f2e7 fix: deliver verbose tool summaries in Telegram forum topics (#43236) (thanks @frankbuild)
* fix(auto-reply): deliver verbose tool summaries in Telegram forum topics

Forum topics have ChatType 'group' but are threaded conversations where
verbose tool output should be delivered (same as DMs). The
shouldSendToolSummaries gate now checks IsForum to allow tool summaries
in forum topic sessions.

Fixes #43206

* test: add sendToolResult count assertion per review feedback

* fix: add changelog for forum topic verbose tool summaries (#43236) (thanks @frankbuild)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-26 09:04:55 +05:30
xieyongliang
4cb8dde894 feat: add video generation core infrastructure and extend image generation parameters (#53681)
* feat: add video generation core infrastructure and extend image generation parameters

Add full video generation capability to OpenClaw core:

- New `video_generate` agent tool with support for prompt, duration, aspect ratio,
  resolution, seed, watermark, I2V (first/last frame), camerafixed, and draft mode
- New `VideoGenerationProvider` plugin SDK type and `registerVideoGenerationProvider` API
- New `src/video-generation/` module (types, runtime with fallback, provider registry)
- New `openclaw/plugin-sdk/video-generation` export for external plugins
- 200MB max file size for generated videos (vs default 5MB for images)

Extend image generation with additional parameters:
- `seed`, `watermark`, `guidanceScale`, `optimizePrompt`, `providerOptions`
- New `readBooleanParam()` helper in tool common utilities

Update plugin registry, contracts, and all test mocks to include
`videoGenerationProviders` and `videoGenerationProviderIds`.

Made-with: Cursor

* fix: validate aspect ratio against target provider when model override is set

* cleanup: remove redundant ?? undefined from video/image generate tools

* chore: regenerate plugin SDK API baseline after video generation additions

---------

Co-authored-by: yongliang.xie <yongliang.xie@bytedance.com>
2026-03-25 18:45:06 -07:00
Mathias Nagler
39fbfd9b28 fix(mattermost): thread resolved cfg through reply delivery send calls (#48347)
Merged via squash.

Prepared head SHA: 7ca468e365
Co-authored-by: mathiasnagler <9951231+mathiasnagler@users.noreply.github.com>
Co-authored-by: mukhtharcm <56378562+mukhtharcm@users.noreply.github.com>
Reviewed-by: @mukhtharcm
2026-03-26 01:31:12 +00:00
gumclaw
208ff68298 fix: allow msteams feedback and welcome config keys (#54679)
Merged via squash.

Prepared head SHA: f56a15ddea
Co-authored-by: gumclaw <265388744+gumclaw@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-26 03:00:52 +03:00
Devin Robison
81ebc7e034 fix(gateway): block silent reconnect scope-upgrade escalation (#54694)
* fix(gateway): block silent reconnect scope-upgrade escalation

* formatting updateas

* Resolve feedback

* formatting fixes

* Update src/gateway/server.silent-scope-upgrade-reconnect.poc.test.ts

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* Feedback updates

* fix unit test

* Feedback update

* Review feedback update

* More Greptile nit fixes

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-03-25 17:54:14 -06:00
adzendo
19d91aaa8f fix: make buttons schema optional in message tool (#54418)
Merged via squash.

Prepared head SHA: 0805c095e9
Co-authored-by: adzendo <246828680+adzendo@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-26 02:43:15 +03:00
Erhhung Yuan
b6f631e045 fix(schema): tools.web.fetch.maxResponseBytes #53397 (#53401)
Merged via squash.

Prepared head SHA: 5d10a98bdb
Co-authored-by: erhhung <5808864+erhhung@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-26 02:40:00 +03:00
Mikhail Beliakov
fd934a566b feat(cli): add json schema to cli tool (#54523)
Merged via squash.

Prepared head SHA: 39c15ee70d
Co-authored-by: kvokka <15954013+kvokka@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-26 02:30:32 +03:00
Tak Hoffman
ab37d8810d test: introduce planner-backed test runner, stabilize local builds (#54650)
* test: stabilize ci and local vitest workers

* test: introduce planner-backed test runner

* test: address planner review follow-ups

* test: derive planner budgets from host capabilities

* test: restore planner filter helper import

* test: align planner explain output with execution

* test: keep low profile as serial alias

* test: restrict explicit planner file targets

* test: clean planner exits and pnpm launch

* test: tighten wrapper flag validation

* ci: gate heavy fanout on check

* test: key shard assignments by unit identity

* ci(bun): shard vitest lanes further

* test: restore ci overlap and stabilize planner tests

* test: relax planner output worker assertions

* test: reset plugin runtime state in optional tools suite

* ci: split macos node and swift jobs

* test: honor no-isolate top-level concurrency budgets

* ci: fix macos swift format lint

* test: cap max-profile top-level concurrency

* ci: shard macos node checks

* ci: use four macos node shards

* test: normalize explain targets before classification
2026-03-25 18:11:58 -05:00
Devin Robison
764394c78b fix: enforce localRoots sandbox on Feishu docx upload file reads (#54693)
* fix: enforce localRoots sandbox on Feishu docx upload file reads

* Formatting fixes

* Update tests

* Feedback updates
2026-03-25 16:09:00 -06:00
Devin Robison
6a79324802 Filter untrusted CWD .env entries before OpenClaw startup (#54631)
* Filter untrusted CWD .env entries before OpenClaw startup

* Add missing test file

* Fix missing and updated files

* Address feedback

* Feedback updates

* Feedback update

* Add test coverage

* Unit test fix
2026-03-25 15:49:26 -06:00
Tak Hoffman
79fbcfc03b fix(ci): restore main green 2026-03-25 16:17:42 -05:00
Nimrod Gutman
501190d2e8 refactor(sandbox): remove tool policy facade (#54684)
* refactor(sandbox): remove tool policy facade

* fix(sandbox): harden blocked-tool guidance

* fix(sandbox): avoid control-char guidance leaks

* fix: harden sandbox blocked-tool guidance (#54684) (thanks @ngutman)
2026-03-25 23:03:24 +02:00
Jared
c6d8318d07 Trigger preflight compaction from transcript estimates when usage is stale (#49479)
Merged via squash.

Prepared head SHA: 8d214b708b
Co-authored-by: jared596 <37019497+jared596@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-25 13:22:16 -07:00
Jacob Tomlinson
c02ee8a3a4 OpenShell: exclude hooks/ from mirror sync (#54657)
* OpenShell: exclude hooks/ from mirror sync

* OpenShell: make excludeDirs case-insensitive for cross-platform safety
2026-03-25 19:59:07 +00:00
Jacob Tomlinson
d1bfe08424 fix: apply host-env blocklist to auth-profile env refs in daemon install (#54627)
* fix: apply host-env blocklist to auth-profile env refs in daemon install

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

* ci: retrigger checks

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 19:57:22 +00:00
Jacob Tomlinson
e34694733f fix(talk-voice): enforce operator.admin scope on /voice set config writes (#54461)
* fix(talk-voice): enforce operator.admin scope on /voice set config writes

* fix(talk-voice): align scope guard with phone-control pattern

Use optional chaining (?.) instead of Array.isArray so webchat callers
with undefined scopes are rejected, matching the established pattern in
phone-control. Add test for webchat-with-no-scopes case.

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 19:55:26 +00:00
Joseph Krug
d81593c6e2 fix: trigger compaction on LLM timeout with high context usage (#46417)
Merged via squash.

Prepared head SHA: 619bc4c1fa
Co-authored-by: joeykrug <5925937+joeykrug@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-25 12:51:36 -07:00
Devin Robison
1b3a1246d0 Block reset-profile on lower-privilege browser request surfaces (#54618)
* Block reset-profile on lower-privilege browser request surfaces

* add missing tests

* Fix tests

* Test fix
2026-03-25 13:36:59 -06:00
Devin Robison
4797bbc5b9 fix: reject path traversal and home-dir patterns in media parse layer (#54642)
* fix: reject path traversal and home-dir patterns in media parse layer

* Update parse tests
2026-03-25 13:35:16 -06:00
kiranvk2011
84401223c7 fix: per-model cooldown scope, stepped backoff, and user-facing rate-limit message (#49834)
Merged via squash.

Prepared head SHA: 7c488c070c
Co-authored-by: kiranvk-2011 <91108465+kiranvk-2011@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-25 22:03:49 +03:00
Tak Hoffman
6efc4e8ef2 test: fix windows tmp root assertions 2026-03-25 13:44:54 -05:00
Devin Robison
b7d70ade3b Fix/telegram writeback admin scope gate (#54561)
* fix(telegram): require operator.admin for legacy target writeback persistence

* Address claude feedback

* Update extensions/telegram/src/target-writeback.ts

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* Remove stray brace

* Add updated docs

* Add missing test file, address codex concerns

* Fix test formatting error

* Address comments, fix tests

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-03-25 12:12:09 -06:00
Andrii Furmanets
89c4c674d1 fix(compaction): surface safeguard cancel reasons and clarify /compact skips (#51072)
Merged via squash.

Prepared head SHA: f1dbef0443
Co-authored-by: afurm <6375192+afurm@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-25 11:03:22 -07:00
M1a0
7847e67f8a plugin-runtime: expose runHeartbeatOnce in system API (#40299)
* plugin-runtime: expose runHeartbeatOnce in system API

Plugins that enqueue system events and need the agent to deliver
responses to the originating channel currently have no way to
override the default `heartbeat.target: "none"` behaviour.

Expose `runHeartbeatOnce` in the plugin runtime `system` namespace
so plugins can trigger a single heartbeat cycle with an explicit
`heartbeat: { target: "last" }` override — the same pattern the
cron service already uses (see #28508).

Changes:
- Add `RunHeartbeatOnceOptions` type and `runHeartbeatOnce` to
  `PluginRuntimeCore.system` (types-core.ts)
- Wire the function through a thin wrapper in runtime-system.ts
- Update the test-utils plugin-runtime mock

Made-with: Cursor

* feat(plugins): expose runHeartbeatOnce in system API (#40299) (thanks @loveyana)

---------

Co-authored-by: George Zhang <georgezhangtj97@gmail.com>
2026-03-25 10:47:01 -07:00
chenxingzhen
4ae4d1fabe fix: mid-turn 429 rate limit silent no-reply and context engine registration failure (#50930)
Merged via squash.

Prepared head SHA: eea7800df3
Co-authored-by: infichen <13826604+infichen@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-25 10:43:08 -07:00
Matt Van Horn
e0972db7a2 fix: stop leaking reply tags in iMessage outbound text (#39512) (thanks @mvanhorn)
* fix: stop leaking reply tags in iMessage outbound text (#39512) (thanks @mvanhorn)

* fix: preserve iMessage outbound whitespace without directive tags (#39512) (thanks @mvanhorn)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-25 23:00:16 +05:30
Tak Hoffman
f63c4b0856 test: keep vitest on forks only 2026-03-25 12:22:22 -05:00
Harold Hunt
055ad65896 Telegram: ignore self-authored DM message updates (#54530)
Merged via squash.

Prepared head SHA: c1c8a85168
Co-authored-by: huntharo <5617868+huntharo@users.noreply.github.com>
Co-authored-by: huntharo <5617868+huntharo@users.noreply.github.com>
Reviewed-by: @huntharo
2026-03-25 13:16:35 -04:00
Peter Steinberger
685f17460d build: update appcast for 2026.3.24 2026-03-25 10:10:34 -07:00
Jackal Xin
2de32fbf14 fix: reconcile session compaction count after late compaction success (#45493)
Merged via squash.

Prepared head SHA: d0715a5555
Co-authored-by: jackal092927 <3854860+jackal092927@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-25 10:00:41 -07:00
Peter Steinberger
cff6dc94e3 docs: format changelog for release 2026-03-25 09:34:30 -07:00
Peter Steinberger
97a7e93db4 build: prepare 2026.3.24 release 2026-03-25 09:31:05 -07:00
liyuan97
e2e9f979ca feat(minimax): add image generation provider and trim model catalog to M2.7 (#54487)
* feat(minimax): add image generation and TTS providers, trim TUI model list

Register MiniMax image-01 and speech-2.8 models as plugin providers for
the image_generate and TTS tools. Both resolve CN/global base URLs from
the configured model endpoint origin.

- Image generation: base64 response, aspect-ratio support, image-to-image
  via subject_reference, registered for minimax and minimax-portal
- TTS: speech-2.8-turbo (default) and speech-2.8-hd, hex-encoded audio,
  voice listing via get_voice API, telephony PCM support
- Add MiniMax to TTS auto-detection cascade (after ElevenLabs, before
  Microsoft) and TTS config section
- Remove MiniMax-VL-01, M2, M2.1, M2.5 and variants from TUI picker;
  keep M2.7 and M2.7-highspeed only (backend routing unchanged)

* feat(minimax): trim legacy model catalog to M2.7 only

Cherry-picked from temp/feat/minimax-trim-legacy-models (949ed28).
Removes MiniMax-VL-01, M2, M2.1, M2.5 and variants from the model
catalog, model order, modern model matchers, OAuth config, docs, and
tests. Keeps only M2.7 and M2.7-highspeed.

Conflicts resolved:
- provider-catalog.ts: removed MINIMAX_TUI_MODELS filter (no longer
  needed since source array is now M2.7-only)
- index.ts: kept image generation + speech provider registrations
  (added by this branch), moved media understanding registrations
  earlier (as intended by the cherry-picked commit)

* fix(minimax): update discovery contract test to reflect M2.7-only catalog

Cherry-picked from temp/feat/minimax-trim-legacy-models (2c750cb).

* feat(minimax): add web search provider and register in plugin entry

* fix(minimax): resolve OAuth credentials for TTS speech provider

* MiniMax: remove web search and TTS providers

* fix(minimax): throw on empty images array after generation failure

* feat(minimax): add image generation provider and trim catalog to M2.7 (#54487) (thanks @liyuan97)

---------

Co-authored-by: tars90percent <tars@minimaxi.com>
Co-authored-by: George Zhang <georgezhangtj97@gmail.com>
2026-03-25 09:29:35 -07:00
xieyongliang
7cc86e9685 fix(release): add plugin-sdk:check-exports to release:check (#54283)
* fix(plugins): resolve sdk alias from import.meta.url for external plugins

When a plugin is installed outside the openclaw package (e.g.
~/.openclaw/extensions/), resolveLoaderPluginSdkPackageRoot() fails to
locate the openclaw root via cwd or argv1 hints, resulting in an empty
alias map. Jiti then cannot resolve openclaw/plugin-sdk/* imports and
the plugin fails to load with "Cannot find module".

Since sdk-alias.ts is always compiled into the openclaw package itself,
import.meta.url reliably points inside the installation directory. Add it
as an unconditional fallback in resolveLoaderPluginSdkPackageRoot() so
external plugins can always resolve the plugin SDK.

Fixes: Error: Cannot find module 'openclaw/plugin-sdk/plugin-entry'

* fix(plugins): pass loader moduleUrl to resolve sdk alias for external plugins

The previous approach of adding import.meta.url as an unconditional
fallback inside resolveLoaderPluginSdkPackageRoot() broke test isolation:
tests that expected null from untrusted fixtures started finding the real
openclaw root. Revert that and instead thread an optional moduleUrl through
buildPluginLoaderAliasMap → resolvePluginSdkScopedAliasMap →
listPluginSdkExportedSubpaths → resolveLoaderPluginSdkPackageRoot.

loader.ts passes its own import.meta.url as the hint, which is always
inside the openclaw installation. This guarantees the sdk alias map is
built correctly even when argv1 does not resolve to the openclaw root
(e.g. single-binary distributions, custom launchers, or Docker images
where the binary wrapper is not a standard npm symlink).

Tests that call sdk-alias helpers directly without moduleUrl are
unaffected and continue to enforce the existing isolation semantics.
A new test covers the moduleUrl resolution path explicitly.

* fix(plugins): use existing fixture file for moduleUrl hint in test

The previous test pointed loaderModuleUrl to dist/plugins/loader.js
which is not created by createPluginSdkAliasFixture, causing resolution
to fall back to the real openclaw root instead of the fixture root.
Use fixture.root/openclaw.mjs (created by the bin+marker fixture) so
the moduleUrl hint reliably resolves to the fixture package root.

* fix(test): use fixture.root as cwd in external plugin alias test

When process.cwd() is mocked to the external plugin dir, the
findNearestPluginSdkPackageRoot(process.cwd()) fallback resolves to
the real openclaw repo root in the CI test runner, making the test
resolve the wrong aliases. Using fixture.root as cwd ensures all
resolution paths consistently point to the fixture.

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

* fix(release): add plugin-sdk:check-exports to release:check

plugin-sdk subpath exports (e.g. openclaw/plugin-sdk/plugin-entry,
openclaw/plugin-sdk/provider-auth) were missing from the published
package.json, causing external plugins to fail at load time with
'Cannot find module openclaw/plugin-sdk/plugin-entry'.

Root cause: sync-plugin-sdk-exports.mjs syncs plugin-sdk-entrypoints.json
into package.json exports, but this sync was never validated in the
release:check pipeline. As a result, any drift between
plugin-sdk-entrypoints.json and the published package.json goes
undetected until users hit the runtime error.

Fix: add plugin-sdk:check-exports to release:check so the CI gate
fails loudly if the exports are out of sync before publishing.

* fix(test): isolate moduleUrl hint test from process.cwd() fallback

Use externalPluginRoot as cwd instead of fixture.root, so only the
moduleUrl hint can resolve the openclaw package root. Previously,
withCwd(fixture.root) allowed the process.cwd() fallback to also
resolve the fixture root, making the moduleUrl path untested.

Spotted by greptile-apps review on #54283.

* fix(test): use empty string to disable argv1 in moduleUrl hint test

Passing undefined for argv1 in buildPluginLoaderAliasMap triggers the
STARTUP_ARGV1 default (process.argv[1], the vitest runner binary inside
the openclaw repo). resolveTrustedOpenClawRootFromArgvHint then resolves
to the real openclaw root before the moduleUrl hint is checked, making
the test resolve wrong aliases.

Pass "" instead: falsy so the hint is skipped, but does not trigger the
default parameter value. Only the moduleUrl can bridge the gap.

Made-with: Cursor

* fix(plugins): thread moduleUrl through SDK alias resolution for external plugins (#54283) Thanks @xieyongliang

---------

Co-authored-by: bojsun <bojie.sun@bytedance.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Jerry <jerry@JerrydeMacBook-Air-2.local>
Co-authored-by: yongliang.xie <yongliang.xie@bytedance.com>
Co-authored-by: George Zhang <georgezhangtj97@gmail.com>
2026-03-25 09:11:17 -07:00
Devin Robison
c2a2edb329 Fix local copied package installs honoring staged project .npmrc (#54543) 2026-03-25 09:59:33 -06:00
Lin Z
a0b9dc0078 fix(feishu): use message create_time for inbound timestamps (#52809)
* fix(feishu): use message create_time instead of Date.now() for Timestamp field

When a message is sent offline and later retried by the Feishu client
upon reconnection, Date.now() captures the *delivery* time rather than
the *authoring* time.  This causes downstream consumers to see a
timestamp that can be minutes or hours after the user actually composed
the message, leading to incorrect temporal semantics — for example, a
"delete this" command may target the wrong resource because the agent
believes the instruction was issued much later than it actually was.

Replace every Date.now() used for message timestamps with the original
create_time from the Feishu event payload (millisecond-epoch string),
falling back to Date.now() only when the field is absent.  The
definition is also hoisted to the top of handleFeishuMessage so that
both the pending-history path and the main inbound-payload path share
the same authoritative value.

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

* test(feishu): verify Timestamp uses message create_time

Add two test cases:
1. When create_time is present, Timestamp must equal the parsed value
2. When create_time is absent, Timestamp falls back to Date.now()

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

* chore: revert unrelated formatting change to lifecycle.test.ts

This file was inadvertently formatted in a prior commit. Reverting to
match main and keep the PR scoped to the Feishu timestamp fix only.

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

* fix(feishu): use message create_time for inbound timestamps (#52809) (thanks @schumilin)

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: George Zhang <georgezhangtj97@gmail.com>
2026-03-25 08:36:12 -07:00
Lin Z
bd4237c16c fix(feishu): close WebSocket connections on monitor stop (#52844)
* fix(feishu): close WebSocket connections on monitor stop/abort

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

* test(feishu): add WebSocket cleanup tests

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

* fix(feishu): close WebSocket connections on monitor stop (#52844) (thanks @schumilin)

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: George Zhang <georgezhangtj97@gmail.com>
2026-03-25 08:32:21 -07:00
Nimrod Gutman
edb5123f26 fix(sandbox): honor sandbox alsoAllow and explicit re-allows (#54492)
* fix(sandbox): honor effective sandbox alsoAllow policy

* fix(sandbox): prefer resolved sandbox context policy

* fix: honor sandbox alsoAllow policy (#54492) (thanks @ngutman)
2026-03-25 16:51:13 +02:00
Peter Steinberger
e9ac2860c1 docs: prepare 2026.3.24-beta.2 release 2026-03-25 06:58:39 -07:00
Harold Hunt
da60aff17a Tests: isolate security audit home skill resolution (#54473)
Merged via squash.

Prepared head SHA: 82181e15fb
Co-authored-by: huntharo <5617868+huntharo@users.noreply.github.com>
Co-authored-by: huntharo <5617868+huntharo@users.noreply.github.com>
Reviewed-by: @huntharo
2026-03-25 09:43:19 -04:00
Peter Steinberger
ee714f5a42 test(media): make local roots fixture windows-safe 2026-03-25 06:24:39 -07:00
Peter Steinberger
ea08f2eb8c fix(runtime): support Node 22.14 installs 2026-03-25 06:22:18 -07:00
Harold Hunt
3c3fd8c386 Discord: log rejected native command deploy failures (#54118)
Merged via squash.

Prepared head SHA: be250f9620
Co-authored-by: huntharo <5617868+huntharo@users.noreply.github.com>
Co-authored-by: huntharo <5617868+huntharo@users.noreply.github.com>
Reviewed-by: @huntharo
2026-03-25 09:19:46 -04:00
Peter Steinberger
436aa838fe test(release): sync llama peer fixture 2026-03-25 06:06:47 -07:00
Peter Steinberger
284084672a fix(ci): restore e2e docker cache boundary 2026-03-25 06:06:47 -07:00
Peter Steinberger
66c88b4c77 fix(update): preflight npm target node engine 2026-03-25 06:01:20 -07:00
Peter Steinberger
c92002e1de fix(media): align outbound media access with fs policy 2026-03-25 05:50:21 -07:00
Peter Steinberger
39ad51426c test: add Open WebUI docker smoke 2026-03-25 05:28:51 -07:00
Peter Steinberger
9e95125f06 fix(config): ignore same-base correction publish warnings 2026-03-25 04:58:44 -07:00
Peter Steinberger
b19cc399b6 test: fix clobbered config snapshot expectation 2026-03-25 04:54:37 -07:00
Peter Steinberger
3b6d980c52 refactor: unify whatsapp identity handling 2026-03-25 04:46:24 -07:00
Peter Steinberger
cdba1e6771 fix: copy openclaw bin before docker install 2026-03-25 04:45:31 -07:00
Peter Steinberger
d874f3970a build: prepare 2026.3.24-beta.1 2026-03-25 04:41:26 -07:00
Peter Steinberger
7c2790cec4 test: isolate voice-call temp stores 2026-03-25 11:39:47 +00:00
Peter Steinberger
c3d1dbc696 refactor(openai): extract codex auth identity helper 2026-03-25 04:24:46 -07:00
Peter Steinberger
d363af8c13 refactor(auth): separate profile ids from email metadata 2026-03-25 04:24:46 -07:00
khhjoe
f3fe019e3d fix(whatsapp): use async fs.promises.readFile for selfLid creds read 2026-03-25 04:24:31 -07:00
khhjoe
770a5ee5b1 fix(whatsapp): read selfLid from creds.json for reply-to-bot detection 2026-03-25 04:24:31 -07:00
khhjoe
93594a1440 fix(whatsapp): compare selfLid for reply-to-bot implicit mention in groups 2026-03-25 04:24:31 -07:00
khhjoe
ff25407861 fix(whatsapp): unwrap FutureProofMessage (botInvokeMessage) to restore reply-to-bot detection 2026-03-25 04:24:31 -07:00
Peter Steinberger
52bec1612c test: collapse telegram transport and status suites 2026-03-25 11:23:18 +00:00
Peter Steinberger
12082f47bd test: collapse telegram button and access suites 2026-03-25 11:23:18 +00:00
Peter Steinberger
b7f2b0d7b9 refactor: align pairing replies, daemon hints, and feishu mention policy 2026-03-25 04:22:53 -07:00
Peter Steinberger
524004ff32 docs: add missing changelog items 2026-03-25 04:22:23 -07:00
Peter Steinberger
3de04bdd6d test: collapse telegram context and transport suites 2026-03-25 11:17:58 +00:00
Peter Steinberger
fc49258c12 test: collapse telegram helper suites 2026-03-25 11:17:58 +00:00
Peter Steinberger
9873ef0e39 docs: sort changelog by user impact 2026-03-25 04:14:52 -07:00
Peter Steinberger
94041f06b4 test: harden parallels npm update runner 2026-03-25 11:13:09 +00:00
Ayaan Zaidi
b497f3cda0 fix: normalize before_dispatch conversation id 2026-03-25 16:28:31 +05:30
Ayaan Zaidi
15776091a8 fix(whatsapp): avoid eager login tool runtime access 2026-03-25 16:25:00 +05:30
ZhangXuan
a10d587b41 fix: preserve before_dispatch delivery semantics (#50444) (thanks @gfzhx)
* Plugins: add before_dispatch hook

* Tests: fix before_dispatch hook mock typing

* Rebase: adapt before_dispatch hook to routeReplyRuntime refactor

* fix: preserve before_dispatch delivery semantics (#50444) (thanks @gfzhx)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-25 16:16:08 +05:30
Ayaan Zaidi
765182dcc6 fix: skip session:patch hook clone without listeners 2026-03-25 16:12:39 +05:30
Ayaan Zaidi
ee0dcaa7b0 fix: unify log timestamp offsets (#38904) (thanks @sahilsatralkar) 2026-03-25 16:06:33 +05:30
Gracie Gould
3e2e9bc238 fix: isolate session:patch hook payload (#53880) (thanks @graciegould)
* gateway: make session:patch hook typed and non-blocking

* gateway(test): add session:patch hook coverage

* docs(gateway): clarify session:patch security note

* fix: address review feedback on session:patch hook

Remove unused createInternalHookEvent import and fix doc example
to use inline event.type check matching existing hook examples.

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

* fix: isolate hook payload to prevent mutation leaking into response

Shallow-copy sessionEntry and patch in the session:patch hook event
so fire-and-forget handlers cannot mutate objects used by the
response path.

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

* fix: isolate session:patch hook payload (#53880) (thanks @graciegould)

---------

Co-authored-by: “graciegould” <“graciegould5@gmail.com”>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-25 15:59:38 +05:30
Liu Yuan
419824729a fix: fail loud when PTY cursor mode is unknown (#51490) (thanks @liuy)
* fix(process): auto-detect PTY cursor key mode for send-keys

When a PTY session sends smkx (\x1b[?1h) or rmkx (\x1b[?1l) to switch
cursor key mode, send-keys now detects this and encodes cursor keys
accordingly.

- smkx/rmkx detection in handleStdout before sanitizeBinaryOutput
- cursorKeyMode stored in ProcessSession
- encodeKeySequence accepts cursorKeyMode parameter
- DECCKM_SS3_KEYS for application mode (arrows + home/end)
- CSI sequences for normal mode
- Modified keys (including alt) always use xterm modifier scheme
- Extract detectCursorKeyMode for unit testing
- Use lastIndexOf to find last toggle in chunk (later one wins)

Fixes #51488

* fix: fail loud when PTY cursor mode is unknown (#51490) (thanks @liuy)

* style: format process send-keys guard (#51490) (thanks @liuy)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-25 15:51:27 +05:30
Ayaan Zaidi
717ff0d667 fix: cover macOS Edge osascript fallback path (#48561) (thanks @zoherghadyali) 2026-03-25 15:47:04 +05:30
Ayaan Zaidi
0295271f97 fix: add changelog for macOS Edge default browser detection (#48561) (thanks @zoherghadyali) 2026-03-25 15:47:04 +05:30
Zoher Ghadyali
2fe38b0201 fix(browser): add Edge LaunchServices bundle IDs for macOS default browser detection
macOS registers Edge as 'com.microsoft.edgemac' in LaunchServices, which
differs from the CFBundleIdentifier 'com.microsoft.Edge' in the app's own
Info.plist. Without recognising the LaunchServices IDs, Edge users who set
Edge as their default browser are not detected as having a Chromium browser.

Add the four com.microsoft.edgemac* variants to CHROMIUM_BUNDLE_IDS and a
corresponding test that mocks the LaunchServices → osascript resolution
path for Edge.
2026-03-25 15:47:04 +05:30
Sparkyrider
55dc6a8bb2 cron: queue isolated delivery awareness 2026-03-25 15:21:14 +05:30
Ayaan Zaidi
2a40612058 fix: make telegram thread create use topic payload (#54336) (thanks @andyliu) 2026-03-25 13:43:09 +05:30
Andy
e1cd90db6e fix(cli): route telegram thread create to topic-create 2026-03-25 13:43:09 +05:30
ToToKr
4140100807 fix: clarify cron best-effort partial delivery status (#42535) (thanks @MoerAI)
* fix(cron): track and log bestEffort delivery failures, mark not delivered on partial failure

* fix(cron): cache successful results on partial failure to preserve replay idempotency

When a best-effort send partially fails, we now still cache the successful delivery results via rememberCompletedDirectCronDelivery. This prevents duplicate sends on same-process replay while still correctly marking the job as not fully delivered.

* fix(cron): preserve partial-failure state on replay (#27069)

* fix(cron): restore test infrastructure and fix formatting

* fix: clarify cron best-effort partial delivery status (#42535) (thanks @MoerAI)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-25 12:49:32 +05:30
hnshah
c7f021f70f fix: preflight invalid telegram photos (#52545) (thanks @hnshah)
* fix(telegram): validate photo dimensions before sendPhoto

Prevents PHOTO_INVALID_DIMENSIONS errors by checking image dimensions
against Telegram Bot API requirements before calling sendPhoto.

If dimensions exceed limits (width + height > 10,000px), automatically
falls back to sending as document instead of crashing with 400 error.

Tested in production (openclaw 2026.3.13) where this error occurred:
  [telegram] tool reply failed: GrammyError: Call to 'sendPhoto' failed!
  (400: Bad Request: PHOTO_INVALID_DIMENSIONS)

Uses existing sharp dependency to read image metadata. Gracefully
degrades if sharp fails (lets Telegram handle validation, backward
compatible behavior).

Closes: #XXXXX (will reference OpenClaw issue if one exists)

* fix(telegram): validate photo aspect ratio

* refactor: use shared telegram image metadata

* fix: fail closed on telegram image metadata

* fix: preflight invalid telegram photos (#52545) (thanks @hnshah)

---------

Co-authored-by: Bob Shah <bobshah@Macs-Mac-Studio.local>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-25 12:00:20 +05:30
Peter Steinberger
b9857a2b79 test: allow daemon start hints to grow on linux (#54058) (thanks @byungsker) 2026-03-24 23:09:04 -07:00
Peter Steinberger
8b80690a1a test: accept fenced discord pairing codes (#54058) (thanks @byungsker) 2026-03-24 23:09:04 -07:00
Peter Steinberger
fac0a172e5 test: refresh pairing reply assertions for fenced codes (#54058) (thanks @byungsker) 2026-03-24 23:09:04 -07:00
Peter Steinberger
2566d6b300 fix: finish feishu open-group docs and baselines (#54058) (thanks @byungsker) 2026-03-24 23:09:04 -07:00
lbo728
a322059efa test(feishu): update config-schema test for removed requireMention default 2026-03-24 23:09:04 -07:00
lbo728
69195f7e9d fix(feishu): default requireMention to false for groupPolicy open
Groups configured with groupPolicy: open are expected to respond to all
messages. Previously, requireMention defaulted to true regardless of
groupPolicy, causing image (and other non-text) messages to be silently
dropped because they cannot carry @-mentions.

Fix: when groupPolicy is 'open' and requireMention is not explicitly
configured, resolve it to false instead of true. Users who want
mention-required behaviour in open groups can still set requireMention: true
explicitly.

Adds three regression tests covering the new default, explicit override, and
the unchanged allowlist-policy behaviour.

Closes #52553
2026-03-24 23:09:04 -07:00
VACInc
89b7fee352 fix: preserve Telegram forum topic last-route delivery (#53052) (thanks @VACInc)
* fix(telegram): preserve forum topic thread in last-route delivery

* style(telegram): format last-route update

* test(telegram): cover General topic last-route thread

* test(telegram): align topic route helper

* fix(telegram): skip bound-topic last-route writes

---------

Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-25 11:31:01 +05:30
Peter Steinberger
1c82b06645 test: collapse msteams state and monitor suites 2026-03-25 05:57:02 +00:00
Peter Steinberger
e53809035e test: collapse msteams graph suites 2026-03-25 05:57:02 +00:00
Peter Steinberger
b99b521a92 test: collapse msteams helper suites 2026-03-25 05:57:02 +00:00
Peter Steinberger
f5408d82d2 refactor: unify gateway handshake timeout wiring 2026-03-24 22:53:55 -07:00
Peter Steinberger
258a214bcb refactor: centralize daemon service start state flow 2026-03-24 22:49:34 -07:00
Liren Pan
5dec3dddc4 style(auth): wrap codex fallback formatting 2026-03-24 22:49:06 -07:00
Liren Pan
773427470a test(auth): cover codex jwt fallback branches 2026-03-24 22:49:06 -07:00
Liren Pan
b6e70a5cdd auth: derive codex oauth profile ids from jwt claims 2026-03-24 22:49:06 -07:00
nimbleenigma
abec3ed645 fix: keep Telegram native commands on runtime snapshot (#53179) (thanks @nimbleenigma)
* fix(telegram): use runtime snapshot for native commands

* fix: keep Telegram native commands on runtime snapshot (#53179) (thanks @nimbleenigma)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-25 11:18:54 +05:30
Peter Steinberger
57e2223eec test: align pairing reply assertions 2026-03-25 05:48:31 +00:00
Peter Steinberger
6c3e767289 refactor: centralize Discord gateway supervision 2026-03-24 22:47:12 -07:00
Peter Steinberger
efafbece17 test: collapse nextcloud-talk send and helper suites 2026-03-25 05:39:11 +00:00
dobbylorenzbot
717ee2fa59 fix(gateway): raise default connect challenge timeout 2026-03-24 22:38:17 -07:00
HCL
db35f30005 fix: validate config before restart + derive loaded from real state
Address Codex P1 + Greptile P2:
- Move config validation before the restart attempt so invalid config
  is caught in the stop→start path (not just the already-loaded path)
- Derive service.loaded from actual isLoaded() after restart instead
  of hardcoded true

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: HCL <chenglunhu@gmail.com>
2026-03-24 22:35:09 -07:00
HCL
d2248534d8 fix(daemon): bootstrap stopped service on gateway start
After `gateway stop` (which runs `launchctl bootout`), `gateway start`
checks `isLoaded` → false → prints "not loaded" hints and exits.
The service is never re-bootstrapped, so `start` cannot recover from
`stop` — only `gateway install` works.

Root cause: src/cli/daemon-cli/lifecycle-core.ts:208-217 — runServiceStart
calls handleServiceNotLoaded which only prints hints, never attempts
service.restart() (which already handles bootstrap via
bootstrapLaunchAgentOrThrow at launchd.ts:598).

Fix: when service is not loaded, attempt service.restart() first (which
handles re-bootstrapping on all platforms). If restart fails (e.g. plist
was deleted, not just booted out), fall back to the existing hints.

The restart path is already proven: restartLaunchAgent (launchd.ts:556)
handles "not loaded" via bootstrapLaunchAgentOrThrow. This fix routes
the start command through the same recovery path.

Closes #53878

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: HCL <chenglunhu@gmail.com>
2026-03-24 22:35:09 -07:00
Peter Steinberger
7467f304a7 test: collapse nextcloud-talk helper suites 2026-03-25 05:33:57 +00:00
Peter Steinberger
e8e45a4936 test: collapse synology-chat helper suites 2026-03-25 05:33:57 +00:00
Peter Steinberger
c22f3c514b test: collapse googlechat helper suites 2026-03-25 05:33:57 +00:00
SUMUKH
149c4683a3 fix: scope Telegram pairing code blocks (#52784) (thanks @sumukhj1219)
* Telegram: format pairing challenge for easier copy

* test: restore Telegram pairing chatId assertion

* fix: scope Telegram pairing code blocks (#52784) (thanks @sumukhj1219)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-25 11:03:33 +05:30
sfwn
9ab226d275 fix(discord): remove safety error listener after teardown to prevent leak 2026-03-24 22:31:03 -07:00
sfwn
731016472c fix(discord): prevent uncaught gateway errors from crashing the process
Move cleanup() after disconnect() in waitForDiscordGatewayStop so the
error listener is still active during disconnect. Add a safety error
listener in the lifecycle finally block to suppress late errors emitted
by Carbon during teardown.

Fixes the "Max reconnect attempts (0) reached after code 1006" uncaught
exception that kills the entire gateway process when a Discord WebSocket
drops and reconnection fails.
2026-03-24 22:31:03 -07:00
w-sss
247f82119c fix: improve Telegram 403 membership delivery errors (#53635) (thanks @w-sss)
* fix(telegram): improve error messages for 403 bot not member errors

- Detect 403 'bot is not a member' errors specifically
- Provide actionable guidance for users to fix the issue
- Fixes #48273 where outbound sendMessage fails with 403

Root cause:
When a Telegram bot tries to send a message to a channel/group it's not
a member of, the API returns 403 'bot is not a member of the channel chat'.
The error message was not clear about how to fix this.

Fix:
1. Detect 403 errors in wrapTelegramChatNotFoundError
2. Provide clear error message explaining the issue
3. Suggest adding the bot to the channel/group

* fix(telegram): fix regex precedence for 403 error detection

- Group alternatives correctly: /403.*(bot.*not.*member|bot was blocked)/i
- Require 403 for both alternatives (previously bot.*blocked matched any error)
- Update error message to cover both scenarios
- Fixes Greptile review feedback

* fix(telegram): correct regex alternation precedence for 403 errors

- Fix: /403.*(bot.*not.*member|bot was blocked)/ → /403.*(bot.*not.*member|bot.*blocked)/
- Ensures 403 requirement applies to both alternatives
- Fixes Greptile review comment on PR #48650

* fix(telegram): add 'bot was kicked' to 403 error regex and message

* fix(telegram): preserve membership delivery errors

* fix: improve Telegram 403 membership delivery errors (#53635) (thanks @w-sss)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-25 10:59:29 +05:30
w-sss
0bdb8ac7ad fix(ui): add width/height to context-notice SVG icon
- Fixes #47924
- Prevents SVG icon from expanding and covering entire chat window
- Adds explicit 24x24px dimensions to context-notice__icon SVG

Root cause:
The SVG element lacked explicit width and height attributes,
causing it to expand to fill the parent container when the context
usage warning appears (at ~85% token limit).
2026-03-25 00:25:55 -05:00
Peter Steinberger
33d31e2b0d test: collapse imessage test suites 2026-03-25 05:21:16 +00:00
Peter Steinberger
bc8622c659 test: collapse helper extension test suites 2026-03-25 05:21:16 +00:00
Peter Steinberger
6f137fff76 test: collapse telegram and whatsapp target suites 2026-03-25 05:21:16 +00:00
Ayaan Zaidi
793b36c5d2 fix: bootstrap proxy for LanceDB embeddings (#54119) (thanks @neeravmakwana) 2026-03-25 10:50:00 +05:30
Neerav Makwana
1a815e323c test(memory): unmock infra runtime cleanup 2026-03-25 10:50:00 +05:30
Neerav Makwana
09a4453026 fix(memory): bootstrap proxy for LanceDB embeddings 2026-03-25 10:50:00 +05:30
Jealous
2c3cf4f387 chore(tts): rename VOICE_BUBBLE identifiers to OPUS and update docs 2026-03-25 10:49:21 +05:30
Peter Steinberger
46d3617d25 refactor: split gateway plugin bootstrap and registry surfaces 2026-03-24 22:16:26 -07:00
Josh Avant
10161c2d79 Plugins: enforce terminal hook decision semantics for tool/message guards (#54241)
* Plugins: enforce terminal hook decision policies

* Tests: assert terminal hook behavior in integration paths

* Docs: clarify terminal hook decision semantics

* Docs: add hook guard semantics to plugin guides

* Tests: isolate outbound format label expectations

* changelog

Signed-off-by: joshavant <830519+joshavant@users.noreply.github.com>

---------

Signed-off-by: joshavant <830519+joshavant@users.noreply.github.com>
2026-03-25 00:11:13 -05:00
dongdong
5a5c5d4cde fix: refresh DeepSeek pricing to current V3.2 rates (#54143) (thanks @arkyu2077)
* fix: add actual DeepSeek API pricing to model catalog

Replace zero-cost placeholder with real pricing from DeepSeek docs:
- deepseek-chat (V3): /bin/bash.27/1M input, .10/1M output, /bin/bash.07 cache read
- deepseek-reasoner (R1): /bin/bash.55/1M input, .19/1M output, /bin/bash.14 cache read

Fixes #54134

* fix: refresh DeepSeek pricing to current V3.2 rates

* fix: refresh DeepSeek pricing to current V3.2 rates (#54143) (thanks @arkyu2077)

---------

Co-authored-by: Jasmine Zhang <jasminezhang@192.168.1.75>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-25 10:34:03 +05:30
Quinn H.
d43dda465d fix: note marketplace streaming and ClawHub URL (#54160) (thanks @QuinnH496)
* fix: correct ClawHub URL in system prompt and use streaming download in marketplace

- Fix #54154: Change clawhub.com to clawhub.ai in system prompt
- Fix #54156: Replace arrayBuffer() with streaming pipeline for marketplace
  plugin downloads to avoid OOM on memory-constrained devices

* fix: guard marketplace archive stream body

* fix: note marketplace streaming and ClawHub URL (#54160) (thanks @QuinnH496)

---------

Co-authored-by: Li Enying <li.enying@openclaw.ai>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-25 10:29:21 +05:30
Peter Steinberger
61dd61e917 refactor: tighten split-runtime live-state guardrails 2026-03-24 21:58:50 -07:00
Peter Steinberger
94425764a8 refactor: centralize systemd unavailable classification 2026-03-24 21:57:48 -07:00
Jonathan Jing
30e80fb947 fix: isolate channel startup failures (#54215) (thanks @JonathanJing)
* fix(gateway): isolate channel startup failures to prevent cascade

When one channel (e.g., WhatsApp) fails to start due to missing runtime
modules, it should not block other channels (e.g., Discord) from starting.

Changes:
- Use Promise.allSettled to start channels concurrently
- Catch individual channel startup errors without affecting others
- Add startup summary logging for observability

Before: Sequential await startChannel() - if one throws, subsequent
channels never start.

After: Concurrent startup with per-channel error handling - all channels
attempt to start, failures are logged but don't cascade.

Fixes: P0 - WhatsApp runtime exception no longer blocks Discord startup

* fix(gateway): keep channel startup isolation sequential

* fix: isolate channel startup failures (#54215) (thanks @JonathanJing)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-25 10:22:42 +05:30
Peter Steinberger
8a463e7aa9 test: collapse helper plugin test suites 2026-03-25 04:52:36 +00:00
Peter Steinberger
fe84148724 test: collapse messaging target test suites 2026-03-25 04:52:36 +00:00
Peter Steinberger
6e050808ef test: collapse channel setup test suites 2026-03-25 04:52:36 +00:00
Sally O'Malley
e5d0d810e1 fixes for cli-containerized (#54223)
Signed-off-by: sallyom <somalley@redhat.com>
2026-03-25 00:51:55 -04:00
VACInc
1c9f62fad3 fix(gateway): restart sentinel wakes session after restart and preserves thread routing (#53940) thanks @VACInc
Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
Co-authored-by: Val Alexander <68980965+BunsDev@users.noreply.github.com>
2026-03-24 23:47:21 -05:00
Peter Steinberger
23a4932997 refactor: share channel card selectors and layout 2026-03-24 21:44:28 -07:00
kevinten10
c00372e559 fix(agents): correct ClawHub URL in system prompt
Change clawhub.com to clawhub.ai in agent system prompt.
The .com domain is incorrect and doesn't point to the real ClawHub.

Fixes #54154
2026-03-25 10:10:37 +05:30
Peter Steinberger
039e87c942 fix: restore WhatsApp active listener singleton (#54232) 2026-03-24 21:36:20 -07:00
chocobo9
762fed1f90 fix(daemon): add headless server hints to systemd unavailable error
Add loginctl enable-linger and XDG_RUNTIME_DIR recovery hints to the
generic (non-WSL) systemd unavailable error path, helping users on
SSH/headless servers diagnose and fix the issue without a desktop
session.

Fixes #11805

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 21:33:18 -07:00
Ayaan Zaidi
2aaea9f99e chore: drop unrelated gitignore change (#53944) (thanks @affsantos) 2026-03-24 21:33:04 -07:00
Ayaan Zaidi
03dc287a29 fix: keep minimal gateway channel registry live (#53944) (thanks @affsantos) 2026-03-24 21:33:04 -07:00
Ayaan Zaidi
5eb6fdca6f fix(gateway): close runtime state on startup abort 2026-03-24 21:33:04 -07:00
Ayaan Zaidi
ef5e554def fix(gateway): invalidate channel caches on re-pin 2026-03-24 21:33:04 -07:00
affsantos
fae4492d92 fix: re-pin channel registry after deferred plugin reload
When preferSetupRuntimeForChannelPlugins is active, gateway boot performs
two plugin loads: a setup-runtime pass and a full reload after listen.
The initial pin captured the setup-entry snapshot. The deferred reload now
re-pins so getChannelPlugin() resolves against the full implementations.
2026-03-24 21:33:04 -07:00
affsantos
61d866838f fix: add inline comment clarifying dual-release scope
Address Greptile review: releasePluginRouteRegistry now releases both
HTTP-route and channel registry pins. Added comment for clarity.
2026-03-24 21:33:04 -07:00
affsantos
3a4c860798 fix(gateway): pin channel registry at startup to survive registry swaps
Channel plugin resolution fails with 'Channel is unavailable: <channel>'
after the active plugin registry is replaced at runtime. The root cause is
that getChannelPlugin() resolves against the live registry snapshot, which
is replaced when non-primary registry loads (e.g., config-schema reads)
call loadOpenClawPlugins(). If the replacement registry does not carry the
same channel entries, outbound message delivery and subagent announce
silently break.

This mirrors the existing pinActivePluginHttpRouteRegistry pattern: the
channel registry is pinned at gateway startup and released on shutdown.
Subsequent setActivePluginRegistry calls no longer evict the channel
snapshot, so getChannelPlugin() always resolves against the registry that
was active when the gateway booted.
2026-03-24 21:33:04 -07:00
ted
4d41b8664c fix(ui): return null when default account ID is stale instead of falling back to first account 2026-03-24 21:28:53 -07:00
ted
dc85235bf0 UI: derive channel configured state from default account 2026-03-24 21:28:53 -07:00
Peter Steinberger
43058c021e test: collapse setup and monitor channel suites 2026-03-25 04:25:02 +00:00
Peter Steinberger
cb76ba2406 test: collapse line channel suites 2026-03-25 04:25:02 +00:00
Peter Steinberger
ed9646516d test: collapse utility plugin suites 2026-03-25 04:25:02 +00:00
Peter Steinberger
410c2dba65 test: collapse provider plugin suites 2026-03-25 04:25:02 +00:00
fishking
6c04ce3092 fix(reasoning): guard model default reasoning when thinking active
- Add hasAgentReasoningDefault to reasoningExplicitlySet check
  This prevents model default from overriding agent's explicit "off"
- Restore !thinkingActive guard for model default fallback
  Prevents redundant Reasoning: output alongside internal thinking

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-25 09:54:12 +05:30
fishking
b91374eb0d fix(reasoning): apply reasoningDefault independently of thinking level
The reasoningDefault was incorrectly skipped when thinking was active.
Thinking controls reasoning depth while reasoning controls visibility -
they should be independent settings.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-25 09:54:12 +05:30
Tak Hoffman
f48571bec6 fix: prefer freshest duplicate rows in session loads 2026-03-24 22:36:50 -05:00
Tak Hoffman
40f820ff7f fix: prefer freshest duplicate session rows in reads 2026-03-24 22:28:50 -05:00
Tak Hoffman
93656da672 test: make vitest config tests platform-aware 2026-03-24 22:23:23 -05:00
Tak Hoffman
f3eb620824 fix: refresh available tools when the session model changes (#54184) 2026-03-24 22:07:06 -05:00
Tak Hoffman
0c35ac4423 fix: prefer freshest transcript session owners 2026-03-24 21:58:53 -05:00
Tak Hoffman
64432f8e46 test: disable Vitest fs cache on Windows 2026-03-24 21:51:55 -05:00
Tak Hoffman
3c46e0307a fix: prefer deterministic transcript session keys 2026-03-24 21:30:54 -05:00
Tak Hoffman
7a7e4cd4c4 fix: prefer deterministic session usage targets 2026-03-24 21:21:57 -05:00
Tak Hoffman
df58b4f5fb fix: prefer deterministic session id resume targets 2026-03-24 21:18:40 -05:00
Tak Hoffman
9c7823350b feat: add /tools runtime availability view (#54088)
* test(memory): lock qmd status counts regression

* feat: make /tools show what the agent can use right now

* fix: sync web ui slash commands with the shared registry

* feat: add profile and unavailable counts to /tools

* refine: keep /tools focused on available tools

* fix: resolve /tools review regressions

* fix: honor model compat in /tools inventory

* fix: sync generated protocol models for /tools

* fix: restore canonical slash command names

* fix: avoid ci lint drift in google helper exports

* perf: stop computing unused /tools unavailable counts

* docs: clarify /tools runtime behavior
2026-03-24 21:09:51 -05:00
Tak Hoffman
fb04801ed7 fix: enforce sandbox visibility for session_status ids 2026-03-24 21:05:25 -05:00
Tak Hoffman
2c1d16e261 fix: drop spawned visibility list caps 2026-03-24 20:52:05 -05:00
Tak Hoffman
6651511e90 fix: verify exact spawned session visibility 2026-03-24 20:39:00 -05:00
Tak Hoffman
57fd0a9b23 fix: enforce spawned session visibility in key resolve 2026-03-24 20:26:29 -05:00
Tak Hoffman
154e14f18f fix: resolve exact session ids without fuzzy limits 2026-03-24 20:26:29 -05:00
Ted Li
5799322d9e Discord: resolve /think autocomplete from session model (#49176)
* Discord: use session model for /think autocomplete

* Discord: use cached session store in think autocomplete

* Discord: align think autocomplete with effective bound route

* Discord: fix think autocomplete route-resolution test mocks

* Discord: stabilize think autocomplete CI coverage

* Discord: gate think autocomplete context behind auth

* Discord: share slash auth for think autocomplete

* Discord: localize think autocomplete auth gate

* Discord: drop think autocomplete auth gating

* Discord: align native autocomplete auth and route readiness

* Discord: use effective route for model picker

---------

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-24 20:13:11 -05:00
Vincent Koc
2069e124a9 chore(agents): normalize pi embedded runner imports 2026-03-24 18:06:24 -07:00
Vincent Koc
d10669629d feat(gateway): make openai compatibility agent-first 2026-03-24 18:06:24 -07:00
Vincent Koc
e1d16ba42e test(parallel): force unit-fast batch planning 2026-03-24 18:04:20 -07:00
Vincent Koc
8d87e85705 test(browser): stabilize default browser detection mocks 2026-03-24 18:02:47 -07:00
Tak Hoffman
1b5b23d2b1 fix: prefer current parents in session rows 2026-03-24 20:00:17 -05:00
Tak Hoffman
475983a364 fix: prefer current subagent owners in session rows 2026-03-24 19:54:07 -05:00
Tak Hoffman
ad818bda84 fix: ignore moved child rows in spawnedBy session filters 2026-03-24 19:47:36 -05:00
Tak Hoffman
6eaff70b55 fix: ignore moved child rows in subagent announces 2026-03-24 19:47:36 -05:00
Tak Hoffman
16d2e68610 fix: ignore stale store ownership in session child lists 2026-03-24 19:47:36 -05:00
Peter Steinberger
f7de5c3b83 test: collapse search helper suites 2026-03-25 00:42:09 +00:00
Peter Steinberger
83591fabfb test: consolidate plugin provider suites 2026-03-25 00:42:09 +00:00
Peter Steinberger
3a1b517581 fix: repair CI regression checks 2026-03-25 00:20:24 +00:00
Tak Hoffman
e6db1dde45 fix: hide moved subagents from stale command targets 2026-03-24 19:15:47 -05:00
Peter Steinberger
f6205de73a refactor: split feishu helpers and tests 2026-03-24 17:12:25 -07:00
Peter Steinberger
5cdb50abe6 refactor: unify Google Generative AI normalization 2026-03-24 17:09:11 -07:00
Devin Robison
56eeec4099 fix: require operator.admin for mutating internal /allowlist commands (#54097) 2026-03-24 18:05:59 -06:00
Peter Steinberger
561acd1675 test: tighten shared card schema coverage 2026-03-24 17:04:07 -07:00
Tak Hoffman
639706f298 fix: ignore moved child rows in subagent status 2026-03-24 18:57:42 -05:00
Peter Steinberger
3664c2ce46 fix: polish feishu retry helper (#43788) (thanks @lefarcen) 2026-03-24 16:55:37 -07:00
Elian
b9f48707dc fix(feishu): prevent silent group message drops when bot-info probe times out
When OpenClaw restarts under load, the Feishu bot-info probe
(`/open-apis/bot/v3/info`) can exceed the 10-second timeout due to
event-loop contention during channel initialization. This leaves
`botOpenId` empty, causing `checkBotMentioned()` to return `false`
for every group message — silently dropping them all while DMs
continue to work fine.

Two fixes:

1. **Increase startup probe timeout from 10s to 30s** and make it
   configurable via `OPENCLAW_FEISHU_STARTUP_PROBE_TIMEOUT_MS` env var.
   The previous 10s budget was too tight when multiple channels
   (Slack, Discord, Feishu) initialize concurrently.

2. **Graceful degradation in `checkBotMentioned()`**: when `botOpenId`
   is unknown, return `true` (assume mentioned) instead of `false`.
   This prevents group messages from being silently discarded when the
   probe fails for any reason. The trade-off is that the bot may
   respond to non-@-mentioned messages temporarily until the next
   successful probe, which is far preferable to total silence.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 16:55:37 -07:00
Peter Steinberger
d4fda79ff7 fix: add merged message tool schema guardrail (#53715) (thanks @lndyzwdxhs) 2026-03-24 16:53:56 -07:00
grassylcao
ca578a9183 fix: mark card field as optional in message tool schema
The `createMessageToolCardSchema()` helper returned a bare `Type.Object()`
which TypeBox treats as required when merged into the parent tool schema via
`Type.Object({ card: ... })`. This caused schema validation to reject
media-only sends on Feishu and MSTeams with "must have required property
card", even though the implementation correctly treats card as optional.

Wrap the return value in `Type.Optional()` so the card field is excluded
from the JSON Schema `required` array. Fixes the catch-22 where omitting
card fails validation and including an empty card triggers the runtime
"does not support card with media" guard.

Closes #53697

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 16:53:56 -07:00
Vincent Koc
eaad4ad1be feat(gateway): add missing OpenAI-compatible endpoints (models and embeddings) (#53992)
* feat(gateway): add OpenAI-compatible models and embeddings

* docs(gateway): clarify model list and agent routing

* Update index.md

* fix(gateway): harden embeddings HTTP provider selection

* fix(gateway): validate compat model overrides

* fix(gateway): harden embeddings and response continuity

* fix(gateway): restore compat model id handling
2026-03-24 16:53:51 -07:00
Peter Steinberger
0709224ce3 fix: tighten gateway compose port parsing (#44083) (thanks @bebule) 2026-03-24 16:51:36 -07:00
Kwanghee Park (hugh.k)
ac7ca52090 Gateway: harden Compose-style gateway port parsing 2026-03-24 16:51:36 -07:00
Kwanghee Park (hugh.k)
b665749e9f Gateway: parse Compose-style gateway port env values 2026-03-24 16:51:36 -07:00
Tak Hoffman
e48a0b80a8 fix: ignore moved subagent children on stale parents 2026-03-24 18:46:37 -05:00
Peter Steinberger
33e9e485b8 refactor: clarify docker setup cli phases 2026-03-24 16:46:12 -07:00
Peter Steinberger
1ba436b372 test: speed up media and image-generation suites 2026-03-24 23:45:33 +00:00
Peter Steinberger
1a7914521b test: speed up infra and shared suites 2026-03-24 23:45:33 +00:00
Peter Steinberger
c9f4dd3c1b test: speed up browser control suites 2026-03-24 23:45:33 +00:00
Aria
63b0036248 fix: normalize baseUrl for custom Google Generative AI providers
Custom providers using `api: "google-generative-ai"` (e.g. a paid
Google tier) resolved in the model picker but failed at runtime with
HTTP 404 because the base URL lacked the required `/v1beta` path
segment and provider normalization was gated on the provider key
being exactly `"google"`.

Two targeted fixes, both keyed on the semantic `api` field rather
than provider name strings:

1. `models-config.providers.ts` — change the normalization gate from
   `normalizedKey === "google"` to
   `normalizedProvider?.api === "google-generative-ai"` and add
   `normalizeGoogleBaseUrl()` to ensure the canonical `/v1beta` suffix.

2. `pi-embedded-runner/model.ts` — apply
   `normalizeGoogleGenerativeAiBaseUrl()` in three resolution paths
   (`applyConfiguredProviderOverrides`, `buildInlineProviderModels`,
   fallback model construction) so the base URL is corrected at
   runtime regardless of how the model was discovered.

No changes to name-only call sites (`model-selection`,
`live-model-filter`, `model-forward-compat`); those paths are not
required for custom provider resolution and broadening their provider
checks would incorrectly capture unrelated providers like
`google-antigravity`.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 16:42:56 -07:00
Peter Steinberger
e10ea53ea1 fix: add changelog for docker setup namespace loop (#53385) (thanks @amsminn) 2026-03-24 16:35:42 -07:00
김채완
d21ecd7642 Tests: match any pre-start openclaw-cli run 2026-03-24 16:35:42 -07:00
김채완
3ce09bd071 Tests: reset docker setup log before isolated assert 2026-03-24 16:35:42 -07:00
김채완
81be4b45a6 Docker: seed localhost control UI origin 2026-03-24 16:35:42 -07:00
김채완
dbb806d257 Docker: avoid setup CLI namespace loop 2026-03-24 16:35:42 -07:00
Peter Steinberger
6f6468027a refactor: dedupe test and runtime seams 2026-03-24 23:33:30 +00:00
Tak Hoffman
369119b6b5 fix: ignore stale parent rows in session child lists 2026-03-24 18:29:03 -05:00
Devin Robison
1d7cb6fc03 fix: close sandbox media root bypass for mediaUrl/fileUrl aliases (#54034)
* fix: close sandbox media root bypass for mediaUrl/fileUrl aliases

* Address Greptile feedback

* Fix windows test case failure
2026-03-24 17:28:53 -06:00
Tak Hoffman
907b5254f6 fix: ignore stale rows in subagent kill cascade 2026-03-24 18:12:48 -05:00
Tak Hoffman
1fd684329d fix: ignore stale rows in fast abort 2026-03-24 17:52:28 -05:00
Tak Hoffman
a03bbca4df fix: cascade bulk subagent kills past stale rows 2026-03-24 17:43:21 -05:00
Tak Hoffman
b6031a98e7 fix: ignore stale rows in subagent steer 2026-03-24 17:38:38 -05:00
Tak Hoffman
fee9d4cf37 fix: dedupe stale child completion announces 2026-03-24 17:25:14 -05:00
Tak Hoffman
2c5c5acb1b fix: ignore stale rows in subagent admin kill 2026-03-24 17:25:14 -05:00
Tak Hoffman
c90ae1ee7f fix: prefer latest subagent rows for session control 2026-03-24 17:25:14 -05:00
Tak Hoffman
b8a0258618 fix: ignore stale rows in subagent activity checks 2026-03-24 17:25:14 -05:00
Peter Steinberger
40ab7aca3d test: speed up slack monitor suites 2026-03-24 22:17:12 +00:00
Peter Steinberger
d282667321 test: speed up cli and command suites 2026-03-24 22:17:12 +00:00
Peter Steinberger
3dc139b0c0 test: speed up discord monitor suites 2026-03-24 22:17:12 +00:00
Vincent Koc
e28b516fb5 fix(slack): trim DM reply overhead and restore Codex auto transport (#53957)
* perf(slack): instrument runtime and trim DM overhead

* perf(slack): lazy-init draft previews

* perf(slack): add turn summary diagnostics

* perf(core): trim repeated runtime setup noise

* perf(core): preselect default web search providers

* perf(agent): restore OpenAI auto transport defaults

* refactor(slack): drop temporary perf wiring

* fix(slack): address follow-up review notes

* fix(security): tighten slack and runtime defaults

* style(web-search): fix import ordering

* style(agent): remove useless spread fallback

* docs(changelog): note slack runtime hardening
2026-03-24 15:03:40 -07:00
Devin Robison
47dc7fe816 fix: blcok non-owner authorized senders from chaning /send policy (#53994) 2026-03-24 15:58:39 -06:00
Tak Hoffman
c541cde0f6 fix: dedupe restarted descendant session counts 2026-03-24 16:52:50 -05:00
Tak Hoffman
e24704d5eb fix: dedupe active child session counts 2026-03-24 16:52:50 -05:00
Tak Hoffman
eb40f0b961 fix: clean up matrix /agents binding labels 2026-03-24 16:52:49 -05:00
Vincent Koc
ac8a5a614b ci: increase test shard fanout 2026-03-24 14:50:31 -07:00
Peter Steinberger
a18e156316 test: speed up telegram and whatsapp suites 2026-03-24 21:48:07 +00:00
Peter Steinberger
14e3c2de5f test: speed up discord channel suites 2026-03-24 21:48:07 +00:00
Peter Steinberger
e5173af77e test: speed up slack monitor suites 2026-03-24 21:48:07 +00:00
Peter Steinberger
3622569853 test: speed up memory provider suites 2026-03-24 21:48:07 +00:00
Vincent Koc
d648aebf4d perf(memory): builtin sqlite hot-path follow-ups (#53939)
* chore(perf): start builtin sqlite hotpath workstream

* perf(memory): reuse sqlite statements during sync

* perf(memory): snapshot file state during sync

* perf(memory): consolidate status sqlite reads

* docs(changelog): note builtin sqlite perf work

* perf(memory): avoid session table scans on targeted sync
2026-03-24 14:47:40 -07:00
Peter Steinberger
23a4ae4759 refactor: dedupe test helpers and harnesses 2026-03-24 21:41:46 +00:00
Tak Hoffman
9f4f997472 fix: align /agents ids with subagent targets 2026-03-24 16:29:49 -05:00
Tak Hoffman
a4ccd75ff3 fix: dedupe verbose subagent status counts 2026-03-24 16:18:03 -05:00
Tak Hoffman
51e59983a1 fix: report deduped subagent totals 2026-03-24 16:13:25 -05:00
Peter Steinberger
cf96fa67af ci: batch shared extensions test lane 2026-03-24 21:07:40 +00:00
Tak Hoffman
69d6e95c2a fix: dedupe stale subagent rows in reply views 2026-03-24 16:07:19 -05:00
Devin Robison
3031f061fc Adjust Feishu webhook request body limits (#53933) 2026-03-24 15:02:05 -06:00
Peter Steinberger
68b36cd9de test: fix rebase gate regressions 2026-03-24 21:01:04 +00:00
Peter Steinberger
bcd61f0a38 refactor: dedupe helpers and source seams 2026-03-24 21:00:36 +00:00
Tak Hoffman
ebe18c0379 fix: keep active-descendant subagents visible in reply status 2026-03-24 15:55:57 -05:00
Vincent Koc
0d2315ed15 fix(test): isolate github copilot token imports 2026-03-24 13:54:07 -07:00
Vincent Koc
6bf90a1d68 fix(test): stabilize memory vector dedupe assertion 2026-03-24 13:45:18 -07:00
Vincent Koc
eda1ef7b1a fix(ci): align lazy memory provider tests 2026-03-24 13:40:03 -07:00
Peter Steinberger
ddf65a995a test: speed up memory and secrets suites 2026-03-24 20:39:13 +00:00
Peter Steinberger
e2acfcf527 test: speed up browser pw-tools-core suites 2026-03-24 20:39:13 +00:00
Tak Hoffman
caa718a554 fix: steer ended subagent orchestrators with live descendants 2026-03-24 15:27:19 -05:00
Tak Hoffman
e99c270684 fix: allow follow-up sends to finished subagents 2026-03-24 15:20:39 -05:00
Vincent Koc
7d6d112656 perf(sqlite): use existence probes for empty memory search 2026-03-24 13:15:41 -07:00
Tak Hoffman
f6a0cdc25a fix: let subagent kill cascade through ended parents 2026-03-24 15:15:01 -05:00
Vincent Koc
aaf2d6359e fix(test): satisfy cli backend config typing 2026-03-24 13:06:20 -07:00
Vincent Koc
7330e2ce23 perf(memory): avoid eager provider init on empty search 2026-03-24 13:03:02 -07:00
Tak Hoffman
db0f957aba fix: surface finished subagent send targets 2026-03-24 15:01:34 -05:00
Devin Robison
c2fb7f1948 Adjust CLI backend environment handling before spawn (#53921)
security(agents): sanitize CLI backend env overrides before spawn
2026-03-24 12:58:10 -07:00
Vincent Koc
1beda4aff1 fix(ci): use target-platform npm path semantics 2026-03-24 12:47:34 -07:00
Tak Hoffman
231d62582f fix: prefer current subagent targets over stale rows 2026-03-24 14:38:34 -05:00
Peter Steinberger
4029ce738c test: speed up targeted unit suites 2026-03-24 19:36:08 +00:00
Vincent Koc
698c02e775 test(gateway): align safe open error code 2026-03-24 12:33:15 -07:00
Vincent Koc
87919dec2c fix(test): stabilize npm runner path assertion 2026-03-24 12:32:01 -07:00
Vincent Koc
805bff6e7e fix(cli): precompute bare root help startup path 2026-03-24 12:24:52 -07:00
Tak Hoffman
91b1e41132 fix: ignore stale bulk subagent kill targets 2026-03-24 14:17:28 -05:00
Tak Hoffman
ec23552b58 test: fix manifest registry fixture typing 2026-03-24 14:17:28 -05:00
Peter Steinberger
a4327ad544 refactor: dedupe tests and harden suite isolation 2026-03-24 19:16:19 +00:00
Devin Robison
d60112287f fix: validate agent workspace paths before writing identity files (#53882)
* fix: validate agent workspace paths before writing identity files

* Feedback updates and formatting fixes
2026-03-24 19:15:11 +00:00
Tak Hoffman
870c52aac7 fix: ignore stale subagent send targets 2026-03-24 14:05:00 -05:00
Vincent Koc
40315556d0 perf(plugins): scope web search plugin loads 2026-03-24 12:01:16 -07:00
Tak Hoffman
627ab895e2 fix: ignore stale subagent kill targets 2026-03-24 13:57:03 -05:00
Peter Steinberger
7101ddc5d3 chore: refresh plugin sdk api baseline 2026-03-24 18:49:51 +00:00
Vincent Koc
783cbd1e9d fix(ci): refresh plugin sdk baseline and formatting 2026-03-24 11:45:37 -07:00
Vincent Koc
a9da52da50 refactor(core): make event and queue state lazy 2026-03-24 11:45:27 -07:00
Peter Steinberger
f6b3377af2 test: stabilize low-profile parallel gate 2026-03-24 18:40:46 +00:00
Peter Steinberger
2383107711 fix: unblock supervisor and memory gate failures 2026-03-24 18:40:46 +00:00
Vincent Koc
a97188ceb3 ci: start required checks earlier (#53844)
* ci: start required checks earlier

* ci: restore pnpm in security-fast

* ci: skip docs-only payloads in early check jobs

* ci: harden untrusted pull request execution

* ci: pin gradle setup action

* ci: normalize pull request concurrency cancellation

* ci: remove duplicate early-lane setup

* ci: keep install-smoke push runs unique
2026-03-24 11:37:58 -07:00
Vincent Koc
e4ce1d9a0e fix(runtime): stabilize dist runtime artifacts (#53855)
* fix(build): stabilize lazy runtime entry paths

* fix(runtime): harden bundled plugin npm staging

* docs(changelog): note runtime artifact fixes

* fix(runtime): stop trusting npm_execpath

* fix(runtime): harden Windows npm staging

* fix(runtime): add safe Windows npm fallback
2026-03-24 11:37:39 -07:00
Vincent Koc
0cdd4db6e9 fix(memory): align status manager concurrency test 2026-03-24 11:31:35 -07:00
Vincent Koc
0caafa587f refactor(plugins): make interactive state lazy 2026-03-24 11:29:20 -07:00
Vincent Koc
d0002c5e1e refactor(gateway): make plugin fallback state lazy 2026-03-24 11:26:21 -07:00
scoootscooob
3a4cc89c53 fix: allow compact retry after failed session compaction (#53875) 2026-03-24 11:23:42 -07:00
Vincent Koc
6bef8deda9 refactor(plugins): make command registry lazy 2026-03-24 11:09:34 -07:00
Vincent Koc
f41bdf3c54 refactor(plugins): make hook runner global lazy 2026-03-24 11:07:37 -07:00
Vincent Koc
6451beddb2 refactor(plugins): make runtime registry lazy 2026-03-24 11:04:03 -07:00
Vincent Koc
e16f0cf908 refactor(channels): route registry lookups through runtime 2026-03-24 11:01:48 -07:00
Bob
7fab2c2897 fix(discord): notify user on discord when inbound worker times out (#53823)
* fix(discord): notify user on discord when inbound worker times out.

* fix(discord): notify user on discord when inbound worker times out.

* Discord: await timeout fallback reply

* Discord: add changelog for timeout reply fix (#53823) (thanks @Kimbo7870)

---------

Co-authored-by: VioGarden <111024100+VioGarden@users.noreply.github.com>
Co-authored-by: Onur Solmaz <2453968+osolmaz@users.noreply.github.com>
2026-03-24 19:01:12 +01:00
Tak Hoffman
03ed0bccf1 fix: ignore stale subagent steer targets 2026-03-24 13:00:48 -05:00
scoootscooob
a395c757ab Chat UI: guard compact retries 2026-03-24 10:58:09 -07:00
scoootscooob
19093112ce Chat UI: tighten compact transport handling 2026-03-24 10:58:09 -07:00
scoootscooob
44e27c6092 Webchat: handle bare /compact as session compaction 2026-03-24 10:58:09 -07:00
scoootscooob
01d3442246 Plugins: sanitize sdk export subpaths 2026-03-24 10:58:06 -07:00
scoootscooob
fc60ced03c Plugins: trust only startup cli sdk roots 2026-03-24 10:58:06 -07:00
scoootscooob
f163759167 Plugins: resolve sdk aliases from the running CLI 2026-03-24 10:58:06 -07:00
scoootscooob
8633c7fa73 Providers: fix kimi fallback normalization 2026-03-24 10:58:03 -07:00
scoootscooob
9acb4c8fbc Providers: fix kimi-coding thinking normalization 2026-03-24 10:58:03 -07:00
Tak Hoffman
d25b4a2943 fix: fail closed when subagent steer remap fails 2026-03-24 12:55:43 -05:00
Vincent Koc
7daaefdb08 test(memory): recycle shared channels batches 2026-03-24 10:54:51 -07:00
Vincent Koc
3b03ff11fc test(memory): isolate slack action-runtime hotspot 2026-03-24 10:51:15 -07:00
Vincent Koc
548c2019f1 test(memory): isolate telegram monitor hotspot 2026-03-24 10:50:32 -07:00
Peter Steinberger
6e9591c4ce test: speed up browser suites 2026-03-24 17:49:25 +00:00
Peter Steinberger
217cb0ac58 test: speed up plugin-sdk and cron suites 2026-03-24 17:49:25 +00:00
Vincent Koc
e7ae7d921a test(memory): isolate telegram fetch hotspot 2026-03-24 10:47:30 -07:00
Tak Hoffman
7ab46301a9 fix: continue subagent kill after session store write failures 2026-03-24 12:46:58 -05:00
Vincent Koc
488ad4ac70 test(memory): isolate telegram bot hotspot 2026-03-24 10:46:17 -07:00
Vincent Koc
86de8b65b1 test(memory): isolate plugin-core hotspot 2026-03-24 10:45:11 -07:00
Vincent Koc
a088109327 test(memory): isolate browser remote-tab hotspot 2026-03-24 10:43:51 -07:00
Vincent Koc
fbe5f45340 test(memory): isolate new unit hotspot files 2026-03-24 10:42:22 -07:00
Tak Hoffman
240479abef fix(ci): stop dropping pending main workflow runs 2026-03-24 12:38:07 -05:00
Peter Steinberger
d58d90074f refactor: isolate ACP final delivery flow 2026-03-24 10:36:46 -07:00
Peter Steinberger
822563d1ab fix: unify pi runner usage snapshot fallback 2026-03-24 10:33:18 -07:00
Peter Steinberger
69a0a6c847 fix: tighten ACP final fallback semantics (#53692) (thanks @w-sss) 2026-03-24 10:29:27 -07:00
w-sss
7b8142997f fix(acp): deliver final result text as fallback when no blocks routed
- Check routedCounts.final to detect prior delivery
- Skip fallback for ttsMode='all' to avoid duplicate TTS processing
- Use delivery.deliver for proper routing in cross-provider turns
- Fixes #46814 where ACP child run results were not delivered
2026-03-24 10:28:33 -07:00
Peter Steinberger
d2e0cfc09f test: speed up media fetch suite 2026-03-24 17:27:02 +00:00
Peter Steinberger
a8bf75f03e test: speed up browser and gateway suites 2026-03-24 17:27:02 +00:00
Vincent Koc
435e2c5967 fix(memory): avoid caching qmd status managers 2026-03-24 10:25:00 -07:00
Peter Steinberger
a37ed72829 test: preserve child_process exports in restart bun mock 2026-03-24 17:24:18 +00:00
Vincent Koc
f2475a7f70 fix(slack): improve interactive reply parity (#53389)
* fix(slack): improve interactive reply parity

* fix(slack): isolate reply interactions from plugins

* docs(changelog): note slack interactive parity fixes

* fix(slack): preserve preview text for local agent replies

* fix(agent): preserve directive text in local previews
2026-03-24 10:23:10 -07:00
Peter Steinberger
398d58fb8a fix: stabilize logging config imports 2026-03-24 17:21:28 +00:00
Vincent Koc
a1c91bdb75 fix(memory): avoid caching status-only managers 2026-03-24 10:21:23 -07:00
Peter Steinberger
f47549c5f6 test: speed up backup and doctor suites 2026-03-24 17:16:25 +00:00
Peter Steinberger
cc9d1103d9 test: speed up command runtime suites 2026-03-24 17:16:25 +00:00
Peter Steinberger
6e20c26397 test: speed up cli and model command suites 2026-03-24 17:16:25 +00:00
Peter Steinberger
4518f6e820 test: speed up slack and telegram suites 2026-03-24 17:16:25 +00:00
Peter Steinberger
b11f4835e2 fix: suppress only recent whatsapp group echoes (#53624) (thanks @w-sss) 2026-03-24 10:10:48 -07:00
w-sss
0d4b47a14e fix(whatsapp): filter fromMe messages in groups to prevent infinite loop (#53386) 2026-03-24 10:10:48 -07:00
Peter Steinberger
f52752889b fix: audit clobbered config reads 2026-03-24 17:10:06 +00:00
Vincent Koc
14f1b65c70 test(memory): enable lower-interval heap snapshots 2026-03-24 10:09:06 -07:00
Tak Hoffman
2990446b21 fix: avoid duplicate orphaned subagent resumes 2026-03-24 12:08:44 -05:00
Peter Steinberger
44d5e6d672 fix(types): add workspace module shims 2026-03-24 10:07:14 -07:00
Vincent Koc
7eefddd0ed test(memory): clear browser and plugin caches between cases 2026-03-24 10:05:32 -07:00
Peter Steinberger
ba95d43e3c refactor: split feishu runtime and inspect secret resolution 2026-03-24 10:05:15 -07:00
Peter Steinberger
8e9e2d2f4e refactor(auth): unify external CLI credential sync 2026-03-24 10:03:00 -07:00
Peter Steinberger
27448c3113 refactor(msteams): split reply and reflection helpers 2026-03-24 10:02:49 -07:00
Peter Steinberger
9f47892bef refactor: centralize google API base URL handling 2026-03-24 10:01:22 -07:00
Tak Hoffman
129b1b5037 fix: return structured errors for subagent control send failures 2026-03-24 11:54:30 -05:00
giulio-leone
bbe6f7fdd9 fix(auth): protect fresher codex reauth state
- invalidate cached Codex CLI credentials when auth.json changes within the TTL window
- skip external CLI sync when the stored Codex OAuth credential is newer
- cover both behaviors with focused regression tests

Refs #53466

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-24 09:53:24 -07:00
Josh Lehman
559b3a5fd4 test: stabilize preaction process title assertion (#53808)
Regeneration-Prompt: |
  Current origin/main fails src/cli/program/preaction.test.ts because the
  test asserts on process.title directly inside Vitest, where that runtime
  interaction is not stable enough to observe the write reliably. Keep the
  production preaction behavior unchanged. Make the test verify that the
  hook assigns the expected title by wrapping process.title with a local
  getter/setter during each test and restoring the original descriptor
  afterward so other tests keep the real process object behavior.
2026-03-24 09:50:11 -07:00
Peter Steinberger
e727ad6898 fix(msteams): harden feedback reflection follow-ups 2026-03-24 09:50:04 -07:00
Peter Steinberger
72300e8fd0 docs: add changelog for PR #53675 (thanks @hpt) 2026-03-24 09:48:32 -07:00
Peter Steinberger
700ec2f25d fix: use v1beta for migrated google nano banana provider (#53757) (thanks @mahopan) 2026-03-24 09:47:59 -07:00
Maho Pan
2f238b5d7d fix(doctor): add missing baseUrl and models when migrating nano-banana apiKey to google provider
The legacy nano-banana-pro skill migration moves the Gemini API key to
models.providers.google.apiKey but does not populate the required baseUrl
and models fields on the provider entry. When the google provider object
is freshly created (no pre-existing config), the resulting config fails
Zod validation on write:

  Config validation failed: models.providers.google.baseUrl:
  Invalid input: expected string, received undefined

Fix: default baseUrl to 'https://generativelanguage.googleapis.com' and
models to [] when they are not already set, matching the defaults used
elsewhere in the codebase (embeddings-gemini, pdf-native-providers).

Fixes the 'doctor --fix' crash for users who only have a legacy
nano-banana-pro skill entry and no existing models.providers.google.
2026-03-24 09:47:21 -07:00
Han Pingtian
a1cb302c20 Feishu: avoid CLI startup failure on unresolved SecretRef 2026-03-24 09:47:18 -07:00
Tak Hoffman
ada703a7b4 fix: preserve session cleanup hooks after subagent announce 2026-03-24 11:44:10 -05:00
Tak Hoffman
79ef86c305 fix: preserve cleanup hooks after subagent register failure 2026-03-24 11:32:19 -05:00
Peter Steinberger
49e3f2db06 test: speed up core unit suites 2026-03-24 16:26:58 +00:00
Peter Steinberger
27b92f8335 test: speed up google and twitch suites 2026-03-24 16:26:58 +00:00
Peter Steinberger
332d2ebfe8 test: speed up whatsapp and signal suites 2026-03-24 16:26:58 +00:00
Peter Steinberger
5edba12f79 test: speed up discord slack telegram suites 2026-03-24 16:26:58 +00:00
Tak Hoffman
f0761b4914 fix(lockfile): sync discord dependency removal 2026-03-24 11:24:12 -05:00
Tak Hoffman
3e9ff16645 fix(discord): avoid bundling pi-ai runtime deps 2026-03-24 11:17:08 -05:00
Peter Steinberger
49ae71fa62 test: speed up signal and whatsapp extension suites 2026-03-24 15:57:16 +00:00
Peter Steinberger
86921b624c test: speed up telegram extension suites 2026-03-24 15:57:16 +00:00
Peter Steinberger
a29b9f2c20 test: speed up slack extension suites 2026-03-24 15:57:16 +00:00
Peter Steinberger
1d4db9920d test: speed up discord extension suites 2026-03-24 15:57:16 +00:00
Peter Steinberger
781295c14b refactor: dedupe test and script helpers 2026-03-24 15:48:35 +00:00
David Guttman
66e954858b add missing autoArchiveDuration to DiscordGuildChannelConfig type (#43427)
* add missing autoArchiveDuration to DiscordGuildChannelConfig type

The autoArchiveDuration field is present in the Zod schema
(DiscordGuildChannelSchema) and actively used at runtime in
threading.ts and allow-list.ts, but was missing from the
canonical TypeScript type definition.

Add autoArchiveDuration to DiscordGuildChannelConfig to align
the type with the schema and runtime usage.

* Discord: add changelog for config type fix (#43427) (thanks @davidguttman)

---------

Co-authored-by: Onur Solmaz <2453968+osolmaz@users.noreply.github.com>
2026-03-24 16:30:24 +01:00
David Guttman
aa91000a5d feat(discord): add autoThreadName 'generated' strategy (#43366)
* feat(discord): add autoThreadName 'generated' strategy

Adds async thread title generation for auto-created threads:
- autoThread: boolean - enables/disables auto-threading
- autoThreadName: 'message' | 'generated' - naming strategy
- 'generated' uses LLM to create concise 3-6 word titles
- Includes channel name/description context for better titles
- 10s timeout with graceful fallback

* Discord: support non-key auth for generated thread titles

* Discord: skip fallback auto-thread rename

* Discord: normalize generated thread title first content line

* Discord: split thread title generation helpers

* Discord: tidy thread title generation constants and order

* Discord: use runtime fallback model resolution for thread titles

* Discord: resolve thread-title model aliases

* Discord: fallback thread-title model selection to runtime defaults

* Agents: centralize simple completion runtime

* fix(discord): pass apiKey to complete() for thread title generation

The setRuntimeApiKey approach only works for full agent runs that use
authStorage.getApiKey(). The pi-ai complete() function expects apiKey
directly in options or falls back to env vars — it doesn't read from
authStorage.runtimeOverrides.

Fixes thread title generation for Claude/Anthropic users.

* fix(agents): return exchanged Copilot token from prepareSimpleCompletionModel

The recent thread-title fix (3346ba6) passes prepared.auth.apiKey to
complete(). For github-copilot, this was still the raw GitHub token
rather than the exchanged runtime token, causing auth failures.

Now setRuntimeApiKeyForCompletion returns the resolved token and
prepareSimpleCompletionModel includes it in auth.apiKey, so both the
authStorage path and direct apiKey pass-through work correctly.

* fix(agents): catch auth lookup exceptions in completion model prep

getApiKeyForModel can throw for credential issues (missing profile, etc).
Wrap in try/catch to return { error } for fail-soft handling rather than
propagating rejected promises to callers like thread title generation.

* Discord: strip markdown wrappers from generated thread titles

* Discord/agents: align thread-title model and local no-auth completion headers

* Tests: import fresh modules for mocked thread-title/simple-completion suites

* Agents: apply exchanged Copilot baseUrl in simple completions

* Discord: route thread runtime imports through plugin SDK

* Lockfile: add Discord pi-ai runtime dependency

* Lockfile: regenerate Discord pi-ai runtime dependency entries

* Agents: use published Copilot token runtime module

* Discord: refresh config baseline and lockfile

* Tests: split extension runs by isolation

* Discord: add changelog for generated thread titles (#43366) (thanks @davidguttman)

---------

Co-authored-by: Onur Solmaz <onur@textcortex.com>
Co-authored-by: Onur Solmaz <2453968+osolmaz@users.noreply.github.com>
2026-03-24 16:27:19 +01:00
Tak Hoffman
3f99a30163 fix: clean up attachments when replacing subagent runs 2026-03-24 10:21:15 -05:00
Tak Hoffman
0bda670d9a fix(ci): do not cancel in-progress bun runs on main 2026-03-24 10:19:59 -05:00
Peter Steinberger
d884676dd2 test: speed up whatsapp and shared test suites 2026-03-24 15:16:18 +00:00
Peter Steinberger
83bb647238 test: speed up telegram extension suites 2026-03-24 15:16:18 +00:00
Peter Steinberger
db4572b459 test: speed up slack extension suites 2026-03-24 15:16:18 +00:00
Peter Steinberger
88f49c27a0 test: speed up discord extension suites 2026-03-24 15:16:18 +00:00
Tak Hoffman
df2f900677 fix: clean up attachments for orphaned subagent runs 2026-03-24 10:05:53 -05:00
Tak Hoffman
075ece3dac fix(ci): do not cancel in-progress main runs 2026-03-24 10:02:25 -05:00
Tak Hoffman
938f8f4d83 fix: clean up attachments for released subagent runs 2026-03-24 09:56:20 -05:00
Harold Hunt
35de467b1a Telegram: recover General topic bindings (#53699)
Merged via squash.

Prepared head SHA: 546f0c8134
Co-authored-by: huntharo <5617868+huntharo@users.noreply.github.com>
Co-authored-by: huntharo <5617868+huntharo@users.noreply.github.com>
Reviewed-by: @huntharo
2026-03-24 10:51:26 -04:00
Tak Hoffman
8754d8e330 fix: ci 2026-03-24 09:25:28 -05:00
Sally O'Malley
91adc5e718 feat(cli): support targeting running containerized openclaw instances (#52651)
Signed-off-by: sallyom <somalley@redhat.com>
2026-03-24 10:17:17 -04:00
Tak Hoffman
dd11bdd003 fix: clean up attachments for killed subagent runs 2026-03-24 09:14:06 -05:00
Tak Hoffman
807daf54fe fix: finalize killed delete-mode subagent cleanup 2026-03-24 09:01:55 -05:00
Tak Hoffman
d7e48d4883 fix: ci 2026-03-24 08:40:55 -05:00
Neerav Makwana
f56a79f838 fix: report qmd status counts from real qmd manager (#53683) (thanks @neeravmakwana)
* fix(memory): report qmd status counts from index

* fix(memory): reuse full qmd manager for status

* fix(memory): harden qmd status manager lifecycle
2026-03-24 19:10:20 +05:30
Tak Hoffman
e6e2407cee fix: initialize plugins before killed subagent hooks 2026-03-24 08:27:50 -05:00
Tak Hoffman
b72d0c8459 fix: clean up failed non-thread subagent spawns 2026-03-24 08:26:59 -05:00
Ayaan Zaidi
0a04ef494d fix: merge explicit reply config overrides onto fresh config 2026-03-24 18:52:04 +05:30
Taras Lukavyi
ac07d8814a fix(secrets): prevent unresolved SecretRef from crashing embedded agent runs
Root cause: Telegram channel monitor captures config at startup before secrets
are resolved and passes it as configOverride into the reply pipeline. Since
getReplyFromConfig() uses configOverride directly (skipping loadConfig() which
reads the resolved runtime snapshot), the unresolved SecretRef objects propagate
into FollowupRun.run.config and crash runEmbeddedPiAgent().

Fix (defense in depth):
- get-reply.ts: detect unresolved SecretRefs in configOverride and fall back to
  loadConfig() which returns the resolved runtime snapshot
- message-tool.ts: try-catch around schema/description building at tool creation
  time so channel discovery errors don't crash the agent
- message-tool.ts: detect unresolved SecretRefs in pre-bound config at tool
  execution time and fall back to gateway secret resolution

Fixes: https://github.com/openclaw/openclaw/issues/45838
2026-03-24 18:52:04 +05:30
HollyChou
c84c630b4c fix(docs): correct json55 typo to json5 in IRC channel docs (#50831) (#50842)
Merged via squash.

Prepared head SHA: 0f743bf472
Co-authored-by: Hollychou924 <128659251+Hollychou924@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-24 16:20:49 +03:00
Mariano
922f4e66ea fix(agents): harden edit tool recovery (#52516)
Merged via squash.

Prepared head SHA: e23bde893a
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Reviewed-by: @mbelinky
2026-03-24 13:19:16 +01:00
Peter Steinberger
60cd98a841 test: defer slack bolt interop for helper-only suites 2026-03-24 09:34:23 +00:00
Peter Steinberger
b1b162fcdb test: harden threaded channel follow-ups 2026-03-24 09:24:29 +00:00
Peter Steinberger
43131dcc08 test: harden threaded shared-worker suites 2026-03-24 08:37:00 +00:00
Peter Steinberger
e7817ad12a test: continue vitest threads migration 2026-03-24 08:37:00 +00:00
Peter Steinberger
2833b27f52 test: continue vitest threads migration 2026-03-24 08:37:00 +00:00
Ayaan Zaidi
d41b92fff2 docs: update CONTRIBUTING.md 2026-03-24 13:36:38 +05:30
Val Alexander
b61a875d56 fix: widen installer regex allowlists and deduplicate safeExternalHref calls
- SAFE_GO_MODULE: allow uppercase in module paths (A-Z)
- SAFE_BREW_FORMULA: allow @ for versioned formulas (python@3.12)
- SAFE_UV_PACKAGE: allow extras [standard] and equality pins ==
- Cache safeExternalHref result in skills detail API key section
2026-03-24 01:46:33 -05:00
Val Alexander
cb58e45130 fix(security): resolve Aisle findings — skill installer validation, terminal sanitization, URL scheme allowlisting (#53471) thanks @BunsDev
Co-authored-by: BunsDev <68980965+BunsDev@users.noreply.github.com>
Co-authored-by: Nova <nova@openknot.ai>
2026-03-24 01:43:48 -05:00
Val Alexander
a710366e9e feat(ui): Control UI polish — skills revamp, markdown preview, agent workspace, macOS config tree (#53411) thanks @BunsDev
Co-authored-by: BunsDev <68980965+BunsDev@users.noreply.github.com>
Co-authored-by: Nova <nova@openknot.ai>
2026-03-24 01:21:13 -05:00
Peter Steinberger
ecb3aa7fe0 test: sync app chat model override expectation 2026-03-23 23:18:59 -07:00
Peter Steinberger
ff2e9a52ff fix: preserve deferred TUI history sync (#53130) (thanks @joelnishanth) 2026-03-23 23:18:59 -07:00
joelnishanth
cc8ed8d25b fix(tui): preserve user message during slow model responses (#53115)
When a local run ends with an empty final event while another run is active,
skip history reload to prevent clearing the user's pending message from the
chat log. This fixes the 'message disappears' issue with slow models like Ollama.
2026-03-23 23:18:59 -07:00
Tak Hoffman
5e9ea804d4 fix: finalize deferred subagent expiry cleanup 2026-03-24 01:12:54 -05:00
Peter Steinberger
5dc42dfb17 fix: format subagent registry test 2026-03-24 06:10:55 +00:00
Peter Steinberger
fd0fa97952 refactor: centralize plugin install config policy 2026-03-23 23:07:40 -07:00
Tak Hoffman
c3744fbfc4 fix: finalize resumed subagent cleanup give-ups 2026-03-24 01:06:39 -05:00
Peter Steinberger
a2d3b9f317 fix: unblock live harness provider discovery 2026-03-23 23:02:44 -07:00
Tak Hoffman
ab8c834aab fix: report dropped subagent announce queue deliveries 2026-03-24 00:54:46 -05:00
Tak Hoffman
0fc27409c0 fix: preserve direct subagent dispatch failures on abort 2026-03-24 00:47:01 -05:00
Peter Steinberger
687ce31f88 test: harden parallels smoke harness 2026-03-24 05:43:22 +00:00
Peter Steinberger
da10b6026a test: prune low-signal live model sweeps 2026-03-24 05:43:07 +00:00
Tak Hoffman
3689a82494 fix: preserve subagent ended hooks until runtime init 2026-03-24 00:31:48 -05:00
Peter Steinberger
0f84aac487 fix: stabilize matrix and teams ci assertions 2026-03-24 05:29:05 +00:00
Tao Xie
0b54b64fe7 fix(feishu): preserve docx block tree order (openclaw#40524)
Verified:
- pnpm install --frozen-lockfile
- pnpm build
- pnpm vitest run extensions/feishu/src/docx.test.ts

Co-authored-by: Tao Xie <7379039+TaoXieSZ@users.noreply.github.com>
2026-03-24 00:28:10 -05:00
Peter Steinberger
9082795b10 refactor(ui): extract chat model resolution state 2026-03-23 22:24:43 -07:00
Peter Steinberger
013385e5c2 refactor: polish trigger and manifest seams 2026-03-23 22:22:48 -07:00
Tak Hoffman
535b792808 fix: delete subagent runs after announce give-up 2026-03-24 00:20:22 -05:00
Peter Steinberger
ec7f564566 fix: clear production dependency advisories 2026-03-24 05:17:34 +00:00
Peter Steinberger
25d52b56a0 fix: keep slash command model qualification on rebase 2026-03-24 05:17:34 +00:00
Peter Steinberger
ef6821a4ca fix: restore teams sdk adapter contracts 2026-03-24 05:17:34 +00:00
Peter Steinberger
a8beb55b74 fix: fetch model catalog for slash command updates 2026-03-24 05:17:34 +00:00
Catalin Lupuleti
cbb11b3662 fix(plugins): address review feedback for Matrix recovery paths (#52899) 2026-03-23 22:16:10 -07:00
Catalin Lupuleti
489797ceaf fix(plugins): address review feedback for Matrix recovery paths (#52899)
1. Narrow loadConfigForInstall() to catch only INVALID_CONFIG errors,
   letting real failures (fs permission, OOM) propagate.
2. Assert allow array is properly cleaned in stale-cleanup test.
3. Add comment clarifying version-resolution is already addressed via
   the shared VERSION constant.
4. Run cleanStaleMatrixPluginConfig() during install so
   persistPluginInstall() → writeConfigFile() does not fail validation
   on stale Matrix load paths.
2026-03-23 22:16:10 -07:00
Catalin Lupuleti
3ae100a8d7 fix(plugins): make Matrix recovery paths tolerate stale plugin config (#52899) 2026-03-23 22:16:10 -07:00
Peter Steinberger
5c9e4cd30a refactor: clarify doctor repair flow 2026-03-23 22:12:56 -07:00
Tak Hoffman
e8ba55adbb test: add exact-stem subagent seam tests 2026-03-24 00:09:36 -05:00
Tak Hoffman
7464330a08 test: audit subagent seam coverage inventory 2026-03-24 00:09:36 -05:00
Peter Steinberger
2e1c88b728 refactor: tighten embedded prompt and sidecar guards 2026-03-23 22:09:22 -07:00
Sid Uppal
cd90130877 msteams: implement Teams AI agent UX best practices (#51808)
Migrates the Teams extension from @microsoft/agents-hosting to the official Teams SDK (@microsoft/teams.apps + @microsoft/teams.api) and implements Microsoft's AI UX best practices for Teams agents.

- AI-generated label on all bot messages (Teams native badge + thumbs up/down)
- Streaming responses in 1:1 chats via Teams streaminfo protocol
- Welcome card with configurable prompt starters on bot install
- Feedback with reflective learning (negative feedback triggers background reflection)
- Typing indicators for personal + group chats (disabled for channels)
- Informative status updates (progress bar while LLM processes)
- JWT validation via Teams SDK createServiceTokenValidator
- User-Agent: teams.ts[apps]/<sdk-version> OpenClaw/<version> on outbound requests
- Fix copy-pasted image downloads (smba.trafficmanager.net auth allowlist)
- Pre-parse auth gate (reject unauthenticated requests before body parsing)
- Reflection dispatcher lifecycle fix (prevent leaked dispatchers)
- Colon-safe session filenames (Windows compatibility)
- Cooldown cache eviction (prevent unbounded memory growth)

Closes #51806
2026-03-23 22:03:39 -07:00
Peter Steinberger
ea62655e19 fix: land cron heartbeat prompt suppression (#53152) (thanks @Protocol-zero-0) 2026-03-23 21:55:34 -07:00
Protocol-zero-0
9f863140d5 test(agents): cover additional heartbeat prompt triggers
Document that default-agent heartbeat prompt injection still applies to memory-triggered and triggerless runs while cron remains excluded.

Made-with: Cursor
2026-03-23 21:55:34 -07:00
Protocol-zero-0
9d21be4193 fix(agents): suppress heartbeat prompt for cron-triggered embedded runs
Prevent cron-triggered embedded runs from inheriting the default heartbeat prompt so non-cron session targets stop reading HEARTBEAT.md and polluting scheduled turns.

Made-with: Cursor
2026-03-23 21:55:34 -07:00
Val Alexander
da5b7ff0af style(ui): polish agent file preview and usage popovers (#53382)
* feat: make workspace links clickable in agent context card and files list

Updated the agent context card and files list to render workspace names as clickable links, allowing users to easily access the corresponding workspace files. This enhances usability by providing direct navigation to the workspace location.

* style(ui): polish markdown preview dialog

* style(ui): reduce markdown preview list indentation

* style(ui): update markdown preview dialog width and alignment

* fix(ui): open usage filter popovers toward the right

* style(ui): adjust positioning of usage filter and export popovers

* style(ui): update sidebar footer padding and modify usage header z-index

* style(ui): adjust positioning of usage filter popover to the left and export popover to the right

* style(ui): simplify workspace link rendering in agent context card

* UI: make workspace paths interactive buttons or plain text

Agent Context card workspace (Channels/Cron panels): replace non-interactive
<div> with a real <button> wired to onSelectPanel('files'), matching the
Overview panel pattern.

Core Files footer workspace: drop workspace-link class since the user is
already on the Files panel — keep as plain text.
2026-03-23 23:55:25 -05:00
HCL
be20eebc21 fix(ui): resolve model provider from catalog instead of stale session default
When the server returns a bare model name (e.g. "deepseek-chat") with
a session-level modelProvider (e.g. "zai"), the UI blindly prepends
the provider — producing "zai/deepseek-chat" instead of the correct
"deepseek/deepseek-chat". This causes "model not allowed" errors
when switching between models from different providers.

Root cause: resolveModelOverrideValue() and resolveDefaultModelValue()
in app-render.helpers.ts, plus the /model slash command handler in
slash-command-executor.ts, all call resolveServerChatModelValue()
which trusts the session's default provider. The session provider
reflects the PREVIOUS model, not the newly selected one.

Fix: for bare model names, create a raw ChatModelOverride and resolve
through normalizeChatModelOverrideValue() which looks up the correct
provider from the model catalog. Falls back to server-provided provider
only if the catalog lookup fails. All 3 call sites are fixed.

Closes #53031

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: HCL <chenglunhu@gmail.com>
2026-03-23 21:54:02 -07:00
Peter Steinberger
5ab3782215 fix: add config clobber forensics 2026-03-24 04:50:30 +00:00
giulio-leone
67c7f98c32 fix(doctor): skip service config repairs during updates
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-23 21:48:18 -07:00
giulio-leone
d8aada9d45 Preserve no-restart during update doctor fixes
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-23 21:48:18 -07:00
giulio-leone
3359dcfdcf fix(doctor): honor --fix in non-interactive mode
Ensure repair-mode doctor prompts auto-accept recommended fixes even when running non-interactively, while still requiring --force for aggressive rewrites.

This restores the expected behavior for upgrade/doctor flows that rely on 'openclaw doctor --fix --non-interactive' to repair stale gateway service configuration such as entrypoint drift after global updates.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-23 21:48:18 -07:00
sudie-codes
6e970010f7 msteams: add message edit and delete support (#49925)
- Add edit/delete action handlers with toolContext.currentChannelId
  fallback for in-thread edits/deletes without explicit target
- Add editMessageMSTeams/deleteMessageMSTeams to channel runtime
- Add updateActivity/deleteActivity to SendContext and MSTeamsTurnContext
- Extend content param with text/content/message fallback chain
- Update test mocks for new SendContext shape

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 23:42:04 -05:00
sudie-codes
9f5d286caf msteams: extract structured quote/reply context (#51647)
* msteams: extract structured quote/reply context from Teams HTML attachments

* msteams: address PR #51647 review feedback
2026-03-23 23:41:51 -05:00
Tak Hoffman
8c89d0e7cd tests: add boundary coverage for media delivery (#53361)
* tests: add boundary coverage for media delivery

* tests: isolate telegram outbound adapter transport

* tests: harden telegram webhook certificate assertion

* tests: fix guardrail false positives on rebased branch
2026-03-23 23:37:34 -05:00
Peter Steinberger
e864421d83 fix: unify live model auth gating 2026-03-24 04:28:53 +00:00
Peter Steinberger
e28e520379 refactor: simplify provider inference and zoned parsing helpers 2026-03-23 21:22:30 -07:00
Peter Steinberger
1bfef17825 ci: balance shards and reuse pr artifacts 2026-03-24 04:19:43 +00:00
Peter Steinberger
26365f7daf fix: hash inline scripts with data-src attributes 2026-03-23 21:14:55 -07:00
Peter Steinberger
0857447a5d fix: reject nonexistent zoned cron at-times 2026-03-23 21:14:55 -07:00
Peter Steinberger
69a317995d fix: fail closed when provider inference drops errored allowlists 2026-03-23 21:14:55 -07:00
Peter Steinberger
3a36fffc15 docs: update mac release automation guidance 2026-03-23 21:12:46 -07:00
Peter Steinberger
dc4d2ca263 build: prepare 2026.3.24 2026-03-23 21:05:59 -07:00
Peter Steinberger
ce49d8bca9 fix: verify global npm correction installs 2026-03-23 21:04:08 -07:00
Tak Hoffman
50d996a6ec tests: cron coverage and NO_REPLY delivery fixes (#53366)
* tools: extend seam audit inventory

* tools: audit cron seam coverage gaps

* test: add cron seam coverage tests

* fix: avoid marking NO_REPLY cron deliveries as delivered

* fix: clean up delete-after-run NO_REPLY cron sessions
2026-03-23 22:52:13 -05:00
Vincent Koc
6142fc1d94 fix(runtime): anchor bundled plugin npm staging to active node 2026-03-23 20:32:48 -07:00
Peter Steinberger
483dc90f05 test: harden linux runtime smoke guards 2026-03-24 03:23:52 +00:00
Peter Steinberger
dd2361a4c4 docs: refresh plugin-sdk api baseline 2026-03-24 03:20:51 +00:00
Ayaan Zaidi
17c1ee7716 fix: preserve command auth resolution errors on empty inferred allowlists 2026-03-24 08:38:27 +05:30
Peter Steinberger
630f1479c4 build: prepare 2026.3.23-2 2026-03-23 20:04:42 -07:00
Peter Steinberger
38137b0cf8 refactor: split tracked ClawHub update flows 2026-03-23 20:01:51 -07:00
Peter Steinberger
b4e392cf9d fix: unblock runtime-api smoke checks 2026-03-24 03:00:51 +00:00
Taras Lukavyi
7ffe7e4822 fix: populate currentThreadTs in threading tool context fallback for Telegram DM topics (#52217)
When a channel plugin lacks a custom buildToolContext (e.g. Telegram),
the fallback path in buildThreadingToolContext did not set currentThreadTs
from the inbound MessageThreadId. This caused resolveTelegramAutoThreadId
to return undefined, so message tool sends without explicit threadId
would route to the main chat instead of the originating DM topic.

Fixes #52217
2026-03-24 08:27:03 +05:30
Peter Steinberger
3ae5d33799 refactor: extract cron schedule and test runner helpers 2026-03-23 19:53:43 -07:00
Taras Lukavyi
d4e3babdcc fix: command auth SecretRef resolution (#52791) (thanks @Lukavyi)
* fix(command-auth): handle unresolved SecretRef in resolveAllowFrom

* fix(command-auth): fall back to config allowlists

* fix(command-auth): avoid duplicate resolution fallback

* fix(command-auth): fail closed on invalid allowlists

* fix(command-auth): isolate fallback resolution errors

* fix: record command auth SecretRef landing notes (#52791) (thanks @Lukavyi)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-24 08:21:30 +05:30
Tak Hoffman
5cd8d43af9 tests: improve boundary audit coverage and safety (#53080)
* tools: extend seam audit inventory

* tools: tighten seam audit heuristics

* tools: refine seam test matching

* tools: refine seam audit review heuristics

* style: format seam audit script

* tools: widen seam audit matcher coverage

* tools: harden seam audit coverage

* tools: tighten boundary audit matchers

* tools: ignore mocked import matches in boundary audit

* test: include native command reply seams in audit
2026-03-23 21:46:53 -05:00
Peter Steinberger
a3f2fbf5a2 refactor: harden extension runtime-api seams 2026-03-23 19:39:20 -07:00
Peter Steinberger
d8e77c423a test: isolate line jiti runtime smoke 2026-03-24 02:38:49 +00:00
Peter Steinberger
9e8abb468d fix: clean changelog merge duplication (#53224) (thanks @RolfHegr) 2026-03-23 19:38:04 -07:00
Peter Steinberger
0cbf6d5fed fix: land cron tz one-shot handling and prerelease config warnings (#53224) (thanks @RolfHegr) 2026-03-23 19:38:04 -07:00
Rolfy
9aac5582d6 fix(cron): make --tz work with --at for one-shot jobs
Previously, `--at` with an offset-less ISO datetime (e.g. `2026-03-23T23:00:00`)
was always interpreted as UTC, even when `--tz` was provided. This caused one-shot
jobs to fire at the wrong time.

Changes:
- `parseAt()` now accepts an optional `tz` parameter
- When `--tz` is provided with `--at`, offset-less datetimes are interpreted in
  that IANA timezone using Intl.DateTimeFormat
- Datetimes with explicit offsets (e.g. `+01:00`, `Z`) are unaffected
- Removed the guard in cron-edit that blocked `--tz` with `--at`
- Updated `--at` help text to mention `--tz` support
- Added 2 tests verifying timezone resolution and offset preservation
2026-03-23 19:38:04 -07:00
Peter Steinberger
8f9799307b test: print failed test lane output tails 2026-03-23 19:36:44 -07:00
Peter Steinberger
7f373823b0 refactor: separate exec policy and execution targets 2026-03-23 19:36:44 -07:00
Val Alexander
a96eded4a0 feat(csp): support inline script hashes in Control UI CSP (#53307) thanks @BunsDev
Co-authored-by: BunsDev <68980965+BunsDev@users.noreply.github.com>
Co-authored-by: Nova <nova@openknot.ai>
2026-03-23 21:35:33 -05:00
Peter Steinberger
e530865274 fix: preserve legacy clawhub skill updates (#53206) (thanks @drobison00) 2026-03-23 19:34:05 -07:00
Devin Robison
003752b9b3 Remove lower casing -- preserving prior behavior 2026-03-23 19:34:05 -07:00
Devin Robison
a339d706c1 Formatting fixes and remove trailing dash acceptance 2026-03-23 19:34:05 -07:00
Devin Robison
40071ea23e fix: tighten skill slug validation to ASCII-only 2026-03-23 19:34:05 -07:00
Peter Steinberger
2be3c996fb test: alias all plugin-sdk subpaths in line smoke 2026-03-24 02:31:59 +00:00
Peter Steinberger
d5917d37c5 test: allow line runtime api source fallback 2026-03-24 02:26:17 +00:00
Peter Steinberger
462a7a9ae6 test: allow realpath in shell planner assertions 2026-03-24 02:15:14 +00:00
Drickon
715b13547f fix(line): pre-export clashing symbols to prevent jiti TypeError on startup (#53221)
* fix(line): pre-export clashing symbols to prevent jiti TypeError on startup

When jiti CJS-transforms extensions/line/runtime-api.ts, both
export * from "openclaw/plugin-sdk/line-runtime" and the subsequent
export * from individual source files attempt to define the same 13
symbols via Object.defineProperty with configurable:false. The second
call throws TypeError: Cannot redefine property.

The root cause is that src/plugin-sdk/line-runtime.ts re-exports
these symbols directly from the extension source files, creating a
circular path back to the same files that runtime-api.ts star-exports.

Fix: add named pre-exports for all symbols that plugin-sdk/line-runtime
re-exports from this extension. Named exports register in jiti's
_exportNames map at transform time; the star re-export's hasOwnProperty
guard then skips them, preventing the duplicate Object.defineProperty.

export * reordering cannot fix this: _exportNames is only populated
by named exports, not by export *, so the guard never fires regardless
of order.

This is the same class of bug as the Matrix plugin crash described in
issues #50868, #52780, and #52891, and uses the same fix pattern as
PR #50919.

* test: add LINE runtime-api Jiti regression (#53221) (thanks @Drickon)

* test: stabilize LINE Jiti regression (#53221) (thanks @Drickon)

* test: harden LINE Jiti regression (#53221) (thanks @Drickon)

* chore: retrigger PR checks (#53221)

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-03-23 19:13:25 -07:00
Peter Steinberger
d8cef14eb1 fix: split exec and policy resolution for wrapper trust (#53134) (thanks @vincentkoc) 2026-03-23 19:04:04 -07:00
Peter Steinberger
21d480ed92 fix(infra): preserve blocked dispatch policy target
# Conflicts:
#	CHANGELOG.md
2026-03-23 19:04:04 -07:00
Vincent Koc
32e89b4687 Infra: preserve wrapper executable for multiplexer trust 2026-03-23 19:04:04 -07:00
Peter Steinberger
2d5f822ca1 fix: warn on same-base prerelease configs 2026-03-24 02:02:31 +00:00
Peter Steinberger
85ed1a8986 refactor: clean up ClawHub compatibility validation 2026-03-23 18:52:37 -07:00
Val Alexander
9dd0530b97 fix(ui): redact sensitive config values in diff panel
Use isSensitiveConfigPath to detect token/password/secret/apiKey paths
and display REDACTED_PLACEHOLDER instead of raw values in the config
diff panel, preventing credential exposure in the UI.
2026-03-23 20:48:08 -05:00
Val Alexander
21ac4b9a8a style(ui): continue ui clarity pass across theme, config, and usage (#53272) thanks @BunsDev
Co-authored-by: BunsDev <68980965+BunsDev@users.noreply.github.com>
Co-authored-by: Nova <nova@openknot.ai>
2026-03-23 20:45:43 -05:00
Peter Steinberger
ecc8fe5dc2 ci: rebalance sharded test lanes 2026-03-24 01:44:26 +00:00
Peter Steinberger
5b4fd6bf31 fix: use runtime version for ClawHub plugin API checks (#53157) (thanks @futhgar) 2026-03-23 18:41:18 -07:00
futhgar
447e074bf4 fix(plugins): use runtime version for plugin API compatibility check
OPENCLAW_PLUGIN_API_VERSION was hardcoded to "1.2.0" while ClawHub-published
plugins require >=2026.3.22, making all plugin installs via ClawHub fail with
"requires plugin API >=2026.3.22, but this OpenClaw runtime exposes 1.2.0".

Use resolveRuntimeServiceVersion() (already imported) to read the actual
version from package.json at runtime.

Fixes #53038
2026-03-23 18:41:18 -07:00
Peter Steinberger
d25ad66069 fix: resolve catalog-backed channel login 2026-03-23 18:25:44 -07:00
Peter Steinberger
69390daa51 test: cover config correction version warnings 2026-03-23 18:23:50 -07:00
Peter Steinberger
b4bda479a4 fix: normalize bundled plugin version reporting 2026-03-23 18:23:50 -07:00
Peter Steinberger
e9905fd696 fix: avoid fd warnings in lock exit cleanup 2026-03-24 01:01:59 +00:00
Val Alexander
6c44b2ea50 fix(cli): guard channel-auth against prototype-chain pollution and control-char injection
- Use hasOwnProperty + isBlockedObjectKey in isConfiguredAuthPlugin to
  prevent __proto__/constructor/prototype keys from matching config
- Sanitize plugin IDs with sanitizeForLog in ambiguity error messages
- Add regression test for __proto__ plugin ID
2026-03-23 19:58:16 -05:00
Val Alexander
c8f4b8533d fix(cli): auto-select login-capable auth channels (#53254) thanks @BunsDev
Co-authored-by: BunsDev <68980965+BunsDev@users.noreply.github.com>
Co-authored-by: Nova <nova@openknot.ai>
2026-03-23 19:54:46 -05:00
Peter Steinberger
5cb8e33a31 build: tag correction npm publishes as latest 2026-03-23 17:42:43 -07:00
Peter Steinberger
00d586b2ce test: reduce flaky gemini live probe coverage 2026-03-24 00:40:17 +00:00
Peter Steinberger
dc02a7520f test: stabilize moonshot and minimax live probes 2026-03-24 00:40:17 +00:00
Peter Steinberger
b8bf6c482e ci: cap channel shard workers 2026-03-24 00:38:51 +00:00
Peter Steinberger
9334015262 fix: ship bundled plugin runtime sidecars 2026-03-23 17:38:08 -07:00
Peter Steinberger
ffd722bc2c build: harden local release verification 2026-03-23 17:38:08 -07:00
Peter Steinberger
ce75f60ae9 fix: canonicalize malformed assistant replay content 2026-03-23 17:37:51 -07:00
Val Alexander
a5c35050f3 style: update border-radius values to use CSS variables for consistency across components (#53238) 2026-03-23 19:23:43 -05:00
Peter Steinberger
90fab48416 ci: stabilize sharded channel lanes 2026-03-24 00:21:50 +00:00
Peter Steinberger
e32148f1dd build: publish 2026.3.23 mac appcast 2026-03-23 17:01:06 -07:00
Peter Steinberger
2d19d2acb9 ci: shorten main critical path 2026-03-23 23:45:51 +00:00
Peter Steinberger
36de481541 docs: capture windows parallels install learnings 2026-03-23 16:40:53 -07:00
Peter Steinberger
ea99984e23 test: fix windows parallels agent quoting 2026-03-23 16:39:51 -07:00
Sally O'Malley
34dc712f36 changelog (#53229)
Signed-off-by: sallyom <somalley@redhat.com>
2026-03-23 19:37:59 -04:00
Peter Steinberger
a0483086b9 docs: fix 2026.3.22 and 2026.3.23 release notes 2026-03-23 16:27:14 -07:00
Peter Steinberger
ccfeecb688 test: harden parallels macos dashboard smoke 2026-03-23 16:13:20 -07:00
Peter Steinberger
a921b5bdff test: fix update-cli default path assertion 2026-03-23 23:05:25 +00:00
Peter Steinberger
725a2cc2ca test: expand gemini live transcript stripping 2026-03-23 23:01:22 +00:00
Peter Steinberger
67dbb1ad42 test: update command coverage 2026-03-23 23:01:22 +00:00
Peter Steinberger
d67efbfbd3 test: stabilize test isolation 2026-03-23 23:01:22 +00:00
Sally O'Malley
ae336d1602 Doctor: prune stale plugin allowlist and entry refs (#53187)
Signed-off-by: sallyom <somalley@redhat.com>
2026-03-23 18:58:54 -04:00
Vincent Koc
03231c0633 fix(auth): prevent stale auth store reverts (#53211) 2026-03-23 15:56:46 -07:00
Peter Steinberger
47bdc36831 test: make update-cli checkout path assertion platform-safe 2026-03-23 22:54:32 +00:00
Vincent Koc
19295994f3 fix(ci): stabilize whatsapp extension checks 2026-03-23 15:50:19 -07:00
Vincent Koc
6f5df14308 test(whatsapp): preserve harness session exports 2026-03-23 15:50:19 -07:00
Vincent Koc
7b03502175 test(whatsapp): preserve media test module exports 2026-03-23 15:50:19 -07:00
Vincent Koc
e87a64f8d0 test(whatsapp): preserve session exports in login coverage 2026-03-23 15:50:19 -07:00
Vincent Koc
dad6018230 test(whatsapp): stabilize login coverage in shared workers 2026-03-23 15:50:19 -07:00
Peter Steinberger
e808f8d6ef docs(changelog): reorder release highlights 2026-03-23 15:44:18 -07:00
Onur
6ab6e7a493 Release: document manual macOS asset upload (#53178)
* Release: document manual macOS asset upload

* Release: document macOS smoke-test mode
2026-03-23 23:19:52 +01:00
Robin Waslander
fb6588cb99 fix(diagnostics): redact credentials from cache-trace diagnostic output
Refs #53103
2026-03-23 22:59:09 +01:00
Onur
6e8d5cd578 Release: privatize macOS publish flow (#53166) 2026-03-23 22:35:51 +01:00
George Zhang
ade0182ae0 feat(modelstudio): add standard (pay-as-you-go) DashScope endpoints for Qwen (#43878)
Add Standard API Key auth methods for China (dashscope.aliyuncs.com)
and Global/Intl (dashscope-intl.aliyuncs.com) pay-as-you-go endpoints
alongside the existing Coding Plan (subscription) endpoints.

Also updates group label to 'Qwen (Alibaba Cloud Model Studio)' and
fixes glm-4.7 -> glm-5 in Coding Plan note messages.

Co-authored-by: wenmeng zhou <wenmengzhou@users.noreply.github.com>
2026-03-23 14:35:19 -07:00
Ayush Ojha
64f4df1886 fix: generalize api_error detection for fallback model triggering (#49611)
Co-authored-by: Ayush Ojha <7945279+ayushozha@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
2026-03-24 00:34:27 +03:00
Vincent Koc
3814f956d1 Infra: tighten shell-wrapper positional-argv allowlist matching (#53133)
* Infra: tighten shell carrier allowlist matching

* fix(security): tighten shell carrier allowlist matcher
2026-03-23 14:11:38 -07:00
theo674
dbe7da7684 fix: prevent delivery-mirror re-delivery and raise Slack chunk limit (#45489)
Merged via squash.

Prepared head SHA: c7664c7b6e
Co-authored-by: theo674 <261068216+theo674@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-24 00:11:19 +03:00
Altay
a53715e9d0 test(auth): align device scope expectations (#53151) 2026-03-23 23:56:02 +03:00
Vincent Koc
c1131ba7e0 fix(plugins): accept clawhub uninstall specs 2026-03-23 13:52:27 -07:00
betoblair
1c7e98de16 docs(feishu): replace botName with name in config examples (#52753)
Merged via squash.

Prepared head SHA: 5237726c9c
Co-authored-by: haroldfabla2-hue <229189334+haroldfabla2-hue@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-23 23:48:33 +03:00
Vincent Koc
34c57487b4 fix(subagents): recheck timed-out announce waits (#53127)
Recheck timed-out subagent announce waits against the latest runtime snapshot before announcing timeout, and keep that recheck best-effort so transient gateway failures do not suppress the announcement.

Co-authored-by: Val Alexander <68980965+BunsDev@users.noreply.github.com>
2026-03-23 15:36:49 -05:00
Onur Solmaz
bc95436a43 release: upload macos preflight artifacts (#53105)
* release: upload macos preflight artifacts

* release: speed up macos preflight

* release: use xlarge macos runner

* release: skip dmg path in macos preflight
2026-03-23 21:34:51 +01:00
Peter Steinberger
f698774324 build: finalize 2026.3.23 release 2026-03-23 13:08:33 -07:00
Nimrod Gutman
b98f3634c4 fix(memory): bootstrap lancedb runtime on demand (#53111)
Bootstrap LanceDB into plugin runtime state on first use for packaged/global installs, keep @lancedb/lancedb plugin-local, and add regression coverage for bundled, cached, retry, and Nix fail-fast runtime paths.

Co-authored-by: Val Alexander <68980965+BunsDev@users.noreply.github.com>
2026-03-23 15:07:24 -05:00
Vincent Koc
fd5496d1d3 docs(changelog): note skill secretref runtime fix 2026-03-23 13:05:32 -07:00
Vincent Koc
13e81870bb fix(agents): prefer runtime snapshot for skill secrets 2026-03-23 13:04:12 -07:00
Peter Steinberger
6c58277577 build: prepare 2026.3.23 2026-03-23 12:58:00 -07:00
Val Alexander
3e2b3bd2c5 Fix Control UI operator.read scope handling (#53110)
Preserve Control UI scopes through the device-auth bypass path, normalize implied operator device-auth scopes, ignore cached under-scoped operator tokens, and degrade read-backed main pages gracefully when a connection truly lacks operator.read.

Co-authored-by: Val Alexander <68980965+BunsDev@users.noreply.github.com>
2026-03-23 14:57:21 -05:00
Tak Hoffman
99c84294f3 ci: shard bun test lane 2026-03-23 14:51:47 -05:00
Onur Solmaz
01c1fc797f release: harden preflight workflows (#53087)
* release: harden preflight-only workflows

* release: require main for publish runs

* release: select xcode for macos workflow

* release: retry flaky macos preflight steps
2026-03-23 20:30:46 +01:00
Tak Hoffman
8670f2cead fix(openai-codex): bootstrap proxy on oauth refresh (openclaw#53078)
Verified:
- pnpm install --frozen-lockfile
- pnpm exec vitest run extensions/openai/openai-codex-provider.runtime.test.ts extensions/openai/openai-provider.test.ts
2026-03-23 14:08:04 -05:00
Vincent Koc
cdd797f943 docs(changelog): credit web search runtime fix 2026-03-23 12:01:17 -07:00
Peter Steinberger
e599c66277 test: harden canvas host undici isolation 2026-03-23 11:54:49 -07:00
Peter Steinberger
870b0d216a build: prepare 2026.3.23-beta.1 2026-03-23 11:54:49 -07:00
Vincent Koc
8fa91d283b fix(cli): preserve posix default git dir 2026-03-23 11:49:55 -07:00
Val Alexander
7a459045de fix packaged control ui asset lookup (#53081) 2026-03-23 13:45:04 -05:00
Peter Steinberger
323ae73b30 fix: bound tracked debounce key accounting 2026-03-23 11:33:06 -07:00
scoootscooob
708cccd43b docs(changelog): add missing recent fixes 2026-03-23 11:26:26 -07:00
scoootscooob
cdc8bac466 fix(discord): reply on native command auth failures (#53072) 2026-03-23 11:20:58 -07:00
Peter Steinberger
1b69d9ee1a fix: preserve debounce and followup ordering (#52998) (thanks @osolmaz) 2026-03-23 11:11:06 -07:00
Vincent Koc
9b7cfd0bea fix(inbound): bound tracked debounce keys 2026-03-23 11:11:06 -07:00
Vincent Koc
02e07a157d fix(reply): clear idle followup callbacks 2026-03-23 11:11:06 -07:00
Vincent Koc
3de42e946a fix(reply): preserve no-debounce inbound concurrency 2026-03-23 11:11:06 -07:00
Vincent Koc
7bc8e67d2a Update CHANGELOG.md 2026-03-23 11:11:06 -07:00
Vincent Koc
a35dcf608e fix(reply): refresh followup drain callbacks 2026-03-23 11:11:06 -07:00
Onur Solmaz
bcaadc39ea Telegram: fix fire-and-forget debounce order 2026-03-23 11:11:06 -07:00
Onur Solmaz
9a34a602bd Telegram: preserve inbound debounce order 2026-03-23 11:11:06 -07:00
Vincent Koc
b15462ebaf Revert "fix(plugins): enable bundled Brave web search plugin by default (#52072)"
This reverts commit 0ea3c4d5d8.
2026-03-23 11:04:02 -07:00
Peter Steinberger
80bd5ba728 fix(release): fail empty control ui tarballs 2026-03-23 11:03:14 -07:00
Quinn H.
0ea3c4d5d8 fix(plugins): enable bundled Brave web search plugin by default (#52072)
Brave is a bundled web search plugin but was missing from
BUNDLED_ENABLED_BY_DEFAULT, causing it to be filtered out during
provider resolution. This made web_search unavailable even when
plugins.entries.brave.enabled was configured.

Fixes #51937

Co-authored-by: Ubuntu <ubuntu@ip-172-26-10-234.us-west-2.compute.internal>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-23 11:01:02 -07:00
Vincent Koc
dd586d59ed fix(mistral): repair max-token defaults and doctor migration (#53054)
* fix(mistral): repair max-token defaults and doctor migration

* fix(mistral): add missing small-model repair cap
2026-03-23 10:57:56 -07:00
Peter Steinberger
ffb287e1de fix: harden update dev switch and refresh changelog 2026-03-23 10:56:35 -07:00
Nimrod Gutman
848414d7f2 docs(changelog): add channel catalog override note (#52988) (#53059) 2026-03-23 19:52:32 +02:00
TheRipper
36d6ba55e3 Release: fix npm release preflight under pnpm (#52985)
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-23 10:51:09 -07:00
Peter Steinberger
f9a7427e8e test: refresh thread-safe agent fixtures 2026-03-23 10:47:02 -07:00
Vincent Koc
2592eb0796 fix(gateway): guard openrouter auto pricing recursion (#53055) 2026-03-23 10:43:01 -07:00
Vincent Koc
3fe2f0a550 docs: fix CLI command tree, SDK import path, and tool group listing
- Remove non-existent 'secrets migrate' from CLI command tree
- Add actual secrets subcommands: audit, configure, apply
- Add missing plugin subcommands: inspect, uninstall, update, marketplace list
- Fix plugins info -> inspect (actual command name)
- Add message send and broadcast subcommands to command tree
- Remove misleading deprecated import from sdk-overview
- Add sessions_yield and subagents to group:sessions tool group docs
- Fix formatting
2026-03-23 10:40:41 -07:00
Jamil Zakirov
921a147196 Agents: fix runtime web_search provider selection (#53020)
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-23 10:30:45 -07:00
scoootscooob
df18063260 fix(clawhub): preserve XDG auth path on macOS 2026-03-23 10:21:28 -07:00
Vincent Koc
d1c95c84b5 docs: fix nav ordering, missing pages, and stale model references
- Sort providers alphabetically in docs.json nav
- Sort channels alphabetically in docs.json nav (slack before synology-chat)
- Add install/migrating-matrix to Maintenance nav section (was orphaned)
- Remove zh-CN/plugins/architecture from nav (file does not exist)
- Add Voice Call to channels index page
- Add missing providers to providers index (DeepSeek, GitHub Copilot, OpenCode Go, Synthetic)
- Sort providers index alphabetically
- Update stale claude-3-5-sonnet model reference to claude-sonnet-4-6 in webhook docs
2026-03-23 10:20:15 -07:00
scoootscooob
5b09463bc3 fix(clawhub): honor macOS auth config path (#53034) 2026-03-23 10:14:23 -07:00
Vincent Koc
b4dd600b37 fix(browser): reuse running loopback browser after probe miss 2026-03-23 10:05:26 -07:00
Vincent Koc
a1df10caac fix(config): ignore stale plugin allow entries 2026-03-23 09:57:46 -07:00
Luke
d98e3a1ea9 Tests: fix fresh-main regressions (#53011)
* Tests: fix fresh-main regressions

* Tests: avoid chat notice cache priming

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-23 09:54:50 -07:00
Vincent Koc
c55d4f63eb fix(release): raise npm pack size budget 2026-03-23 09:54:28 -07:00
Vincent Koc
071de383ff fix(clawhub): resolve auth token for skill browsing (#53017)
* fix(clawhub): resolve auth token for skill browsing

* docs(changelog): note clawhub skill auth fix
2026-03-23 09:53:40 -07:00
Vincent Koc
d5dc6b6573 fix(gateway): require auth for canvas routes 2026-03-23 09:31:40 -07:00
Vincent Koc
b0ce53a79c fix(voice-call): stabilize plivo v2 replay keys 2026-03-23 09:31:40 -07:00
Vincent Koc
50f6a2f136 fix(gateway): require admin for agent session reset 2026-03-23 09:31:39 -07:00
Nimrod Gutman
041c47419f fix(channels): preserve external catalog overrides (#52988)
* fix(channels): preserve external catalog overrides

* fix(channels): clarify catalog precedence

* fix(channels): respect overridden install specs
2026-03-23 18:08:17 +02:00
Vincent Koc
29ad211e76 fix(plugins): unblock Discord/Slack message tool sends and Feishu media (#52991)
* fix(plugins): unblock Discord and Slack message tool payloads

* docs(changelog): note Discord Slack and Feishu message fixes
2026-03-23 09:04:57 -07:00
Vincent Koc
83e86482c6 docs(changelog): note release and matrix fixes 2026-03-23 08:51:46 -07:00
Vincent Koc
93df5f613e fix(gateway): avoid probe false negatives after connect 2026-03-23 08:49:31 -07:00
Vincent Koc
1e5f38a1a8 fix(matrix): avoid duplicate runtime api exports 2026-03-23 08:46:58 -07:00
Vincent Koc
beadd4c553 fix(gateway): harden supervised lock and browser attach readiness 2026-03-23 08:42:00 -07:00
Nimrod Gutman
b84a130788 fix(release): preserve shipped channel surfaces in npm tar (#52913)
* fix(channels): ship official channel catalog (#52838)

* fix(release): keep shipped bundles in npm tar (#52838)

* build(release): fix rebased release-check helpers (#52838)
2026-03-23 17:39:22 +02:00
Vincent Koc
7299b42e2a fix(config): keep built-in auto-enable idempotent 2026-03-23 08:34:37 -07:00
Vincent Koc
2615402a2b Update CHANGELOG.md 2026-03-23 08:29:02 -07:00
Vincent Koc
e68cbea5b4 fix(config): keep built-in channels out of plugin allowlists (#52964)
* fix(config): keep built-in channels out of plugin allowlists

* docs(changelog): note doctor whatsapp allowlist fix

* docs(changelog): move doctor whatsapp fix to top
2026-03-23 08:26:51 -07:00
Vincent Koc
70b235f312 fix(release): ship bundled plugins in pack artifacts 2026-03-23 08:22:00 -07:00
Vincent Koc
31675d65d4 fix(agents): preserve anthropic thinking block order (#52961) 2026-03-23 08:18:15 -07:00
Tak Hoffman
6872e079e6 Improve PR template regression prompts 2026-03-23 10:12:59 -05:00
Vincent Koc
8a8ed34716 Update CHANGELOG.md 2026-03-23 08:10:57 -07:00
Onur Solmaz
8ed33c2aff release: automate macOS publishing (#52853)
* release: automate macOS publishing

* release: keep mac appcast in openclaw repo

* release: add preflight-only release workflow runs

* release: keep appcast updates manual

* release: generate signed appcast as workflow artifact

* release: require preflight before publish

* release: require mac app for every release

* docs: clarify every release ships mac app

* release: document Sparkle feed and SHA rules

* release: keep publish flow tag-based

* release: stabilize mac appcast flow

* release: document local mac fallback
2026-03-23 16:04:53 +01:00
Vincent Koc
e9078b3ff6 fix(msteams): isolate probe test env credentials 2026-03-23 08:00:54 -07:00
Sathvik Veerapaneni
d2e8ed3632 fix: keep session transcript pointers fresh after compaction (#50688)
Co-authored-by: Frank Yang <frank.ekn@gmail.com>
2026-03-23 22:58:07 +08:00
Robin Waslander
dd132ea77b release: add changelog for control UI tarball check 2026-03-23 15:40:36 +01:00
evann
c278e8d7a8 release-check: include stderr/stdout when npm pack fails 2026-03-23 15:40:35 +01:00
evann
fcafd8d4de release: verify control-ui assets are included in npm tarball 2026-03-23 15:40:34 +01:00
Peter Steinberger
b2bf508dc5 docs: refresh generated config baseline 2026-03-23 14:38:57 +00:00
Peter Steinberger
05e31eb9ff test: stabilize channel ci gate 2026-03-23 14:32:10 +00:00
Peter Steinberger
3725b38335 fix: restore ci gates 2026-03-23 14:32:10 +00:00
wangchunyue
4e849ac127 fix: ensure env proxy dispatcher before MiniMax and OpenAI Codex OAuth flows (openclaw#52228)
Verified:
- pnpm install --frozen-lockfile
- NPM_CONFIG_CACHE=/tmp/openclaw-npm-cache-52228 pnpm build
- pnpm check
- pnpm test:macmini (failed on inherited pre-existing plugin contract test: src/plugins/contracts/registry.contract.test.ts missing deepseek in bundled provider contract registry outside this PR surface)

Co-authored-by: openperf <80630709+openperf@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-23 09:26:34 -05:00
Peter Steinberger
8fdb9194d2 build: sync lockfile for mattermost plugin 2026-03-23 12:59:15 +00:00
Peter Steinberger
b393effba6 test: harden channel suite isolation 2026-03-23 12:57:43 +00:00
Peter Steinberger
949d6be1d1 test: fix status plugin pagination expectation 2026-03-23 05:51:19 -07:00
Peter Steinberger
383c61e305 test: stabilize gateway thread harness 2026-03-23 05:44:34 -07:00
Onur Solmaz
cd7d49b48e CI: remove npm release preview workflow (#52825)
* CI: remove npm release preview workflow

* Docs: align release maintainer skill with manual publish

* Docs: expand release maintainer skill flow
2026-03-23 13:35:57 +01:00
Peter Steinberger
41850c3880 refactor: isolate attempt context engine thread helpers 2026-03-23 05:27:44 -07:00
Peter Steinberger
76dc854963 test: remove replaced spawn-workspace monolith 2026-03-23 05:21:59 -07:00
Peter Steinberger
4e661d5c4b test: split attempt spawn-workspace thread fixtures 2026-03-23 05:21:59 -07:00
Peter Steinberger
08b5ba1a12 test: reset line webhook mocks between cases 2026-03-23 12:13:11 +00:00
Ayaan Zaidi
69b9e44762 fix: declare typebox runtime dep for mattermost plugin 2026-03-23 17:39:30 +05:30
Peter Steinberger
9f2330363e fix: refactor deepseek bundled plugin (#48762) (thanks @07akioni) 2026-03-23 05:06:07 -07:00
Bakhtier Sizhaev
a0cb443aa3 fix: document Telegram asDocument alias (#52461) (thanks @bakhtiersizhaev)
* feat(telegram): add asDocument param to message tool

Adds `asDocument` as a user-facing alias for the existing `forceDocument`
parameter in the message tool. When set to `true`, media files (images,
videos, GIFs) are sent via `sendDocument` instead of `sendPhoto`/
`sendVideo`/`sendAnimation`, preserving the original file quality
without Telegram compression.

This is useful when agents need to deliver high-resolution images or
uncompressed files to users via Telegram.

`asDocument` is intentionally an alias rather than a replacement — the
existing `forceDocument` continues to work unchanged.

Changes:
- src/agents/tools/message-tool.ts: add asDocument to send schema
- src/agents/tools/telegram-actions.ts: OR asDocument into forceDocument
- src/infra/outbound/message-action-runner.ts: same OR logic for outbound path
- extensions/telegram/src/channel-actions.ts: read and forward asDocument
- src/channels/plugins/actions/actions.test.ts: add test case

* fix: restore channel-actions.ts to main version (rebase conflict fix)

* fix(test): match asDocument test payload to actual params structure

* fix(telegram): preserve forceDocument alias semantics

* fix: document Telegram asDocument alias (#52461) (thanks @bakhtiersizhaev)

---------

Co-authored-by: Бахтиер Сижаев <bkh@MacBook-Air.local>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-23 17:32:46 +05:30
Peter Steinberger
47db5abece test: inject thread-safe base seams 2026-03-23 04:59:15 -07:00
Peter Steinberger
8fd2fa13c6 test: avoid repo-root perf profile artifacts 2026-03-23 11:51:26 +00:00
Peter Steinberger
f98811a67c fix: harden parallels smoke agent invocation 2026-03-23 04:51:07 -07:00
Peter Steinberger
b3844d920a test: decouple vitest config checks from ambient env 2026-03-23 11:48:19 +00:00
Peter Steinberger
e84ca730a3 test: stabilize e2e module isolation 2026-03-23 11:46:54 +00:00
Peter Steinberger
4f92eaad37 test: inject image-tool provider deps for raw threads 2026-03-23 04:40:58 -07:00
Peter Steinberger
a6c7ad84ba build: prepare 2026.3.23 2026-03-23 04:40:13 -07:00
Peter Steinberger
6a3d4f9fad test: isolate pi model and reset-model thread fixtures 2026-03-23 04:36:06 -07:00
Peter Steinberger
6bcd9a801a test: inject thread-safe gateway and ACP seams 2026-03-23 04:34:42 -07:00
Peter Steinberger
d841d02439 ci: stabilize windows and bun unit lanes 2026-03-23 11:28:25 +00:00
Peter Steinberger
adf63eba9f test: raise timeout for slow provider auth normalization 2026-03-23 11:19:26 +00:00
Peter Steinberger
f182c3a292 test: inject thread-safe deps for agent tools 2026-03-23 04:16:53 -07:00
Peter Steinberger
fc9739313c test: harden channel suite isolation 2026-03-23 11:09:12 +00:00
Peter Steinberger
e7d11f6c33 build: prepare 2026.3.22 2026-03-23 04:08:15 -07:00
Peter Steinberger
6f048f59cb test: stabilize trigger handling and hook e2e tests 2026-03-23 11:06:24 +00:00
Peter Steinberger
b9efba1faf test: target gemini 3.1 flash alias 2026-03-23 10:59:12 +00:00
Peter Steinberger
4dcc39c25c test: snapshot ci timeout investigation 2026-03-23 10:44:34 +00:00
Peter Steinberger
6e012d7feb test: inject model runtime hooks for thread-safe tests 2026-03-23 03:25:42 -07:00
Peter Steinberger
2df10e81c8 test: isolate server-context browser harness imports 2026-03-23 03:23:30 -07:00
Peter Steinberger
9d3d7f9e65 fix: restart windows gateway after npm update 2026-03-23 10:20:59 +00:00
Peter Steinberger
a0ad47440a test: stabilize live provider docker probes 2026-03-23 10:16:06 +00:00
Peter Steinberger
3ff2f85bad fix: stop browser server tests from launching real chrome 2026-03-23 03:15:02 -07:00
Peter Steinberger
203eebec2f test: split pi embedded model thread fixtures 2026-03-23 02:54:10 -07:00
Peter Steinberger
399fae33ca test: add parallels npm update smoke 2026-03-23 09:47:35 +00:00
Peter Steinberger
8067ae50fa fix: restore provider runtime lazy boundary 2026-03-23 09:35:18 +00:00
Peter Steinberger
d8d545bac1 build: prepare 2026.3.22-beta.1 2026-03-23 02:34:30 -07:00
Julia Bush
e94ebfa084 fix: harden gateway SIGTERM shutdown (#51242) (thanks @juliabush)
* fix: increase shutdown timeout to avoid SIGTERM hang

* fix(telegram): abort polling fetch on shutdown to prevent SIGTERM hang

* fix(gateway): enforce hard exit on shutdown timeout for SIGTERM

* fix: tighten gateway shutdown watchdog

* fix: harden gateway SIGTERM shutdown (#51242) (thanks @juliabush)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-23 15:01:42 +05:30
Penchan
95fec668a0 fix: preserve Telegram reply context text (#50500) (thanks @p3nchan)
* fix: guard Telegram reply context text (#50500) (thanks @p3nchan)

* fix: preserve Telegram reply caption fallback (#50500) (thanks @p3nchan)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-23 14:54:39 +05:30
Peter Steinberger
75b65c2a35 fix: restore provider runtime lazy boundary 2026-03-23 09:24:20 +00:00
Peter Steinberger
7a92d43d9a test: isolate pi embedded model thread fixtures 2026-03-23 02:22:31 -07:00
Peter Steinberger
9516c72618 docs: sync generated release baselines 2026-03-23 09:19:05 +00:00
Matt Van Horn
988bd782f7 fix: restore Telegram topic announce delivery (#51688) (thanks @mvanhorn)
When `replyLike.text` or `replyLike.caption` is an unexpected
non-string value (edge case from some Telegram API responses),
the reply body was coerced to "[object Object]" via string
concatenation. Add a `typeof === "string"` guard to gracefully
fall back to empty string, matching the existing pattern used
for `quoteText` in the same function.

Co-authored-by: Penchan <penchan@penchan.co>
2026-03-23 14:48:46 +05:30
Peter Steinberger
7ba28d6dba fix: repair runtime seams after rebase 2026-03-23 09:14:17 +00:00
Peter Steinberger
2a06097184 test: update codex test fixtures to gpt-5.4 2026-03-23 02:14:00 -07:00
Ayaan Zaidi
8f8b79496f fix: keep message-tool buttons optional for Telegram and Mattermost (#52589) (thanks @tylerliu612) 2026-03-23 14:43:41 +05:30
liuyang
bf12835995 fix(telegram): make buttons schema optional in message tool
The Telegram plugin injects a `buttons` property into the message tool
schema via `createMessageToolButtonsSchema()`, but without wrapping it
in `Type.Optional()`. This causes TypeBox to include `buttons` in the
JSON Schema `required` array.

In isolated sessions (e.g. cron jobs) where no `currentChannel` is set,
all plugin schemas are merged into the message tool. When the LLM calls
the message tool without a `buttons` parameter, AJV validation fails
with: `buttons: must have required property 'buttons'`.

Wrap the buttons schema in `Type.Optional()` so it is not required.
2026-03-23 14:43:41 +05:30
RichardCao
a835c200f3 fix(status): recompute fallback context window (#51795)
* fix(status): recompute fallback context window

* fix(status): keep live context token caps on fallback

* fix(status): preserve fallback runtime context windows

* fix(status): preserve configured fallback context caps

* fix(status): keep provider-aware transcript context lookups

* fix(status): preserve explicit fallback context caps

* fix(status): clamp fallback configured context caps

* fix(status): keep raw runtime slash ids

* fix(status): refresh plugin-sdk api baseline

* fix(status): preserve fallback context lookup

* test(status): refresh plugin-sdk api baseline

* fix(status): keep runtime slash-id context lookup

---------

Co-authored-by: create <create@createdeMacBook-Pro.local>
Co-authored-by: Frank Yang <frank.ekn@gmail.com>
Co-authored-by: RichardCao <RichardCao@users.noreply.github.com>
2026-03-23 17:08:48 +08:00
Frank Yang
b186d9847c fix(memory-core): register memory tools independently to prevent coupled failure (#52668)
Merged via admin squash because current required CI failures are inherited from base and match latest `main` failures outside this PR's `memory-core` surface.

Prepared head SHA: df7f968581
Co-authored-by: artwalker <44759507+artwalker@users.noreply.github.com>
Reviewed-by: @frankekn
2026-03-23 17:05:37 +08:00
Peter Steinberger
a381e0d115 style: format image-generation runtime tests 2026-03-23 09:04:36 +00:00
Peter Steinberger
a4367eb656 test: finish no-isolate suite hardening 2026-03-23 02:02:03 -07:00
Peter Steinberger
4ea014d581 fix: align websocket stream fallback types 2026-03-23 08:59:16 +00:00
Peter Steinberger
fb602c9b02 test: harden ci isolated mocks 2026-03-23 08:53:35 +00:00
Peter Steinberger
aa02b86a9e docs: clarify unreleased breaking changes 2026-03-23 01:47:38 -07:00
Peter Steinberger
d44a399ae0 fix: alphabetize web search provider listings 2026-03-23 01:46:47 -07:00
Vincent Koc
5637f9b516 fix(changelog): note windows media path guardrails (#52738) 2026-03-23 01:41:53 -07:00
Peter Steinberger
c4420c0324 docs: reorder unreleased changelog 2026-03-23 01:35:20 -07:00
Peter Steinberger
7cee097df9 test: harden no-isolate mocked module resets 2026-03-23 01:33:47 -07:00
Peter Steinberger
abf2157b18 fix: sync agent and autoreply e2e updates 2026-03-23 01:33:40 -07:00
Peter Steinberger
f64f3fdb53 test: isolate base vitest thread blockers 2026-03-23 01:26:31 -07:00
Vincent Koc
8b02ef1332 fix(android): gate canvas bridge to trusted pages (#52722)
* fix(android): gate canvas bridge to trusted pages

* fix(changelog): note android canvas bridge gating

* Update apps/android/app/src/main/java/ai/openclaw/app/node/CanvasActionTrust.kt

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* fix(android): snapshot canvas URL on UI thread

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-03-23 01:24:51 -07:00
Peter Steinberger
94f397bc5f test: stop leaking image workspace temp dirs 2026-03-23 01:23:10 -07:00
Onur Solmaz
fe459c9084 ACPX: align pinned runtime version (#52730)
* ACPX: align pinned runtime version

* ACPX: drop version example from help text
2026-03-23 09:21:57 +01:00
Vincent Koc
6c60a3773a chore(plugins): remove opik investigation checkpoints 2026-03-23 01:21:19 -07:00
Peter Steinberger
db5369f5f9 test: harden isolated test mocks 2026-03-23 01:13:08 -07:00
Peter Steinberger
a60672b708 test: fix channel summary registry setup 2026-03-23 08:07:44 +00:00
Vincent Koc
d22279d2e8 fix(plugins): preserve live hook registry during gateway runs 2026-03-23 01:05:13 -07:00
Peter Steinberger
9105b3723d test: harden no-isolate test module resets 2026-03-23 01:02:16 -07:00
Peter Steinberger
771a78cc77 test: stabilize ci test harnesses 2026-03-23 07:58:34 +00:00
Peter Steinberger
dc90d3b1d3 refactor(media): share local file access guards 2026-03-23 00:58:23 -07:00
Peter Steinberger
eac93507c3 fix(browser): enforce node browser proxy allowProfiles 2026-03-23 00:56:44 -07:00
Peter Steinberger
3fd5d13315 test: fix ci docs drift and bun qr exit handling 2026-03-23 07:41:23 +00:00
Peter Steinberger
ff54c02b7d test: narrow live transcript scaffolding strip 2026-03-23 07:41:18 +00:00
Peter Steinberger
fe5819887b refactor(gateway): centralize discovery target handling 2026-03-23 00:38:31 -07:00
Peter Steinberger
9fbb840c79 docs(changelog): add Windows media security fix 2026-03-23 00:37:45 -07:00
Peter Steinberger
5f05c92922 test: harden no-isolate reply teardown 2026-03-23 00:37:32 -07:00
Peter Steinberger
93880717f1 fix(media): harden secondary local path seams 2026-03-23 00:29:46 -07:00
Peter Steinberger
4fd7feb0fd fix(media): block remote-host file URLs in loaders 2026-03-23 00:29:46 -07:00
Peter Steinberger
abbd1b6b8a feat: add slash plugin installs 2026-03-23 00:29:20 -07:00
Peter Steinberger
deecf68b59 fix(gateway): fail closed on unresolved discovery endpoints 2026-03-23 00:27:37 -07:00
Peter Steinberger
0b58829364 test: fix ci env-sensitive assertions 2026-03-23 07:27:03 +00:00
Vincent Koc
83e715cdaa fix(plugin-sdk): normalize hashed diagnostic event exports 2026-03-23 00:24:47 -07:00
Vincent Koc
b23e9c577d fix(plugin-sdk): resolve hashed diagnostic events chunks 2026-03-23 00:18:19 -07:00
Peter Steinberger
04c69ea3a0 refactor: reuse canonical setup bootstrap profile 2026-03-23 00:15:55 -07:00
Peter Steinberger
6686f1cb2c refactor: centralize bootstrap profile handling 2026-03-23 00:15:55 -07:00
scoootscooob
43557668d2 Infra: support shell carrier allow-always approvals 2026-03-23 00:14:58 -07:00
Peter Steinberger
fe3663a9fe refactor: remove embedded runner cwd mutation 2026-03-23 00:11:55 -07:00
Peter Steinberger
65405edaab docs: reorder unreleased changelog by user impact 2026-03-23 00:11:08 -07:00
Peter Steinberger
ddf823036b fix: harden Windows Parallels smoke installs 2026-03-23 07:07:50 +00:00
Peter Steinberger
4a26f10f68 docs: sync minimax m2.7 references 2026-03-23 00:02:35 -07:00
Peter Steinberger
80cd8cd6be refactor: unify minimax model and failover live policies 2026-03-23 00:02:35 -07:00
Peter Steinberger
ebc2b711ea docs(synology-chat): clarify multi-account webhook paths 2026-03-23 00:01:29 -07:00
Peter Steinberger
937f78b69f refactor(synology-chat): type startup webhook path policy 2026-03-23 00:01:07 -07:00
Peter Steinberger
535263572e fix(tlon): unify settings reconciliation semantics 2026-03-22 23:59:04 -07:00
Peter Steinberger
a600c72ed7 fix: bind bootstrap setup codes to node profile 2026-03-22 23:57:15 -07:00
scoootscooob
4580d585ff Gateway: resolve fallback plugin context lazily 2026-03-22 23:52:21 -07:00
scoootscooob
52b92f2973 Test: isolate qr dashboard integration suite 2026-03-22 23:52:21 -07:00
Peter Steinberger
47186c50a2 fix(ci): restore stale guardrails and baselines 2026-03-23 06:50:23 +00:00
ruochen
b62fed0ea7 Docs: align MiniMax examples with M2.7 2026-03-22 23:44:11 -07:00
Peter Steinberger
202b588db5 fix: harden plugin docker e2e 2026-03-22 23:42:34 -07:00
Peter Steinberger
d2a1b24b83 test: honor env auth in gateway live probes 2026-03-23 06:42:09 +00:00
Peter Steinberger
ed614938d7 test(voice-call): accept oversize webhook socket resets 2026-03-23 06:37:57 +00:00
Peter Steinberger
37c2166f52 test: finish base vitest thread fixture fixes 2026-03-22 23:37:31 -07:00
Peter Steinberger
3fac0d11fa test: fix base vitest thread regressions 2026-03-22 23:37:31 -07:00
Peter Steinberger
2349693924 docs: credit nexrin in synology changelog 2026-03-22 23:36:30 -07:00
Peter Steinberger
980940aa58 fix(synology-chat): fail closed shared webhook paths 2026-03-22 23:34:21 -07:00
Peter Steinberger
651dc7450b fix(voice-call): harden webhook pre-auth guards 2026-03-22 23:33:30 -07:00
Peter Steinberger
2467fa4c5b fix: normalize scoped vitest filter paths 2026-03-22 23:32:22 -07:00
Peter Steinberger
dda347eda3 refactor: dedupe synology config schema 2026-03-22 23:32:22 -07:00
Peter Steinberger
c42cb1ca66 refactor: audit synology dangerous name matching 2026-03-22 23:32:22 -07:00
Peter Steinberger
fb6454c543 refactor: narrow synology legacy name lookup 2026-03-22 23:32:21 -07:00
Peter Steinberger
677a821a2f refactor: centralize synology dangerous name matching 2026-03-22 23:32:21 -07:00
Peter Steinberger
ea800dd4ef refactor: clarify synology delivery identity names 2026-03-22 23:32:21 -07:00
scoootscooob
6c1ea41472 Docs: refresh config baseline for Synology Chat 2026-03-22 23:32:03 -07:00
Peter Steinberger
f3de580ca1 test: trim docker live auth mounts 2026-03-23 06:31:41 +00:00
Peter Steinberger
957fff443f fix: include .npmrc in onboard docker build 2026-03-23 06:27:58 +00:00
Peter Steinberger
cef7d14861 refactor(exec): rename wrapper plans for trust semantics 2026-03-22 23:18:54 -07:00
Peter Steinberger
0b40ec38ab refactor(exec): share wrapper trust planning 2026-03-22 23:18:54 -07:00
Peter Steinberger
6ba5595004 refactor(exec): make dispatch wrapper semantics spec-driven 2026-03-22 23:18:54 -07:00
Peter Steinberger
c041f8587b refactor(exec): split wrapper resolution modules 2026-03-22 23:18:54 -07:00
Peter Steinberger
97abc6db55 docs: clarify sessions_spawn ACP vs subagent policies 2026-03-22 23:08:06 -07:00
Peter Steinberger
7ade3553b7 fix: gate synology chat reply name matching 2026-03-22 23:06:38 -07:00
Peter Steinberger
55ad5d7bd7 fix(security): harden explicit-proxy SSRF pinning 2026-03-22 23:05:42 -07:00
Peter Steinberger
f52eb934d6 fix(security): unify dispatch wrapper approval hardening 2026-03-22 23:01:49 -07:00
Peter Steinberger
72e58ca260 test(models): refresh example model fixtures 2026-03-22 23:00:18 -07:00
Peter Steinberger
4d50084c6e fix(exec): escape invisible approval filler chars 2026-03-22 22:52:14 -07:00
Peter Steinberger
78175aeb0a docs(agents): prefer current test model examples 2026-03-22 22:48:30 -07:00
Vincent Koc
e1d4c38cee fix(runtime): skip peer resolution for bundled plugin deps 2026-03-22 22:47:46 -07:00
Peter Steinberger
c036e4d176 fix: restrict remote marketplace plugin sources 2026-03-22 22:47:08 -07:00
Peter Steinberger
09faed6bd8 fix(gateway): gate internal command persistence mutations 2026-03-22 22:46:49 -07:00
Peter Steinberger
81445a9010 fix(media): bound remote error-body snippet reads 2026-03-22 22:43:42 -07:00
Vincent Koc
fd5555d5be fix(runtime): make dist-runtime staging idempotent 2026-03-22 22:41:27 -07:00
Peter Steinberger
5822892fee docs: refresh plugin-sdk api baseline 2026-03-23 05:35:51 +00:00
Peter Steinberger
c82fc9a0fd docs(changelog): note time exec approval fix 2026-03-22 22:32:02 -07:00
Peter Steinberger
a55f371cc5 fix(ci): skip docs-only preflight pnpm audit 2026-03-23 05:29:27 +00:00
Vincent Koc
be3a2e2eb6 fix(plugin-sdk): fall back to src root alias files 2026-03-22 22:26:18 -07:00
Peter Steinberger
39409b6a6d fix(security): unwrap time dispatch wrappers 2026-03-22 22:25:57 -07:00
Peter Steinberger
af9de86286 perf: trim vitest hot imports and refresh manifests 2026-03-23 05:25:05 +00:00
Peter Steinberger
7fcbf383d8 fix(ci): write dist build stamp after builds 2026-03-22 22:23:25 -07:00
Peter Steinberger
ea579ef858 fix(gateway): preserve async hook ingress provenance 2026-03-22 22:21:49 -07:00
Peter Steinberger
c5a941a506 refactor!: remove moltbot state-dir migration fallback 2026-03-22 22:19:35 -07:00
Peter Steinberger
6b9915a106 refactor!: drop legacy CLAWDBOT env compatibility 2026-03-22 22:13:39 -07:00
oliviareid-svg
5ff60cc39f fix(build): add stable memory-cli dist entry (#51759)
Co-authored-by: oliviareid-svg <269669958+oliviareid-svg@users.noreply.github.com>
Co-authored-by: Frank <vibespecs@gmail.com>
2026-03-23 13:11:06 +08:00
Vincent Koc
91f2c92960 docs(tools): soften DDG wording (scrapes -> pulls/gathers) 2026-03-22 22:10:56 -07:00
Rick_Xu
2fe1ff8ea8 Usage: include reset and deleted session archives (#43215)
Merged via squash.

Prepared head SHA: 49ed6c2fa3
Co-authored-by: rcrick <23069968+rcrick@users.noreply.github.com>
Co-authored-by: frankekn <4488090+frankekn@users.noreply.github.com>
Reviewed-by: @frankekn
2026-03-23 13:10:26 +08:00
Peter Steinberger
30ed4342b3 fix(agents): deny local MEDIA paths for MCP results 2026-03-22 22:10:13 -07:00
Vincent Koc
5730865f08 docs(tools): update DuckDuckGo Search for landed plugin code
- Mark as experimental (not just unofficial)
- Add region and safeSearch tool parameters (from DDG schema)
- Add plugin config example for region/safeSearch defaults
- Document auto-detection order (100 = last)
- Note SafeSearch defaults to moderate
- Verified against extensions/duckduckgo/src/
2026-03-22 22:09:47 -07:00
Vincent Koc
d43e26e399 fix(web-search): mark DuckDuckGo experimental 2026-03-22 22:07:53 -07:00
Peter Steinberger
8791aaae2b refactor: extract gateway install token helpers 2026-03-22 22:05:56 -07:00
Peter Steinberger
c15282062f refactor: split durable service env helpers 2026-03-22 22:05:56 -07:00
Vincent Koc
c6ca11e5a5 feat(web-search): add DuckDuckGo bundled plugin (#52629)
* feat(web-search): add DuckDuckGo bundled plugin

* chore(changelog): restore main changelog

* fix(web-search): harden DuckDuckGo challenge detection
2026-03-22 22:05:33 -07:00
Peter Steinberger
827c441902 fix(test): isolate flaky extension lanes 2026-03-23 05:02:07 +00:00
Peter Steinberger
dc6c22b812 fix: narrow exec exit failure kind typing 2026-03-23 04:58:46 +00:00
Kevin ONeill
dd860e76aa fix: normalize env var keys and isolate tests from real .env
- Apply normalizeEnvVarKey({ portable: true }) before security
  filtering, matching the established pattern in env-vars.ts.
  Rejects non-portable key names (spaces, special chars) that
  would produce invalid plist/systemd syntax.

- Isolate existing tests from the developer's real ~/.openclaw/.env
  by providing a temp HOME directory, preventing flaky failures
  when the test machine has a populated .env file.
2026-03-22 21:55:58 -07:00
Kevin ONeill
77ec7b4adf fix: include .env file vars in gateway service environment on install
When building the gateway install plan, read and parse
~/.openclaw/.env (or $OPENCLAW_STATE_DIR/.env) and merge those
key-value pairs into the service environment at the lowest
priority — below config env vars, auth-profile refs, and the
core service environment (HOME, PATH, OPENCLAW_*).

This ensures that user-defined secrets stored in .env (e.g.
BRAVE_API_KEY, OPENROUTER_API_KEY, DISCORD_BOT_TOKEN) are
embedded in the LaunchAgent plist (macOS), systemd unit (Linux),
and Scheduled Task (Windows) at install time, rather than
relying solely on the gateway process loading them via
dotenv.config() at startup.

Previously, on macOS the LaunchAgent plist never included .env
vars, which meant:
- launchctl print did not show user secrets (hard to debug)
- Child processes spawned before dotenv loaded had no access
- If the same key existed in both .env and the plist, the stale
  plist value won via dotenv override:false semantics

Dangerous host env vars (NODE_OPTIONS, LD_PRELOAD, etc.) are
filtered using the same security policy applied to config env
vars.

Fixes #37101
Relates to #22663
2026-03-22 21:55:58 -07:00
Vincent Koc
3afb6a2b95 fix(exec): accept runtime failure kind in formatter 2026-03-22 21:54:02 -07:00
Vincent Koc
8a59b09fc8 refactor(ci): collapse fast setup jobs into preflight 2026-03-22 21:52:19 -07:00
Peter Steinberger
97e4f37171 fix: keep status --json stdout clean (#52449) (thanks @cgdusek) 2026-03-22 21:51:08 -07:00
Charles Dusek
03c4bacbfb fix(cli): route deferred plugin logs to stderr in status --json 2026-03-22 21:51:08 -07:00
Charles Dusek
0e1da034c2 fix(cli): route plugin logs to stderr during --json output 2026-03-22 21:51:08 -07:00
Peter Steinberger
46a455d9e3 perf: enable vitest fs module cache by default 2026-03-23 04:48:31 +00:00
Vincent Koc
9378b31e08 fix(test): allow empty extension lane 2026-03-22 21:47:40 -07:00
Peter Steinberger
e001e8f2f8 test: isolate exec foreground failure coverage 2026-03-23 04:47:12 +00:00
Peter Steinberger
8e568142f6 refactor: extract exec outcome and tool result helpers 2026-03-23 04:47:12 +00:00
Vincent Koc
85023d6f9c fix(telegram): harden grammy seams across tests 2026-03-22 21:46:26 -07:00
Peter Steinberger
7818344f82 fix(ci): harden changed extension diff fallback 2026-03-23 04:41:54 +00:00
Peter Steinberger
7909236bd1 perf: add vitest test perf workflows 2026-03-23 04:41:38 +00:00
Vincent Koc
1c60e00a34 docs(tools): update Exa Search notes for current API behavior
Add notes about default highlights contents, highlightScores/summary
preservation from responses, description resolution order, and
100-result cap.
2026-03-22 21:39:33 -07:00
Vincent Koc
09cb77ed38 fix(ci): harden telegram seams and cap job timeouts 2026-03-22 21:38:26 -07:00
Vincent Koc
6eafa2ec87 fix(telegram): inject media loader through bot deps 2026-03-22 21:29:44 -07:00
Vincent Koc
df3f9bb555 docs(tools): add Exa Search page, align all search provider docs
New page: tools/exa-search.md
- Neural/keyword/hybrid search modes with content extraction
- Tool parameters including contents (highlights, text, summary)
- Search mode reference table

Rewritten: tools/duckduckgo-search.md
- Aligned to consistent template (Setup, Config, Tool parameters, Notes, Related)
- Simplified from previous version

Aligned across all providers:
- Every search page now ends with a consistent ## Related section
- Replaced 'See [Web tools]' with proper Related links
- Added Exa + DuckDuckGo to web.md overview CardGroup and comparison table
- Added Exa to docs.json nav and redirects
2026-03-22 21:27:24 -07:00
Vincent Koc
c11f95eced test(telegram): align webhook grammy mock 2026-03-22 21:23:34 -07:00
Vincent Koc
0a8eb7fdf0 docs(tools): add DuckDuckGo Search provider page
New page: tools/duckduckgo-search.md
- Key-free fallback provider, no API key needed
- Clear Warning about unofficial HTML-based integration
- Limitations section covering bot-challenge risk and reliability
- CardGroup showing good-for vs not-recommended-for use cases

Updated: tools/web.md with DuckDuckGo in CardGroup and comparison table
Updated: docs.json nav and redirect
2026-03-22 21:17:11 -07:00
Vincent Koc
a45ebf74bb fix(web-search): align Exa plugin with current API 2026-03-22 21:16:08 -07:00
Vincent Koc
0d161069f2 fix(matrix): avoid touching dropped room bindings 2026-03-22 21:14:17 -07:00
Vincent Koc
ee749b520e fix(exa): align freshness typing and config docs 2026-03-22 21:10:49 -07:00
Vincent Koc
5f746422aa fix(plugin-sdk): fast-path root diagnostic subscriptions 2026-03-22 21:07:11 -07:00
Vincent Koc
faae3e155d fix(whatsapp): remove outbound runtime cycle 2026-03-22 21:04:14 -07:00
Vincent Koc
218663c956 test(msteams): await async setup status lines 2026-03-22 20:59:28 -07:00
Vincent Koc
1042b59471 feat(web-search): add bundled Exa plugin (#52617) 2026-03-22 20:57:33 -07:00
Vincent Koc
5213382195 fix(auth): route copilot login through sdk seam 2026-03-22 20:53:30 -07:00
Vincent Koc
f69062c16e fix(plugin-sdk): export line runtime subpath 2026-03-22 20:39:16 -07:00
Vincent Koc
8b667cbe44 fix(build): repair stale plugin sdk surfaces 2026-03-22 20:36:28 -07:00
Vincent Koc
50bc625203 fix(matrix): preserve send aliases and voice intent 2026-03-22 20:35:40 -07:00
Vincent Koc
1354f37c88 fix(plugins): route keyed queue imports through core (#52608) 2026-03-22 20:35:28 -07:00
François Martin
daa3b4ba89 Remove personal references from docs (#25260)
* docs: remove personal references from AGENTS.md

* docs: remove personal reference from sag skill

* docs: note generic agent guidance cleanup

* Update CHANGELOG.md

---------

Co-authored-by: Josh Lehman <josh@martian.engineering>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-22 20:29:09 -07:00
Vincent Koc
06ddfb7e5f fix(ci): satisfy voice-call typing and extension boundaries 2026-03-22 20:26:15 -07:00
Vincent Koc
04cd389ef8 fix(ci): repair voice-call typing and provider contracts 2026-03-22 20:17:01 -07:00
Vincent Koc
d949dffc6e fix(ci): repair tts and matrix refactor fallout 2026-03-22 20:12:01 -07:00
Vincent Koc
5f3753c9ab test(voice-call): cover outbound call flow helpers 2026-03-22 20:04:48 -07:00
Vincent Koc
828afd39ec docs(config): refresh generated baseline 2026-03-22 20:04:28 -07:00
Vincent Koc
28f48a2dfd style(docs): format sdk entrypoints doc 2026-03-22 20:03:17 -07:00
Vincent Koc
59105fd614 fix(ci): restore plugin manifests and boundary tests 2026-03-22 20:01:25 -07:00
Vincent Koc
afb4b1173b fix(docs): remove duplicate '### Options' headings (MD024) 2026-03-22 19:58:35 -07:00
Vincent Koc
ac0fd26e16 fix(ci): resync generated baselines and line runtime seam 2026-03-22 19:53:26 -07:00
Vincent Koc
dbeac0763c test(voice-call): cover utility and tailscale helpers 2026-03-22 19:52:17 -07:00
Vincent Koc
af333f3a2c docs(changelog): note ACP hidden thought replay fix 2026-03-22 19:50:42 -07:00
Vincent Koc
f1bff0b9d6 test(voice-call): cover twilio and reaper helpers 2026-03-22 19:49:22 -07:00
Vincent Koc
32fdd21c80 fix(acp): preserve hidden thought replay on session load 2026-03-22 19:48:19 -07:00
Vincent Koc
feea4763fb test(voice-call): cover manager and api helpers 2026-03-22 19:47:34 -07:00
Vincent Koc
bbd4b39afb test(voice-call): cover helper utilities 2026-03-22 19:44:48 -07:00
Vincent Koc
ac7b7f5536 fix(line): narrow plugin-sdk seams after refactor 2026-03-22 19:44:30 -07:00
Vincent Koc
a9f4cb7544 fix(docs): rename 'Channel Options' to 'Options' in sdk-entrypoints
This heading labels definePluginEntry options (generic, not channel-specific).
Another agent reverted the previous fix during a merge.
2026-03-22 19:44:07 -07:00
Vincent Koc
742c005ac8 fix(acp): preserve hidden thought chunks from gateway chat 2026-03-22 19:43:19 -07:00
Vincent Koc
a83b7bca15 refactor(plugin-sdk): route core provider and telegram seams through sdk barrels 2026-03-22 19:43:19 -07:00
Vincent Koc
02f8a86e5c refactor(kilocode): route shared model constants through core seam 2026-03-22 19:43:19 -07:00
Vincent Koc
3ad652fa9e fix(build): restore plugin-sdk and line compat after refactor 2026-03-22 19:37:27 -07:00
Vincent Koc
c0933e2fc8 perf(reply): lazy-load session store writes 2026-03-22 19:32:24 -07:00
Vincent Koc
009980465f fix(openshell): bundle upstream cli fallback 2026-03-22 19:20:33 -07:00
Peter Steinberger
f8731b3d9d fix: finish exec tool failure landing (#52508) (thanks @martingarramon) 2026-03-22 19:19:07 -07:00
Martin Garramon
22c75a55b0 fix(exec): return plain-text tool result on failure instead of raw JSON
When an exec command fails (e.g. timeout), the tool previously rejected
with an Error, which the tool adapter caught and wrapped in a JSON object
({ status, tool, error }). The model then received this raw JSON as the
tool result and could parrot it verbatim to the user.

Now exec failures resolve with a proper tool result containing the error
as human-readable text in content[], matching the success path structure.
The model sees plain text it can naturally incorporate into its reply.

Also fixes a pre-existing format issue in update-cli.test.ts.

Fixes #52484

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 19:19:07 -07:00
Peter Steinberger
339a67262d test: clear msteams gate drift for gateway probe auth landing (#52513) (thanks @CodeForgeNet) 2026-03-22 19:14:44 -07:00
Peter Steinberger
24f77d7457 fix: finish gateway probe auth landing (#52513) (thanks @CodeForgeNet) 2026-03-22 19:14:44 -07:00
Peter Steinberger
a02499b335 fix: finish gateway probe auth landing (#52513) (thanks @CodeForgeNet) 2026-03-22 19:14:44 -07:00
CodeForgeNet
b2107d3503 fix(status): await resolveGatewayProbeAuthResolution in scan.shared
Function is now async after switching to resolveGatewayProbeAuthSafeWithSecretInputs.
Missing await caused TS error: Property 'auth' does not exist on type 'Promise<...>'.
2026-03-22 19:14:44 -07:00
CodeForgeNet
52acc57a61 fix(status): resolve only selected probe-auth branch and fix plain status path
Address two Codex P1/P2 issues:

1. (P1) Plain 'openclaw status' and 'openclaw status --json' still went
   through the sync resolveGatewayProbeAuthSafe path in
   status.gateway-probe.ts, which cannot expand SecretRef objects.
   Switched to async resolveGatewayProbeAuthSafeWithSecretInputs.

2. (P2) status-all.ts was eagerly resolving both local and remote probe
   auth before deciding which to use. A stale SecretRef in the unused
   branch could abort the command. Collapsed to a single resolution
   call using the correct mode upfront.

Updated status.scan.test.ts to use mockResolvedValue since
resolveGatewayProbeAuthResolution is now async.
2026-03-22 19:14:44 -07:00
CodeForgeNet
3595ecba45 fix(gateway): pass process.env in status command probe auth to resolve SecretRef
Fixes #52360

resolveGatewayProbeAuthSafe was called from status-all.ts without an
env argument, causing the credential resolution chain to fall back to
an empty object instead of process.env. This made env-backed SecretRef
tokens (gateway.auth.token, Telegram botToken, etc.) appear unresolved
in the status command path even when the runtime was healthy.

Added process.env as default fallback in buildGatewayProbeCredentialPolicy
and passed env explicitly from status-all.ts callers.

Related: #33070, #38973, #39415, #46014, #49730
2026-03-22 19:14:44 -07:00
Vincent Koc
042669d8c8 refactor(plugins): finish provider and whatsapp cleanup 2026-03-22 19:13:25 -07:00
Vincent Koc
2131981230 refactor(plugins): move remaining channel and provider ownership out of src 2026-03-22 19:13:25 -07:00
Vincent Koc
9ffde8efb2 style(format): fix msteams test drift 2026-03-22 19:13:01 -07:00
Vincent Koc
7bfa261c42 perf(reply): lazy-load media path normalization 2026-03-22 19:12:44 -07:00
Peter Steinberger
462d7ad9c0 perf: default channel vitest lanes to threads 2026-03-22 19:10:43 -07:00
Peter Steinberger
f04b49ee3e test: fix provider config typing drift 2026-03-22 19:10:43 -07:00
Vincent Koc
8ff277d2a2 test(msteams): cover poll and file-card helpers 2026-03-22 19:09:59 -07:00
Peter Steinberger
3ccf1bee2c test: default scoped vitest configs to no-isolate 2026-03-22 19:09:32 -07:00
Peter Steinberger
74cb08bede fix(plugins): accept media-understanding id hints 2026-03-23 02:08:49 +00:00
Peter Steinberger
9aafff7378 fix: restore main gate after type updates 2026-03-22 19:08:08 -07:00
Peter Steinberger
96d61aa50c refactor: harden generated-file guards and provider ids 2026-03-22 19:08:08 -07:00
Vincent Koc
7d11f6cf69 test(msteams): cover upload and webhook helpers 2026-03-22 19:07:04 -07:00
Vincent Koc
1ea2593362 test(msteams): cover graph helpers 2026-03-22 19:06:02 -07:00
Vincent Koc
2fcd6507ec perf(reply): narrow queue imports 2026-03-22 19:05:12 -07:00
Vincent Koc
3392558b42 perf(reply): split usage line helpers 2026-03-22 19:00:35 -07:00
Vincent Koc
05055e200a fix(docs): remaining code audit fixes
- sdk-entrypoints.md: fix mislabeled 'Channel entry options' heading
  (should be 'Options' — these are definePluginEntry options, not
  channel-specific)
- sdk-overview.md: add 4 missing API object fields (version, description,
  source, rootDir) from OpenClawPluginApi type
2026-03-22 18:59:03 -07:00
Peter Steinberger
562e4a1791 refactor(outbound): split delivery queue storage and recovery 2026-03-23 01:57:56 +00:00
Peter Steinberger
103098513f perf: remove stale unit isolated entry 2026-03-22 18:57:44 -07:00
Peter Steinberger
5051a37de4 test: fix googlechat security typing drift 2026-03-22 18:57:44 -07:00
Vincent Koc
16de5a7b41 fix(docs): code-verified fixes from deep reference audit
- sdk-runtime.md: add missing required params (runId, timeoutMs) to
  runEmbeddedPiAgent example
- sdk-provider-plugins.md: add missing onModelSelected hook (#22),
  clarify capabilities is data not callable, drop misleading '21' count
2026-03-22 18:56:51 -07:00
Peter Steinberger
e5be5c1b99 style: format plugin sdk helper updates 2026-03-23 01:56:01 +00:00
Peter Steinberger
f56edd62f0 fix: export provider-entry plugin sdk subpath 2026-03-23 01:56:01 +00:00
Peter Steinberger
5c8ea0a175 refactor: share channel setup status helpers 2026-03-23 01:56:01 +00:00
Peter Steinberger
583bea001c refactor: share parsed channel allowlist prompts 2026-03-23 01:56:01 +00:00
Peter Steinberger
7d032ed38c refactor: add provider onboarding preset appliers 2026-03-23 01:56:00 +00:00
Peter Steinberger
956fe72b39 refactor: extract single-provider plugin entry helper 2026-03-23 01:56:00 +00:00
Peter Steinberger
6237cfc6a6 fix: finish telegram reply fallback landing (#52524) (thanks @moltbot886) 2026-03-22 18:54:01 -07:00
moltbot886
b12dc4d04d fix(telegram): update test expectations for allow_sending_without_reply
Update exact-match test assertions in send.test.ts to include the new
allow_sending_without_reply: true parameter. Tests using objectContaining
already pass, but several tests use exact object matching.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 18:54:01 -07:00
moltbot886
d264c761cb fix(telegram): add allow_sending_without_reply to prevent lost messages
When a Telegram message that OpenClaw is replying to gets deleted before
delivery, the Telegram API rejects the entire sendMessage call with
"message to be replied not found". This causes the bot's response to be
silently lost and stuck in the failed delivery queue permanently.

Setting allow_sending_without_reply: true tells Telegram to deliver the
message as a standalone message if the reply target no longer exists,
instead of failing the entire request.

Applied to all 6 locations across 4 source files where
reply_to_message_id is set:
- send.ts: buildTelegramReplyParams (both reply_parameters and plain reply)
- bot/delivery.send.ts: buildTelegramSendParams
- draft-stream.ts: draft stream reply params
- bot-handlers.runtime.ts: error reply messages (file too large, media download failed)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 18:54:01 -07:00
Peter Steinberger
3547b5fd1e fix: restore bundled plugin metadata generator 2026-03-23 01:53:28 +00:00
Peter Steinberger
54213b587f refactor: reuse shared cli runtime test mocks 2026-03-23 01:53:28 +00:00
Peter Steinberger
2e6f2b0f07 test: centralize cli runtime capture helpers 2026-03-23 01:53:28 +00:00
Vincent Koc
9a07187339 style(format): fix extension test drift 2026-03-22 18:53:22 -07:00
Vincent Koc
a960cba2db perf(reply): lazy-load context token lookup 2026-03-22 18:52:53 -07:00
Vincent Koc
8a7ae5b67e test(googlechat): cover security normalization 2026-03-22 18:51:20 -07:00
Vincent Koc
7d8daa7173 perf(reply): lazy-load usage cost resolution 2026-03-22 18:48:42 -07:00
Peter Steinberger
ee362b7e38 perf: remove remaining unit thread pins 2026-03-22 18:47:35 -07:00
Peter Steinberger
ee1001d0ae test: slim outbound test import graphs 2026-03-22 18:47:35 -07:00
Vincent Koc
7a0781a367 test(irc): cover inbound behavior branches 2026-03-22 18:47:28 -07:00
Vincent Koc
a437f0417e test(nextcloud-talk): cover inbound behavior branches 2026-03-22 18:47:28 -07:00
Vincent Koc
3f002feffb fix(plugins): remove metadata generator conflict markers 2026-03-22 18:46:51 -07:00
Vincent Koc
263d5ea687 perf(reply): lazy-load runner execution and memory 2026-03-22 18:44:17 -07:00
create
aaba1ae653 fix(mattermost): honor replyToMode off for threaded messages 2026-03-22 18:42:56 -07:00
Vincent Koc
55e0c6380a test(msteams): cover store and live directory helpers 2026-03-22 18:42:43 -07:00
Vincent Koc
6e9cf81a8b test(mattermost): cover directory discovery 2026-03-22 18:42:43 -07:00
Peter Steinberger
29f3b7f6eb fix: harden image auth env lookups (#52552) (thanks @vincentkoc) 2026-03-22 18:42:18 -07:00
Vincent Koc
94f36bf373 docs(image): remove duplicate typical values bullet 2026-03-22 18:42:18 -07:00
Vincent Koc
654089320b fix(image): deprecate legacy skill and clarify auth 2026-03-22 18:42:18 -07:00
Vincent Koc
a59db4c9f4 fix(plugins): make metadata generator formatter portable 2026-03-22 18:41:13 -07:00
Peter Steinberger
2dd3425584 fix(ci): restore delivery queue branch checks 2026-03-22 18:39:24 -07:00
Stephen Schoettler
b07312c55b fix(delivery-queue): increment retryCount on deadline-deferred entries
Codex P1: entries deferred by the recovery time budget kept retryCount=0
forever, so they could loop across restarts without ever reaching MAX_RETRIES.

After breaking on deadline, call failDelivery() for all remaining entries
so retryCount is incremented. Entries stay in queue until MAX_RETRIES is
reached and they are pruned normally.

Also updates the maxRecoveryMs test to assert retryCount=1 on deferred entries.
2026-03-22 18:39:24 -07:00
Stephen Schoettler
329e539796 test(delivery-queue): align test assertion with 'next startup' log message 2026-03-22 18:39:24 -07:00
Stephen Schoettler
ca778b3f3c fix(browser): add ChildProcessWithoutNullStreams cast for @types/node compat
The stdio tuple overload resolves differently across @types/node versions
(v20 vs v24/v25). Cast the spawn() result to ChildProcessWithoutNullStreams
to ensure proc.stderr?.on/off type-checks regardless of installed @types/node.
2026-03-22 18:39:24 -07:00
Stephen Schoettler
a05a251be0 fix(delivery-queue): align test assertion and JSDoc with 'next startup' log message 2026-03-22 18:39:24 -07:00
Stephen Schoettler
20f758d4cb fix(delivery-queue): break immediately on deadline instead of failing all remaining entries
P1-C: After now >= deadline, the old code would iterate all remaining queue
entries and call failDelivery() on each — O(n) work that nullified the
maxRecoveryMs wall-clock cap on large queues.

Fix: break out of the recovery loop immediately when the deadline is exceeded.
Remaining entries are picked up on next startup unchanged (retryCount not
incremented). The deadline means 'stop here', not 'fail everything remaining'.
2026-03-22 18:39:24 -07:00
Stephen Schoettler
4e92807f10 fix(delivery-queue): increment retryCount on deferred entries when time budget exceeded
When delivery recovery ran out of the 60s time budget, remaining pending
entries were silently deferred to the next restart with no retryCount
increment. This caused them to loop forever across restarts, never hitting
MAX_RETRIES and never moving to failed/.

Fix: call failDelivery() on each remaining entry before breaking out of
the recovery loop (both the deadline check and the backoff-exceeds-deadline
check). This increments retryCount so that entries eventually exhaust
MAX_RETRIES and are permanently skipped.

Fixes #24353
2026-03-22 18:39:24 -07:00
Stephen Schoettler
5e64265537 fix(browser): prevent stdio buffer blocking in Docker environments 2026-03-22 18:39:24 -07:00
Stephen Schoettler
69217164f7 fix: add null guards to usage sort comparators
Prevents crash when totals is undefined in byModel/byProvider/byAgent
sort comparators. Fixes 'Cannot read properties of undefined (reading
totalTokens)' crash that causes context overflow in active sessions.
2026-03-22 18:39:24 -07:00
Vincent Koc
248caf09f5 fix(test): speed up openshell remote fs bridge 2026-03-22 18:38:45 -07:00
Vincent Koc
653d69ede7 test(msteams): cover setup wizard status 2026-03-22 18:37:37 -07:00
Peter Steinberger
f3650b466f style: format mattermost slash monitor test 2026-03-22 18:36:56 -07:00
Peter Steinberger
75835fc664 test: restore runtime-aware cli mocks 2026-03-22 18:35:37 -07:00
Peter Steinberger
c43bfcbbec refactor: split best-effort network display discovery 2026-03-22 18:35:37 -07:00
Peter Steinberger
8ef36e228f perf: demote outbound policy from hotspot lanes 2026-03-22 18:35:19 -07:00
Peter Steinberger
39752e7921 test: slim outbound context fixtures 2026-03-22 18:35:19 -07:00
Vincent Koc
a40f781cbe test(mattermost): cover slash and resources 2026-03-22 18:34:54 -07:00
Vincent Koc
383d5ac476 fix(test): split feishu bot helpers 2026-03-22 18:33:19 -07:00
Vincent Koc
2db10fb1d4 perf(reply): lazy-load embedded queue steering 2026-03-22 18:32:31 -07:00
Vincent Koc
bad9c8c223 style(format): fix channel test drift 2026-03-22 18:32:07 -07:00
Vincent Koc
4f0bb7594b fix(plugins): allow media-understanding package suffix 2026-03-22 18:30:40 -07:00
Vincent Koc
f580b68380 test(mattermost): cover onchar parsing 2026-03-22 18:30:23 -07:00
Peter Steinberger
639470c52c fix(ci): restore main gate 2026-03-23 01:30:07 +00:00
Vincent Koc
23c8af3d24 fix(test): split msteams attachment helpers 2026-03-22 18:29:52 -07:00
Vincent Koc
d1404014d9 perf(reply): split runner auth profile seam 2026-03-22 18:29:09 -07:00
Peter Steinberger
ecdf5d457a test: fix latest main test regressions 2026-03-22 18:28:13 -07:00
Vincent Koc
774a2064c9 test(msteams): cover routing and setup 2026-03-22 18:27:41 -07:00
Vincent Koc
645c9210b3 style(format): fix provider test formatting drift 2026-03-22 18:25:55 -07:00
Vincent Koc
0a329b2c9f Revert "style(format): fix check drift in provider tests"
This reverts commit 2619f5fe55.
2026-03-22 18:25:07 -07:00
Vincent Koc
2619f5fe55 style(format): fix check drift in provider tests 2026-03-22 18:24:40 -07:00
Vincent Koc
1b53918d4f test(mattermost): cover monitor auth gates 2026-03-22 18:23:46 -07:00
Vincent Koc
125e778fe6 perf(reply): split payload dedupe helpers 2026-03-22 18:22:56 -07:00
Peter Steinberger
3b32c41718 perf: trim vitest thread pins to hotspot tail 2026-03-22 18:22:36 -07:00
Vincent Koc
2df229cf8d test(googlechat): cover inbound access policy 2026-03-22 18:21:19 -07:00
Vincent Koc
fe867c2467 fix(ci): refresh plugin sdk baseline 2026-03-22 18:19:49 -07:00
Vincent Koc
6fde263046 fix(ci): refresh bundled plugin metadata 2026-03-22 18:19:49 -07:00
Vincent Koc
a753ee064d fix(agents): centralize moonshot compat and xai fast remaps 2026-03-22 18:17:02 -07:00
sudie-codes
8b5eeba386 fix(msteams): batch multi-block replies into single continueConversation call (#29379) (#49587)
Teams silently drops blocks 2+ when each deliver() opens its own
continueConversation() call. Accumulate rendered messages across all
deliver() calls and flush them together in markDispatchIdle().

On batch failure, retry each message individually so trailing blocks
are not silently lost. Log a warning when any individual messages fail
so flush failures are visible in logs.
2026-03-22 20:16:17 -05:00
Vincent Koc
71113ea0cb test(nextcloud-talk): cover setup adapter 2026-03-22 18:13:59 -07:00
Vincent Koc
cbf9cd0acb fix(ci): repair main checks 2026-03-22 18:13:14 -07:00
Vincent Koc
25f05612ee fix(test): unblock openshell remote fs bridge 2026-03-22 18:13:07 -07:00
Vincent Koc
f9a063ee2d test(irc): cover setup adapter 2026-03-22 18:08:31 -07:00
Vincent Koc
75ab4db87d fix(test): repair extensions lane regressions 2026-03-22 18:07:45 -07:00
Vincent Koc
05088eebdc test(mattermost): cover setup surface 2026-03-22 18:01:24 -07:00
Vincent Koc
b5b193d5ca fix(moonshot): refresh kimi k2 catalog 2026-03-22 18:00:51 -07:00
Vincent Koc
fdcf26ffd3 test(mattermost): cover setup adapter 2026-03-22 17:59:04 -07:00
Vincent Koc
3dcc802fe5 refactor(media): move deepgram and groq providers into plugins 2026-03-22 17:57:31 -07:00
Vincent Koc
0f54ca20aa refactor(image-generation): move provider builders into plugins 2026-03-22 17:57:31 -07:00
Josh Lehman
3fe96c7b9e device-pair: align internal command checks 2026-03-22 17:56:33 -07:00
Vincent Koc
a61e5d17f0 deps: update fast-xml-parser and markdown-it (#52541)
* style: format test files

* deps: bump fast-xml-parser override
2026-03-22 17:55:47 -07:00
Vincent Koc
45ede8729e feat(mistral): add curated catalog models 2026-03-22 17:55:27 -07:00
Vincent Koc
dc2013aae5 perf(reply): lazy-load followup context lookup 2026-03-22 17:55:14 -07:00
Vincent Koc
0743368013 fix(test): split discord monitor agent components 2026-03-22 17:54:52 -07:00
Vincent Koc
bb8e2fceff test(googlechat): cover webhook auth branches 2026-03-22 17:54:27 -07:00
Vincent Koc
d38cda5aab test(googlechat): cover setup adapter 2026-03-22 17:53:21 -07:00
Vincent Koc
e3c7a05cb5 fix(cli): remove duplicate test runtime keys 2026-03-22 17:52:05 -07:00
Vincent Koc
bd28eb9f5b fix(zai): align remaining pi metadata 2026-03-22 17:51:29 -07:00
Vincent Koc
88859c974f fix(xai): restore pi model drift coverage 2026-03-22 17:51:29 -07:00
Vincent Koc
8eb7d3543e test(googlechat): cover message actions 2026-03-22 17:50:51 -07:00
Vincent Koc
7dc1389d55 test(mattermost): cover routing helpers 2026-03-22 17:49:35 -07:00
Vincent Koc
de6bf58e79 refactor(tts): move speech providers into plugins 2026-03-22 17:48:59 -07:00
Vincent Koc
1d08ad4bac refactor(tts): remove legacy core speech builders 2026-03-22 17:48:09 -07:00
Vincent Koc
a8b9763d66 fix(test): split discord monitor utility coverage 2026-03-22 17:47:45 -07:00
Vincent Koc
52538575c3 test(nextcloud-talk): cover room info and routing 2026-03-22 17:46:47 -07:00
Vincent Koc
2ce79428c5 test(firecrawl): cover client and tool helpers 2026-03-22 17:44:52 -07:00
Vincent Koc
a8f7c274bc fix(test): split telegram channel-post media coverage 2026-03-22 17:42:44 -07:00
Vincent Koc
ac6542584b fix(ci): unblock windows generator and gateway test typing 2026-03-22 17:42:28 -07:00
Vincent Koc
e1c0e94d0c test(tavily): cover config and search helpers 2026-03-22 17:42:08 -07:00
Vincent Koc
dcef96e6d4 test(nextcloud-talk): cover signature and format helpers 2026-03-22 17:38:36 -07:00
Peter Steinberger
100d9a7a23 refactor: share boundary open and gateway test helpers 2026-03-23 00:37:05 +00:00
Vincent Koc
b21bcf6eb6 fix(test): reduce channel suite startup hotspots 2026-03-22 17:36:32 -07:00
Peter Steinberger
976eefe373 test: align cli runtime mocks with output runtime 2026-03-22 17:32:20 -07:00
Peter Steinberger
7bd12f4b37 test: fix rebased directory runtime mock (#52428) (thanks @karanuppal) 2026-03-22 16:56:19 -07:00
Peter Steinberger
aaa6068c08 test: stabilize json runtime captures (#52428) (thanks @karanuppal) 2026-03-22 16:56:19 -07:00
Peter Steinberger
a2999c6cfb test: normalize rebase fallout (#52428) (thanks @karanuppal) 2026-03-22 16:56:19 -07:00
Peter Steinberger
a5ace56a19 test: align rebased expectations and typecheck (#52428) (thanks @karanuppal) 2026-03-22 16:56:19 -07:00
Peter Steinberger
047a01f9e2 build: serialize formatter checks in CI (#52428) (thanks @karanuppal) 2026-03-22 16:56:19 -07:00
Peter Steinberger
38ee17145b fix: restore rebase regression coverage (#52428) (thanks @karanuppal) 2026-03-22 16:56:19 -07:00
Peter Steinberger
b00306a75b test: fix post-rebase test drift (#52428) 2026-03-22 16:56:19 -07:00
Peter Steinberger
797f3cc377 fix: stabilize stale buffer sweep landing (#52428) (thanks @karanuppal) 2026-03-22 16:56:19 -07:00
Peter Steinberger
841add8414 build: refresh plugin-sdk api baseline (#52428) 2026-03-22 16:56:19 -07:00
Peter Steinberger
f76e653776 fix: guard stale chat buffer sweep (#52428) (thanks @karanuppal) 2026-03-22 16:56:19 -07:00
Karan Uppal
550deb8c18 fix: sweep stale chatRunState buffers for stuck runs
emitChatFinal frees buffers on clean run completion, and the
maintenance timer sweeps abortedRuns after ABORTED_RUN_TTL_MS. But
runs that get stuck (e.g. LLM timeout without triggering clean
lifecycle end) are never aborted and their string buffers persist
indefinitely. This is the direct trigger for the StringAdd_CheckNone
OOM crash reported in the issue.

Add a stale buffer sweep in the maintenance timer that cleans up
buffers, deltaSentAt, and deltaLastBroadcastLen for any run not
updated within ABORTED_RUN_TTL_MS, regardless of abort status.

Closes #51821
2026-03-22 16:56:19 -07:00
Peter Steinberger
80e9737db4 test: align discord action expectation 2026-03-22 23:54:52 +00:00
Peter Steinberger
165aeccd24 test: align memory cli json assertions 2026-03-22 23:46:54 +00:00
Peter Steinberger
4e531d382b fix(ci): restore cli runtime mocks and timeout exits 2026-03-22 23:43:58 +00:00
Peter Steinberger
85f8437399 test: fix remaining tsgo issues after payload output refactor 2026-03-22 23:39:07 +00:00
Peter Steinberger
0684d887f6 perf: trim more vitest thread pins 2026-03-22 16:37:17 -07:00
Peter Steinberger
ac8e81a5cd refactor: compose account security warning helpers 2026-03-22 23:37:12 +00:00
Peter Steinberger
39faf4725d refactor: share warning collector projections 2026-03-22 23:37:12 +00:00
Peter Steinberger
2cfc7bca10 refactor: simplify telegram status snapshots 2026-03-22 23:37:12 +00:00
Peter Steinberger
f0ab31366c refactor: share async computed channel status adapters 2026-03-22 23:37:12 +00:00
Peter Steinberger
d9a2666ee1 refactor: share more computed channel status adapters 2026-03-22 23:37:12 +00:00
Peter Steinberger
72bfaf6ee2 refactor: share computed channel status adapters 2026-03-22 23:37:12 +00:00
Peter Steinberger
4cc0d05cfb refactor: share channel runtime state defaults 2026-03-22 23:37:12 +00:00
Peter Steinberger
023394bc0b fix(plugin-sdk): keep testing barrel narrow 2026-03-22 23:30:59 +00:00
Peter Steinberger
bca7787f92 style: fix rebased format drift 2026-03-22 23:26:21 +00:00
Peter Steinberger
60273758f8 fix(ci): use Windows-safe formatter spawn 2026-03-22 23:24:29 +00:00
Peter Steinberger
63130118ba test: fix rebased gateway and binding baselines (#52491) 2026-03-22 23:22:48 +00:00
Peter Steinberger
d73852ff21 fix: land plugin discovery missing-entry handling (#52491) (thanks @hclsys) 2026-03-22 23:22:48 +00:00
Peter Steinberger
d0f5e7cb2d test: simplify vitest runner pools 2026-03-22 16:22:09 -07:00
Peter Steinberger
4ee41cc6f3 refactor(cli): separate json payload output from logging 2026-03-22 23:19:17 +00:00
Peter Steinberger
274af0486a perf: trim more vitest thread pins 2026-03-22 16:18:26 -07:00
Peter Steinberger
6245b4f3d8 test: import vi in gateway chat server test 2026-03-22 23:17:05 +00:00
HCL
4f11982ae6 fix: emit warn diagnostic for I/O errors, keep silent only for ENOENT
Address Codex P1 + Greptile P2: the previous commit collapsed both
"path" (ENOENT) and "io" (EACCES/EMFILE) into silent null returns.

Now:
- reason="path" (missing file): return null silently — not a security issue
- reason="io" (permission/disk): push warn diagnostic — surface anomaly
  without aborting gateway
- reason="validation" (path escape): push error diagnostic — security violation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: HCL <chenglunhu@gmail.com>
2026-03-22 16:13:54 -07:00
HCL
8701a224f8 fix(plugins): distinguish missing entry file from security violation
resolvePackageEntrySource() treats all openBoundaryFileSync failures
as path-escape security violations. When an extension entry file is
simply missing (ENOENT, reason="path"), the gateway emits "extension
entry escapes package directory" and aborts — crashing in a loop.

Root cause: src/plugins/discovery.ts:478 checks !opened.ok but never
inspects opened.reason. SafeOpenSyncResult already distinguishes
"path" (ENOENT) from "validation" (actual path escape).

Fix: only push the security diagnostic when opened.reason is
"validation". For "path" or "io" failures, return null to skip the
entry silently — a missing file is not a security violation.

Closes #52445

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: HCL <chenglunhu@gmail.com>
2026-03-22 16:13:54 -07:00
Peter Steinberger
cac9de5326 perf: trim more vitest thread pins 2026-03-22 16:12:52 -07:00
Peter Steinberger
e0273bb1e9 test: fix remaining gate drift after main rebase (#52195) (thanks @meng-clb) 2026-03-22 16:09:43 -07:00
Peter Steinberger
e0463331bb fix: land gateway status hardening (#52195) (thanks @meng-clb) 2026-03-22 16:09:43 -07:00
Codex
8c7d603f25 fix(gateway): keep status helpers resilient to netif failures 2026-03-22 16:09:43 -07:00
Vincent Koc
c0cbc7403b test(env): normalize path expectations on windows 2026-03-22 16:08:49 -07:00
Peter Steinberger
ec51ee1af5 test: harden gateway no-isolate coverage 2026-03-22 16:08:22 -07:00
Vincent Koc
d02ef5ae02 test(irc): cover connection and probe helpers 2026-03-22 16:08:14 -07:00
Peter Steinberger
1eba6f830c test: remove stale plugin registry helper 2026-03-22 23:07:32 +00:00
Peter Steinberger
c454fe0fb3 refactor: adopt chat plugin builder in whatsapp 2026-03-22 23:06:25 +00:00
Peter Steinberger
ec232aca39 refactor: adopt chat plugin builder in twitch 2026-03-22 23:06:25 +00:00
Peter Steinberger
854f3ad0f8 refactor: share twitch account resolution helpers 2026-03-22 23:06:25 +00:00
Vincent Koc
091b811a61 fix(ci): lazy-load TypeScript in ts guard utils 2026-03-22 16:04:55 -07:00
Vincent Koc
19c85cf44f test(extensions): cover xai and firecrawl helpers 2026-03-22 16:04:31 -07:00
Vincent Koc
bd1c6efca5 test(extensions): cover web search provider helpers 2026-03-22 16:04:31 -07:00
Peter Steinberger
e0af23106c ci: fix workflow sanity gate 2026-03-22 23:04:04 +00:00
Vincent Koc
ca3165a3e7 fix(docs): deduplicate titles across 9 pages
Differentiate titles that were identical across multiple pages:
- Platform pages: add '(Platform)' suffix to distinguish from install guides
- Legacy root-level pages: add '(legacy path)' suffix for files that
  redirect to canonical tools/ paths
- Logging: 'Logging' -> 'Logging Overview' (root) and 'Gateway Logging'
- building-extensions: add '(redirect)' suffix
2026-03-22 16:02:28 -07:00
Peter Steinberger
5b42754afd perf: trim more vitest thread pins 2026-03-22 16:02:13 -07:00
Peter Steinberger
d84b3f5d5d test: fix conversation binding registry stub 2026-03-22 16:02:13 -07:00
Peter Steinberger
f8ff2a4f38 test: fix gate after main drift 2026-03-22 23:00:02 +00:00
Vincent Koc
377be1329d test(telegram): cover caption and forum service helpers 2026-03-22 15:58:33 -07:00
Vincent Koc
bd8ca6dbd7 test(telegram): cover dm access and allowed updates 2026-03-22 15:57:41 -07:00
Vincent Koc
efcf3c9f16 test(xai): align web search model expectations 2026-03-22 15:57:10 -07:00
Vincent Koc
f1603314f0 fix(docs): add missing title frontmatter to 7 template files 2026-03-22 15:56:26 -07:00
Vincent Koc
833d0dfeb0 test(telegram): cover normalization and status issues 2026-03-22 15:55:41 -07:00
Vincent Koc
59be2c8679 test(discord): cover additional utility surfaces 2026-03-22 15:55:41 -07:00
Vincent Koc
82508e3931 test(extensions): add discord and telegram coverage 2026-03-22 15:55:41 -07:00
Vincent Koc
5c8e1275a0 feat(minimax): add missing pi catalog models 2026-03-22 15:55:20 -07:00
Peter Steinberger
a9b20e5de9 perf: trim more vitest thread pins 2026-03-22 15:54:01 -07:00
Vincent Koc
a89d9f491b fix(docs): broken anchor links across 4 more files
- sandboxing.md: remove stale '-opt-in' suffix from Docker accordion anchor
- troubleshooting.md: remove #troubleshooting anchor (matrix has no such section)
- docker.md: drop unpredictable security heading anchor with parens/dots
- faq.md: fix SSL error accordion anchor slug
2026-03-22 15:50:56 -07:00
Peter Steinberger
1ad47b8fa1 test: harden path resolution test helpers 2026-03-22 22:47:58 +00:00
Peter Steinberger
d91913c207 test: fix windows bundled metadata generation 2026-03-22 22:47:35 +00:00
Vincent Koc
ed6b894873 fix(docs): correct sandbox config anchor slug in 5 files
Mintlify strips dots from backtick headings when generating anchors.
`agents.defaults.sandbox` -> #agentsdefaultssandbox (not #agents-defaults-sandbox)
2026-03-22 15:47:11 -07:00
Peter Steinberger
3a949646da refactor: simplify builder pairing adapters 2026-03-22 22:45:06 +00:00
Peter Steinberger
18c4a00b6f refactor: adopt chat plugin builder in synology chat 2026-03-22 22:45:06 +00:00
Peter Steinberger
7f65b3463b refactor: simplify chat plugin pairing configs 2026-03-22 22:45:06 +00:00
Peter Steinberger
3365f2e157 refactor: adopt chat plugin builder in feishu 2026-03-22 22:45:06 +00:00
Peter Steinberger
ad5e3f0cd5 refactor: adopt chat plugin builder in msteams 2026-03-22 22:45:06 +00:00
Peter Steinberger
7709aa33d8 refactor: adopt chat plugin builder in matrix 2026-03-22 22:45:06 +00:00
Peter Steinberger
5a8f77aa6a refactor: adopt chat plugin builder in zalouser 2026-03-22 22:45:06 +00:00
Jacob Tomlinson
bdd35a6239 docs: remove SETUP_PASSWORD and /setup wizard from Northflank and Railway docs 2026-03-22 22:44:42 +00:00
Charles Dusek
81b498b2b6 fix(cli): keep JSON payloads on stdout when logs route to stderr 2026-03-22 15:43:48 -07:00
Charles Dusek
5bba1b45bc fix(cli): bypass patched console.log for JSON payloads in --json mode 2026-03-22 15:43:48 -07:00
Charles Dusek
ebb4cc0128 fix(cli): route logs to stderr in --json mode to keep stdout clean 2026-03-22 15:43:48 -07:00
Vincent Koc
b863e1c315 fix(docs): update remaining npm-spec references for ClawHub-first default
- cli/plugins.md: rewrite install synopsis with ClawHub-first order
- cli/hooks.md: update hook pack install examples
- help/troubleshooting.md: <npm-spec> -> <package>
- gateway/security/index.md: drop npm-specific framing
2026-03-22 15:43:15 -07:00
Vincent Koc
9653c47133 fix(docs): remove apostrophes from headings (breaks Mintlify anchors)
- azure.md: "What you'll do" -> "What you will do"
- standing-orders.md: "Don't" -> "Avoid"

Per CLAUDE.md: avoid em dashes and apostrophes in headings because
they break Mintlify anchor links.
2026-03-22 15:41:08 -07:00
Vincent Koc
b93a045a96 docs(agents): update steering semantics 2026-03-22 15:40:56 -07:00
Vincent Koc
6e7855fdf5 feat(xai): support fast mode 2026-03-22 15:40:56 -07:00
Vincent Koc
9c0983618e feat(models): sync pi provider catalogs 2026-03-22 15:40:56 -07:00
Jacob Tomlinson
2e2f7c844f docs: update pi package versions to 0.61.1 2026-03-22 22:39:55 +00:00
Peter Steinberger
4ed69e4314 fix: note browser blank-tab launch fix (#52451) (thanks @rogerdigital) 2026-03-22 15:39:34 -07:00
Peter Steinberger
77cbbae81b test: fix ci drift after main rebase 2026-03-22 22:38:27 +00:00
Roger Deng
c3f1d43a10 test: align chrome launch args fixture with current browser profile type 2026-03-22 15:37:19 -07:00
Roger Deng
72d775e069 fix: stop forcing an extra blank tab on browser launch 2026-03-22 15:37:19 -07:00
Peter Steinberger
58c3f8673a test: fix full gate after runtime lazy-load 2026-03-22 22:33:07 +00:00
Peter Steinberger
6df6a691fc perf: lazy-load plugin runtime heavy surfaces 2026-03-22 22:33:07 +00:00
Vincent Koc
196c07cac6 fix(docs): broken FAQ anchor #how-does-openclaw-load-environment-variables
Correct anchor is #env-vars-and-env-loading (matching the actual
heading '## Env vars and .env loading' in help/faq.md).

Fixed in: tools/web.md, tools/perplexity-search.md, perplexity.md
2026-03-22 15:28:29 -07:00
scoootscooob
d9de38e0f8 Reply: tighten media dedupe and OpenAI ratio claims 2026-03-22 15:26:45 -07:00
Peter Steinberger
24b9baebab fix: preserve runtime path env in secrets snapshots 2026-03-22 15:25:58 -07:00
Peter Steinberger
d907ebffc5 perf: trim vitest thread overrides 2026-03-22 15:25:58 -07:00
Vincent Koc
ad01465318 fix(docs): update stale 'Web tools' link text to 'Web Search' 2026-03-22 15:24:08 -07:00
Peter Steinberger
47e8ffc2e9 test: repair current main gate regressions 2026-03-22 22:22:46 +00:00
Jacob Tomlinson
d6c63163b4 docs: fix memory loading, WhatsApp media caps, and Signal reaction mode docs 2026-03-22 22:20:55 +00:00
Vincent Koc
d1ad48cb50 fix(docs): align frontmatter titles with H1 headings
- sdk-runtime: 'Plugin SDK Runtime' -> 'Plugin Runtime Helpers'
- sdk-testing: 'SDK Testing' -> 'Plugin Testing'
- sdk-setup: 'Plugin SDK Setup' -> 'Plugin Setup and Config'
2026-03-22 15:20:46 -07:00
scoootscooob
24032dcc0e Reply: fix generated image delivery to Discord (#52489) 2026-03-22 15:18:16 -07:00
Peter Steinberger
6d34d62795 test: harden no-isolate gateway auth and pairing 2026-03-22 15:15:50 -07:00
Peter Steinberger
91cd38f4d4 fix(test): repair main CI drift 2026-03-22 22:12:43 +00:00
Peter Steinberger
030e950e5f test: simplify ACP spawn scenarios 2026-03-22 15:11:22 -07:00
Peter Steinberger
a81e671509 refactor: extract ACP spawn flow helpers 2026-03-22 15:11:22 -07:00
Peter Steinberger
8395d5cca2 refactor: adopt chat plugin builder in bluebubbles 2026-03-22 22:11:00 +00:00
Peter Steinberger
6ba9764b0f refactor: adopt chat plugin builder in zalo 2026-03-22 22:11:00 +00:00
Peter Steinberger
b5ea31ff1c refactor: adopt chat plugin builder in nostr 2026-03-22 22:11:00 +00:00
Peter Steinberger
eca0809a6d refactor: convert session manager cache to factory 2026-03-22 22:10:48 +00:00
Peter Steinberger
8eadc2f43b refactor: extract shared expiring cache 2026-03-22 22:10:48 +00:00
Jacob Tomlinson
6a228d9145 docs(render): fix port env var, remove nonexistent setup wizard 2026-03-22 22:10:28 +00:00
Peter Steinberger
e06b8d3e62 fix: harden update channel switching 2026-03-22 15:09:38 -07:00
Peter Steinberger
601f560682 test(whatsapp): cover monitor lifecycle states 2026-03-22 22:07:41 +00:00
Peter Steinberger
66743b84fa refactor(whatsapp): centralize web monitor state 2026-03-22 22:07:41 +00:00
Peter Steinberger
cfd9242e5d perf: lazy-load memory runtime surfaces 2026-03-22 22:06:45 +00:00
Vincent Koc
e15f156f85 fix(test): reduce node hotspot startup cost
Scope: conversation-binding/targets startup trimming + schema help isolation guardrail
2026-03-22 15:05:00 -07:00
Peter Steinberger
ee077804b0 refactor(gateway): separate ciao classification from logging 2026-03-22 15:02:43 -07:00
Peter Steinberger
31ee442d3f refactor(gateway): share interface discovery helpers 2026-03-22 15:02:43 -07:00
Peter Steinberger
c0d4abc59e fix(gateway): suppress ciao interface assertions
Closes #38628.
Refs #47159, #52431.
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-03-22 15:02:43 -07:00
Peter Steinberger
3faaf8984f fix(gateway): guard interface discovery failures
Closes #44180.
Refs #47590.
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-03-22 15:02:43 -07:00
Peter Steinberger
44bbd2d83d refactor: split control ui gateway connect flow 2026-03-22 15:01:42 -07:00
Vincent Koc
bb3e565487 docs(tools): restructure web tools IA and rewrite web.md
Navigation restructure:
- "Browser" group -> "Web Browser"
- New "Web Tools" group containing Web Fetch, Web Search, and all
  7 search provider sub-pages
- Other tools (btw, diffs, etc.) stay at top level

New page:
- tools/web-fetch.md: dedicated web_fetch reference with Steps,
  config, Firecrawl fallback, limits

Rewritten page:
- tools/web.md: now "Web Search" -- focused search overview with
  Steps quick-start, CardGroup provider picker, Tabs for key storage,
  provider comparison table, auto-detection, parameters, examples.
  Removed all inline provider setup (lives in sub-pages) and web_fetch
  content (now in dedicated page).

Final sidebar:
  Tools
  ├── Web Browser (browser, login, troubleshooting)
  ├── Web Tools
  │   ├── Web Fetch
  │   ├── Web Search
  │   └── Brave / Firecrawl / Gemini / Grok / Kimi / Perplexity / Tavily
  ├── btw, diffs, exec, ...
2026-03-22 15:01:09 -07:00
Peter Steinberger
3a68e87f84 refactor: adopt chat plugin builder in tlon 2026-03-22 22:00:25 +00:00
Peter Steinberger
cb4ae1a56d refactor: adopt chat plugin builder in line 2026-03-22 22:00:25 +00:00
Peter Steinberger
d868ee0882 refactor: adopt chat plugin builder in irc 2026-03-22 22:00:25 +00:00
Peter Steinberger
a6f918731f refactor: adopt chat plugin builder in nextcloud talk 2026-03-22 22:00:25 +00:00
Peter Steinberger
523b76c6c1 refactor: adopt chat plugin builder in slack 2026-03-22 22:00:25 +00:00
Peter Steinberger
f1975c0c0a refactor: adopt chat plugin builder in discord 2026-03-22 22:00:25 +00:00
Vincent Koc
f7bc9818b5 feat(minimax): support fast mode and sync pi defaults 2026-03-22 14:54:30 -07:00
Felix
57267b23d5 fix(acp): restore inline delivery for run-mode spawns from main sessions (#52426)
* fix(acp): restore inline delivery for run-mode spawns from main sessions

* test: restore matrix ACP spawn coverage (#52426) (thanks @distractedCoding)

---------

Co-authored-by: Felix <distractedCoding@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-03-22 14:54:09 -07:00
Peter Steinberger
ef3f64952a fix: bound session manager cache growth (#52427) (thanks @karanuppal) 2026-03-22 14:54:04 -07:00
Karan Uppal
30090e4895 fix: evict expired SESSION_MANAGER_CACHE entries on TTL miss
isSessionManagerCached() checks TTL before returning stale hits but
never deletes expired entries from the Map. They accumulate
indefinitely over the lifetime of a long-running gateway.

Delete the expired entry when the TTL check fails so the Map stays
bounded to active sessions.

Closes #51820
2026-03-22 14:54:04 -07:00
Karan Uppal
055f62e43e Add devcontainer with SSHD for Codespaces development 2026-03-22 14:54:04 -07:00
Peter Steinberger
1e1372027e perf: avoid cron startup store reload churn 2026-03-22 21:52:42 +00:00
Peter Steinberger
f075e2eebd perf: lazy-load hook install runtime helpers 2026-03-22 21:52:42 +00:00
Peter Steinberger
5d379f92a3 perf: lazy-load plugin install runtime helpers 2026-03-22 21:52:42 +00:00
Peter Steinberger
4235fb16d6 perf: split provider metadata from runtime loading 2026-03-22 21:52:42 +00:00
Peter Steinberger
abd948f2b7 fix(whatsapp): preserve watchdog message age across reconnects 2026-03-22 21:52:15 +00:00
Peter Steinberger
3e10d4c33d perf: remove remaining vitest fork batches 2026-03-22 14:49:40 -07:00
Peter Steinberger
ae6f42484f fix: restore cron and bundled plugin test metadata 2026-03-22 14:49:40 -07:00
XING
9d7719e8f0 fix(control-ui): add missing operator.read and operator.write scopes to connect params
The Control UI websocket connect params declared only admin, approvals,
and pairing scopes, omitting operator.read and operator.write. This
caused the gateway to reject all agent/send RPC calls from the dashboard
webchat with "missing scope: operator.write".

Add the two missing scopes to the connect params array so dashboard
webchat can send messages and read session state. Also update the test
fixture in gateway.node.test.ts to match the new scope list.

Fixes #52087

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 14:49:10 -07:00
XING
fa0a9ce2af fix(control-ui): add missing operator.read and operator.write scopes to connect params
The Control UI websocket connect params declared only admin, approvals,
and pairing scopes, omitting operator.read and operator.write. This
caused the gateway to reject all agent/send RPC calls from the dashboard
webchat with "missing scope: operator.write".

Add the two missing scopes to the connect params array so dashboard
webchat can send messages and read session state.

Fixes #52087

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 14:49:10 -07:00
Peter Steinberger
af4f2a8028 fix(discord): ignore empty components on media send 2026-03-22 21:45:36 +00:00
Jacob Tomlinson
77bdb33735 docs: fix channel lists, pairing channels, and discord step title 2026-03-22 21:43:29 +00:00
Vincent Koc
11ca41f457 fix(test): repair cron and loader regressions 2026-03-22 14:39:17 -07:00
Peter Steinberger
dcd5473283 refactor: adopt shared chat plugin builder 2026-03-22 21:37:32 +00:00
Peter Steinberger
99462776d1 refactor: compose shared channel security adapters 2026-03-22 21:37:31 +00:00
Peter Steinberger
87b2672126 refactor: centralize computed channel status adapters 2026-03-22 21:37:31 +00:00
Peter Steinberger
29c0e783b1 refactor: rename vitest behavior lanes 2026-03-22 14:36:57 -07:00
Vincent Koc
a8e10d9dc4 fix(docs): broken link /configuration -> /gateway/configuration in manifest.md 2026-03-22 14:36:41 -07:00
Peter Steinberger
f0dc42a9c5 test: improve parallels smoke defaults 2026-03-22 21:35:02 +00:00
Vincent Koc
9e0d298a08 docs(nav): move Web Tools above Web Search group 2026-03-22 14:34:58 -07:00
Peter Steinberger
2773f33084 test: stabilize vitest no-isolate suites 2026-03-22 14:28:43 -07:00
Peter Steinberger
9267e694f7 perf: reduce cron persistence churn 2026-03-22 21:28:16 +00:00
Peter Steinberger
46876edd86 perf: add lightweight memory status manager 2026-03-22 21:28:16 +00:00
Peter Steinberger
ca99163b98 perf: precompute base config schema 2026-03-22 21:28:16 +00:00
Peter Steinberger
593e333c10 perf: avoid plugin loader on provider fast paths 2026-03-22 21:28:16 +00:00
Peter Steinberger
171b24c5c5 perf: generate bundled plugin metadata for cold startup 2026-03-22 21:28:16 +00:00
Peter Steinberger
3ca7922dfe refactor: split plugin command registration startup path 2026-03-22 21:28:16 +00:00
Vincent Koc
78399c67e9 docs(tools): restructure web search into nested group with provider sub-pages
Restructure "Web and search" into a "Web Search" nested group
(matching Browser pattern) with provider sub-pages:

New pages:
- tools/gemini-search: Gemini with Google Search grounding setup
- tools/grok-search: Grok/xAI web-grounded search setup
- tools/kimi-search: Kimi/Moonshot web search setup

Existing pages (Brave, Firecrawl, Perplexity, Tavily) already exist.

Changes to tools/web.md (overview page):
- Simplify provider table with links to sub-pages
- Replace inline Gemini section with link to dedicated page
- Replace inline Brave/Perplexity setup with CardGroup linking all 7 providers
- Keep auto-detection, web_search, web_fetch reference sections

Navigation: "Web and search" -> "Web Search" with all 8 pages
(overview + 7 providers alphabetical)
2026-03-22 14:25:16 -07:00
Vincent Koc
8745132f9c fix(plugin-sdk): restore extension guardrails 2026-03-22 14:24:56 -07:00
Peter Steinberger
6dd4aff64a perf: shrink unit singleton isolation tail 2026-03-22 14:22:21 -07:00
Peter Steinberger
2c05e07f68 test: reset leaked cron fake timers 2026-03-22 14:22:21 -07:00
Jacob Tomlinson
2e246b47b9 docs: fix hook load order, command event payload, and session-memory confirmation 2026-03-22 21:09:36 +00:00
Peter Steinberger
7babb4583b test: stream live vitest console output 2026-03-22 21:09:09 +00:00
Vincent Koc
0df0c9f601 refactor(ci): add explicit preflight fanout hub 2026-03-22 14:08:56 -07:00
Vincent Koc
201124e183 fix(test): restore whatsapp setup surface partial mock 2026-03-22 14:07:27 -07:00
Peter Steinberger
ff6e0bed5f refactor: finish remaining status helper dedupe 2026-03-22 21:07:19 +00:00
Vincent Koc
26db578edc perf(inbound): trim reply startup imports (#52470)
* perf(inbound): trim dispatch and command startup imports

* fix(reply): restore command alias canonicalization

* style(reply): format command context

* fix(reply): restore runtime shim exports

* test(reply): mock ACP route seam

* fix(reply): repair dispatch type seams

* perf(inbound): defer followup reply helpers

* refactor(tts): share auto mode normalization

* fix(reply): catch followup compaction notice failures
2026-03-22 14:04:00 -07:00
Vincent Koc
0a20c5c8b3 fix(ci): stop serializing push workflow runs 2026-03-22 13:58:54 -07:00
Peter Steinberger
1efda3d041 test: improve live test progress feedback 2026-03-22 20:57:04 +00:00
Peter Steinberger
2a1acec6a7 refactor: reuse account status helpers in bundled channels 2026-03-22 20:55:16 +00:00
Peter Steinberger
87722d6327 refactor: extend shared account status snapshot helpers 2026-03-22 20:55:16 +00:00
Peter Steinberger
00b2f10dec refactor: share account snapshot descriptions 2026-03-22 20:55:16 +00:00
Jacob Tomlinson
11c66b36fd docs: add irc/googlechat/line channels, fix hook os field, cron add, BOOTSTRAP.md 2026-03-22 20:49:09 +00:00
Peter Steinberger
7a3346871e test: fix no-isolate integration regressions 2026-03-22 20:41:03 +00:00
Jacob Tomlinson
773fb9cead docs: update hooks load order, session-memory reset event, and bootstrap allowlists 2026-03-22 20:38:54 +00:00
Peter Steinberger
822c68a2d0 test: remove remaining account helper boilerplate 2026-03-22 20:34:09 +00:00
Peter Steinberger
8eb881ef18 test: harden no-isolate mock and timer cleanup 2026-03-22 13:32:59 -07:00
Vincent Koc
8d27617918 perf(ci): fan out macos from preflight scope (#52467)
* perf(ci): fan out macos from preflight scope

* refactor(ci): reorder preflight and native lanes
2026-03-22 13:32:56 -07:00
Peter Steinberger
7a2709c391 refactor: simplify channel allowlist resolvers 2026-03-22 20:30:21 +00:00
Peter Steinberger
fa13c2960e refactor: share scoped account accessor adapters 2026-03-22 20:30:21 +00:00
Peter Steinberger
8aca2fdc73 refactor: reuse shared core account lookups 2026-03-22 20:30:20 +00:00
Vincent Koc
5369ea53be perf(inbound): trim dispatch and command startup imports (#52374)
* perf(inbound): trim dispatch and command startup imports

* fix(reply): restore command alias canonicalization

* style(reply): format command context

* fix(reply): restore runtime shim exports

* test(reply): mock ACP route seam

* fix(reply): repair dispatch type seams
2026-03-22 13:19:57 -07:00
Peter Steinberger
3025760867 perf: shrink unit fork exception manifest 2026-03-22 13:10:55 -07:00
Peter Steinberger
e04d203bff test: remove heartbeat runner jiti mock pollution 2026-03-22 13:10:55 -07:00
Peter Steinberger
cf8891a824 fix: stabilize docker live test lanes 2026-03-22 20:07:27 +00:00
Jacob Tomlinson
43ee9fff48 docs: fix message hook context fields and session-memory example output 2026-03-22 20:01:30 +00:00
Vincent Koc
4bd90f24d1 perf(ci): gate install smoke on changed-smoke (#52458) 2026-03-22 12:58:08 -07:00
Onur Solmaz
b369397b43 ACP: revert acpx-plugin package naming (#52456)
* ACP: revert acpx-plugin package naming

* Test: format acp install hints expectations
2026-03-22 20:55:48 +01:00
Peter Steinberger
ff941b0193 refactor: share nested account config merges 2026-03-22 19:53:51 +00:00
Peter Steinberger
6fa0027c61 refactor: simplify tlon and discord setup accounts 2026-03-22 19:53:51 +00:00
Vincent Koc
9bb5eb6c7f fix(test): repair channel regression suites 2026-03-22 12:51:36 -07:00
Peter Steinberger
0404c16217 test: harden media local roots path assertions 2026-03-22 19:47:11 +00:00
Peter Steinberger
16616e21df refactor: share synology chat account helpers 2026-03-22 19:45:59 +00:00
Peter Steinberger
3c071a397f refactor: share account id selection helpers 2026-03-22 19:45:59 +00:00
Peter Steinberger
bddb6fca7b refactor: share remaining account config helpers 2026-03-22 19:45:59 +00:00
Vincent Koc
66beff726b perf(ci): trim install smoke overhead (#52447)
* perf(ci): trim install smoke overhead

* perf(ci): skip discord opus native build noise

* fix(ci): use npm latest dist-tag in install smoke
2026-03-22 12:44:47 -07:00
Peter Steinberger
bd108dfe00 test: fix CI contract drift in channel suites 2026-03-22 19:44:10 +00:00
Vincent Koc
59837b25bd fix(doctor): scope discord off hint to legacy migration 2026-03-22 12:42:47 -07:00
Peter Steinberger
285f903462 test: flatten feishu docx hoisted mocks 2026-03-22 12:41:17 -07:00
Peter Steinberger
0329412af2 docs: note extension vitest threads default 2026-03-22 12:38:42 -07:00
Peter Steinberger
c1067e90c9 perf: default extension vitest lanes to threads 2026-03-22 12:38:42 -07:00
Peter Steinberger
fdc993e779 test: fix extension thread proof regressions 2026-03-22 12:38:42 -07:00
Vincent Koc
5f723ecd7f fix(doctor): explain discord streaming opt-in (#52450) 2026-03-22 12:37:03 -07:00
Peter Steinberger
c26655d397 fix: stabilize plugin sdk api baselines 2026-03-22 19:32:29 +00:00
Peter Steinberger
4240c64491 test: harden no-isolate oauth contract coverage 2026-03-22 12:31:36 -07:00
Peter Steinberger
52b9d2091e fix: ignore poisoned home env values 2026-03-22 12:31:36 -07:00
Peter Steinberger
c0f107e246 docs: note no-isolate cleanup expectations 2026-03-22 12:31:36 -07:00
Peter Steinberger
5b2ff17d3c chore: refresh plugin sdk api baselines 2026-03-22 19:30:15 +00:00
Vincent Koc
dbd26e49f1 fix(test): reduce startup-heavy hotspot retention (#52381) 2026-03-22 12:28:55 -07:00
Peter Steinberger
26d400bea6 docs: note unit vitest threads default 2026-03-22 12:25:51 -07:00
Peter Steinberger
e39d5b9ef8 perf: default unit vitest lanes to threads 2026-03-22 12:25:51 -07:00
Peter Steinberger
e64dbb00b3 fix: prefer source plugin-sdk root alias in tests 2026-03-22 12:25:51 -07:00
Vincent Koc
f85cfc8b6c fix(gateway): harden first-turn startup readiness (#52387)
* fix(gateway): harden first-turn startup readiness

* fix(gateway): scope startup model retry
2026-03-22 12:12:08 -07:00
Peter Steinberger
aef2c60aa5 test: fix no-isolate canvas and timer regressions 2026-03-22 12:10:08 -07:00
Peter Steinberger
e80d456608 fix: restore CI install and format checks 2026-03-22 19:09:46 +00:00
Peter Steinberger
c69b1da6a0 perf: expand base vitest thread lanes further 2026-03-22 12:08:45 -07:00
Vincent Koc
2ef4d472f2 fix(install): restore memory-core workspace link 2026-03-22 12:06:44 -07:00
Peter Steinberger
1822db9227 test: harden no-isolate timer and undici seams 2026-03-22 12:04:11 -07:00
Peter Steinberger
1b1167012f test(cli): split plugin cli test coverage by surface 2026-03-22 12:02:53 -07:00
Peter Steinberger
e3151af6bc refactor(cli): extract hook pack update flow 2026-03-22 12:02:52 -07:00
Peter Steinberger
5696e24c3f refactor(cli): use typed clawhub fallback decisions 2026-03-22 12:02:52 -07:00
Peter Steinberger
145e514bb5 refactor(cli): share plugin install persistence 2026-03-22 12:02:52 -07:00
Peter Steinberger
5920ea838d refactor(cli): extract plugin install and update commands 2026-03-22 12:02:52 -07:00
Peter Steinberger
c74a50e4f1 refactor: share merged account config helper 2026-03-22 19:01:52 +00:00
Peter Steinberger
d06413e335 refactor: share normalized account lookups 2026-03-22 19:01:52 +00:00
Peter Steinberger
017d295edb refactor: adopt shared account merge helpers 2026-03-22 19:01:52 +00:00
Peter Steinberger
ff759f991e refactor: share account config merge helper 2026-03-22 19:01:52 +00:00
Peter Steinberger
7ae09410b7 refactor: reuse shared account config lookups 2026-03-22 19:01:52 +00:00
Peter Steinberger
8e09568bc7 perf: expand base vitest thread lanes 2026-03-22 11:59:38 -07:00
Peter Steinberger
412a3eb1ac build: bump version to 2026.3.22 2026-03-22 11:58:33 -07:00
Vincent Koc
4113db7d78 fix(protocol): sync generated swift bindings 2026-03-22 11:58:25 -07:00
Vincent Koc
dbf3dd6559 docs: update plugin install references for ClawHub-first default
OpenClaw now tries ClawHub before npm for bare plugin specs.
Update install examples and guidance across:
- building-plugins.md: intro and publish step
- sdk-setup.md: publishing section with clawhub:/npm: prefix examples
- tools/plugin.md: CLI reference table
- community.md: submission guidance and quality bar
2026-03-22 11:58:11 -07:00
Vincent Koc
6d9d9319b2 fix(docs): resolve markdownlint regressions 2026-03-22 11:56:06 -07:00
Vincent Koc
1f93a99f47 docs(plugins): overhaul SDK pages with Mintlify components and IA restructure
- Rewrite building-plugins.md as focused quick-start with CardGroup routing
- Rewrite sdk-channel-plugins.md with Steps, CodeGroup, Accordion walkthrough
- Move SDK Migration under Building Plugins nav, rename to "Migrate to SDK"
- Fix code examples and use valid Lucide icons for Mintlify Cards
2026-03-22 11:51:09 -07:00
Vincent Koc
8f12303a72 fix(ci): unblock rc build and drift checks (#52429) 2026-03-22 11:50:52 -07:00
Peter Steinberger
c40488453e test: remove thread-unsafe cwd mutations 2026-03-22 11:47:28 -07:00
Peter Steinberger
7d1ab5baca fix: restore CI type and baseline checks 2026-03-22 18:46:50 +00:00
Peter Steinberger
75692249d2 perf: expand vitest thread lanes again 2026-03-22 11:40:03 -07:00
Vincent Koc
28838802d4 docs(plugins): add SDK reference and how-to guide pages (#52366)
* docs(plugins): add SDK reference and how-to guide pages

Create 7 new plugin SDK documentation pages:
- sdk-overview: import map, registration API reference
- sdk-entrypoints: definePluginEntry/defineChannelPluginEntry reference
- sdk-runtime: api.runtime namespace reference
- sdk-setup: packaging, manifests, config schemas reference
- sdk-channel-plugins: step-by-step channel plugin how-to
- sdk-provider-plugins: step-by-step provider plugin how-to
- sdk-testing: test utilities and patterns reference

Restructure plugin docs navigation with nested groups:
- Top-level: user-facing pages (Install, Community, Bundles)
- Building Plugins: Getting Started, Channel, Provider
- SDK Reference: Overview, Entry Points, Runtime, Setup, Testing, Migration, Manifest, Internals

Revise existing pages for new IA:
- building-plugins.md: tightened as quick-start, routes to detailed guides
- architecture.md: updated info box with links to new guides
- sdk-migration.md: expanded Related section

* docs(plugins): add Mintlify components (Steps, CodeGroup, Tabs, Accordion, CardGroup)

- Channel plugin guide: wrap walkthrough in Steps, use CodeGroup for
  package.json/manifest, Accordion for createChatChannelPlugin details,
  CardGroup for advanced topics
- Provider plugin guide: wrap walkthrough in Steps, use CodeGroup for
  package files, Tabs for hook examples, Accordion for all-hooks reference
- Getting started: use CardGroup for plugin-type picker and next steps,
  CodeGroup for package/manifest
- SDK Overview: wrap subpath tables in AccordionGroup for scannability

* fix(docs): address PR review feedback on plugin SDK pages

- Remove nonexistent api.runtime.channel.handleInboundMessage call,
  replace with realistic webhook pattern and note about channel-specific
  inbound handling (issue a)
- Fix registrationMode values: 'setup' -> 'setup-only' and 'setup-runtime'
  matching actual PluginRegistrationMode type (issue b)
- Fix createOptionalChannelSetupSurface params: channelId -> channel,
  add required label field (issue c)
- Fix broken anchor links: #multi-capability-providers ->
  #step-5-add-extra-capabilities, #plugin-kinds -> #registration-api (issue d)
- Add missing acmeChatApi import in channel plugin example (issue e)
- Fix undefined provider variable in provider test example (issue f)

* fix(docs): use correct createProviderApiKeyAuthMethod options

Replace incorrect params (provider, validate) with actual required fields
(providerId, methodId, optionKey, flagName, promptMessage) matching
src/plugins/provider-api-key-auth.ts.

* fix(docs): address second round of PR review feedback

- Add required model fields (reasoning, input, cost, contextWindow,
  maxTokens) to catalog example (issue b)
- Fix buildChannelConfigSchema to take a Zod schema argument (issue c)
- Replace fabricated setupWizard steps/run with real ChannelSetupWizard
  contract (channel, status, credentials) (issue d)
- Add required sessionFile/workspaceDir to runEmbeddedPiAgent (issue e)
- Fix wrapStreamFn to return StreamFn from ctx.streamFn (issue f)
2026-03-22 11:35:53 -07:00
Peter Steinberger
1ed40cdf54 perf: expand vitest thread lanes further 2026-03-22 11:34:19 -07:00
Onur Solmaz
b79560c7f0 ACP: fully rename acpx plugin (#52404)
* ACP: rename acpx plugin package

* ACP: fully rename acpx plugin

* ACP: remove old acpx paths

* Docs: add bundled plugin naming guardrails

* Docs: keep plugin naming guardrails internal

* ACP: keep acpx plugin id stable

* ACP: drop old acpx-plugin tree
2026-03-22 19:33:31 +01:00
Peter Steinberger
59b5583e0a test: merge registry-backed channel contract suites 2026-03-22 18:31:52 +00:00
Peter Steinberger
04b283ffc2 fix: restore clawhub archive installs and gateway params 2026-03-22 18:31:52 +00:00
Peter Steinberger
95ae8aabb7 perf: expand vitest thread lanes 2026-03-22 11:26:38 -07:00
Peter Steinberger
aa80b1eb7c feat(cli): unify hook pack installs under plugins 2026-03-22 11:20:50 -07:00
Peter Steinberger
b44152fcc8 perf: automate vitest thread candidate scans 2026-03-22 11:19:10 -07:00
Peter Steinberger
8d9686bd0f feat!: prefer clawhub plugin installs before npm 2026-03-22 18:17:45 +00:00
Peter Steinberger
13c239039a refactor: consolidate qmd mcporter state 2026-03-22 18:09:45 +00:00
Peter Steinberger
23a6e0ccd3 refactor: share remaining runtime listener state 2026-03-22 18:09:45 +00:00
Peter Steinberger
17739910e9 refactor: centralize channel plugin registry lookups 2026-03-22 18:09:45 +00:00
Peter Steinberger
2c919078e1 refactor: share memory and discord singleton state 2026-03-22 18:09:45 +00:00
Peter Steinberger
88d39b1542 refactor: simplify remaining runtime singletons 2026-03-22 18:09:45 +00:00
Peter Steinberger
89d65521fe refactor: dedupe extension runtime caches 2026-03-22 18:09:45 +00:00
Peter Steinberger
f095bbd7b0 refactor: simplify plugin runtime singletons 2026-03-22 18:09:45 +00:00
Peter Steinberger
9428b38452 refactor: consolidate core runtime state helpers 2026-03-22 18:09:45 +00:00
Peter Steinberger
ca986d05aa test: trim repeated archive install work 2026-03-22 18:05:44 +00:00
Peter Steinberger
1ceaad18a6 test: harden vitest no-isolate coverage 2026-03-22 10:48:21 -07:00
Peter Steinberger
719bfb46ff style: format macos sources for ci 2026-03-22 17:32:30 +00:00
Onur Solmaz
047485dda6 docs: improve plugin manifest reference (#52393)
* docs: improve plugin manifest reference

* docs: clarify manifest field semantics
2026-03-22 18:29:20 +01:00
Peter Steinberger
405d808409 fix: restore repo-wide gate after exec safe-bin refactor 2026-03-22 17:28:04 +00:00
Peter Steinberger
5863ce1f78 fix(media): narrow default local attachment roots 2026-03-22 10:24:47 -07:00
Peter Steinberger
2a66eaf473 test: harden voice call regression assertions 2026-03-22 17:23:01 +00:00
Peter Steinberger
c8a36c621e test: tighten msteams regression assertions 2026-03-22 17:23:01 +00:00
Peter Steinberger
14074d3337 fix: restore repo-wide gate after upstream sync 2026-03-22 17:16:43 +00:00
Peter Steinberger
0ac939059e refactor(exec): split safe-bin semantics 2026-03-22 10:14:46 -07:00
Kaneki
d551d8b8f7 fix: make Android current-location callback cancellation-safe (#52318) (thanks @Kaneki-x)
* Android: make current-location callback cancellation-safe

* fix: make Android current-location callback cancellation-safe (#52318) (thanks @Kaneki-x)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-22 22:40:45 +05:30
Peter Steinberger
af90ddd8d3 test: harden voice call manager regressions 2026-03-22 17:07:20 +00:00
Peter Steinberger
36c951769c test: harden voice call provider regressions 2026-03-22 17:07:20 +00:00
Peter Steinberger
689a7342c2 test: tighten msteams regression assertions 2026-03-22 17:07:20 +00:00
Peter Steinberger
090ac8831f test: tighten pairing and phone control assertions 2026-03-22 17:07:20 +00:00
Peter Steinberger
6c2a3b74e3 fix(exec): harden jq safe-bin policy 2026-03-22 10:04:40 -07:00
Peter Steinberger
91b2800241 feat: add native clawhub install flows 2026-03-22 17:03:49 +00:00
Kaneki
c7788773bf fix: serialize TalkModeManager player cleanup (#52310) (thanks @Kaneki-x)
* Android: fix MediaPlayer double-release race in TalkModeManager

* Android: guard currentPosition read against concurrent player release

* fix: serialize TalkModeManager player cleanup

* fix: serialize TalkModeManager player cleanup (#52310) (thanks @Kaneki-x)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-22 22:31:30 +05:30
Peter Steinberger
5cb2f45585 refactor(hooks): unify hook policy resolution 2026-03-22 09:59:51 -07:00
Peter Steinberger
c96c319db3 refactor(plugin-sdk): share direct dm ingress helpers 2026-03-22 09:57:51 -07:00
Peter Steinberger
8a111f1cb9 test: harden discord monitor regression assertions 2026-03-22 16:56:44 +00:00
Peter Steinberger
b0176f626c test: tighten discord model picker regressions 2026-03-22 16:56:41 +00:00
Peter Steinberger
279c0c621f test: harden integration provider regressions 2026-03-22 16:49:24 +00:00
Peter Steinberger
68170f99ac test: tighten discord monitor regression assertions 2026-03-22 16:49:16 +00:00
Peter Steinberger
42f23619e3 fix(hooks): harden workspace hook loading 2026-03-22 09:38:09 -07:00
Peter Steinberger
1ee9611079 fix(nostr): enforce inbound dm policy before decrypt 2026-03-22 09:37:40 -07:00
Peter Steinberger
a94ec3b79b fix(security): harden exec approval boundaries 2026-03-22 09:35:25 -07:00
Peter Steinberger
e99d44525a test: refresh unit timing snapshot 2026-03-22 16:34:09 +00:00
Peter Steinberger
1d2dff0c4e test: trim import-heavy startup paths 2026-03-22 16:34:09 +00:00
Peter Steinberger
3fa2300ba1 perf: reduce plugin runtime startup overhead 2026-03-22 16:34:09 +00:00
Peter Steinberger
bb16ab9e08 refactor(synology-chat): split inbound webhook flow 2026-03-22 09:26:50 -07:00
Peter Steinberger
ce19a41f52 fix(synology-chat): scope DM sessions by account 2026-03-22 09:21:05 -07:00
Bob
4f1e12a2b1 Docs: prototype generated plugin SDK reference (#51877)
* Chore: unblock synced main checks

* Docs: add plugin SDK docs implementation plan

* Docs: scaffold plugin SDK reference phase 1

* Docs: mark plugin SDK reference surfaces unstable

* Docs: prototype generated plugin SDK reference

* docs(plugin-sdk): replace generated reference with api baseline

* docs(plugin-sdk): drop generated reference plan

* docs(plugin-sdk): align api baseline flow with config docs

---------

Co-authored-by: Onur <2453968+osolmaz@users.noreply.github.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-22 09:19:00 -07:00
Peter Steinberger
0f2c380bd9 chore: add security triage skill 2026-03-22 09:16:18 -07:00
Peter Steinberger
43d95a2db3 docs: clarify tools invoke operator boundary 2026-03-22 09:14:42 -07:00
Vincent Koc
9b9e1ae901 fix(discord): trim dm allowlist entries (#52354) 2026-03-22 09:13:30 -07:00
Vincent Koc
3ce5a8366a fix(plugins): enforce minimum host versions for installable plugins (#52094)
* fix(plugins): enforce min host versions

* fix(plugins): tighten min host version validation

* chore(plugins): trim dead min host version code

* fix(plugins): handle malformed min host metadata

* fix(plugins): key manifest cache by host version
2026-03-22 09:12:08 -07:00
Vincent Koc
6b7206ed35 perf(inbound): trim reply-run startup imports (#52332)
* perf(inbound): trim reply-run startup imports

* style(reply): format body runtime import

* test(reply): restore runtime seam mocks
2026-03-22 09:05:14 -07:00
Peter Steinberger
05279539a8 docs(plugin-sdk): document public SDK surface 2026-03-22 08:50:48 -07:00
Vincent Koc
e1ff24903f fix(gateway): follow up startup import reviews (#52337) 2026-03-22 08:48:53 -07:00
Vincent Koc
b9e71240ed refactor(doctor): centralize channel capability metadata (#52325)
* refactor(doctor): centralize channel capabilities

* fix(doctor): preserve msteams sender warnings
2026-03-22 08:47:16 -07:00
Vincent Koc
d3a0a623a3 fix(gateway): trim startup plugin imports (#52326)
* fix(gateway): trim startup plugin imports

* fix(gateway): follow up startup import reviews
2026-03-22 08:43:54 -07:00
Peter Steinberger
d1b080eac5 perf: route more vitest files to threads 2026-03-22 08:40:27 -07:00
Peter Steinberger
49091ebcbd chore: update dependencies except carbon 2026-03-22 08:37:03 -07:00
Vincent Koc
4685fc7e77 fix(doctor): align matrix and zalouser allowlist semantics (#52096)
* fix(doctor): align extension allowlist semantics

* fix(doctor): skip generic zalouser group warning
2026-03-22 08:19:24 -07:00
Tak Hoffman
52a0aa0672 ci: build dist before macos tests (#52279)
* ci: hydrate dist before plugin-sdk test lanes

* ci: skip bun-only dist build on PRs

* ci: build dist before macos tests
2026-03-22 09:10:08 -05:00
CharZhou
a07dcfde84 fix: pass clientTools to runEmbeddedAttempt in /v1/responses agent path (#52171)
Merged via squash.

Prepared head SHA: 74519e7da6
Co-authored-by: CharZhou <17255546+CharZhou@users.noreply.github.com>
Co-authored-by: frankekn <4488090+frankekn@users.noreply.github.com>
Reviewed-by: @frankekn
2026-03-22 22:05:00 +08:00
Tak Hoffman
7066316db8 ci: hydrate dist before plugin-sdk test lanes (#52267)
* ci: hydrate dist before plugin-sdk test lanes

* ci: skip bun-only dist build on PRs
2026-03-22 08:59:13 -05:00
Luke
ad24fccff5 test(gateway): stabilize suite session-store config (#52193)
* test(gateway): stabilize suite session-store config

* test(gateway): preserve seeded config semantics

* test(gateway): update seeded session store overrides
2026-03-22 19:18:44 +08:00
Bartok9
c70ae1c96e fix(poll-params): treat zero-valued numeric poll params as unset (#52150)
Merged via squash.

Prepared head SHA: 189e695b7c
Co-authored-by: Bartok9 <259807879+Bartok9@users.noreply.github.com>
Co-authored-by: frankekn <4488090+frankekn@users.noreply.github.com>
Reviewed-by: @frankekn
2026-03-22 18:39:31 +08:00
Peter Steinberger
67e61acac7 test: refresh unit timing snapshot 2026-03-22 09:35:36 +00:00
Peter Steinberger
b70b7b0d94 test: trim more local test startup overhead 2026-03-22 09:35:36 +00:00
Peter Steinberger
3382ef2724 perf: reduce plugin runtime startup overhead 2026-03-22 09:35:36 +00:00
Bob
aa6b962a3a Discord/ACP: forward abort signals into ACP turns (#52148)
* Discord/ACP: forward abort signals into ACP turns

* ACP: abort queued turns before actor start
2026-03-22 10:04:32 +01:00
Peter Steinberger
8ac3e41cdf test: refresh unit timing snapshot 2026-03-22 07:52:58 +00:00
Peter Steinberger
574cc9de64 test: trim more unit test startup overhead 2026-03-22 07:52:58 +00:00
Peter Steinberger
3cd4978fc2 refactor(agents): unify tool media reply delivery 2026-03-22 00:49:56 -07:00
Peter Steinberger
2d492ab534 test: tighten regression assertions across extension tests 2026-03-22 07:46:07 +00:00
Peter Steinberger
4becbc8b25 test: remove duplicate outbound plugin tests 2026-03-22 07:38:01 +00:00
Peter Steinberger
b4656f193a test: strengthen regression coverage and trim low-value checks 2026-03-22 07:38:01 +00:00
Peter Steinberger
f537ea90ed perf: route hot vitest files to threads 2026-03-22 00:31:17 -07:00
Peter Steinberger
037fa2f8fb test: refresh unit timing snapshot 2026-03-22 07:22:42 +00:00
Peter Steinberger
94ec0d6aeb test: trim plugin-sdk import-heavy startup 2026-03-22 07:22:42 +00:00
Ayaan Zaidi
537115bbdc test: fix channel interaction/media regressions 2026-03-22 11:56:19 +05:30
Peter Steinberger
ec0e4ff218 test: refresh unit timing snapshot 2026-03-22 05:59:46 +00:00
Peter Steinberger
b2f9ab9a1f test: trim import-heavy startup paths 2026-03-22 05:59:46 +00:00
Vincent Koc
041f0b87ec perf(inbound): trim cold startup import graph (#52082)
* perf(inbound): trim cold startup import graph

* chore(reply): drop redundant inline action type import

* fix(inbound): restore warning and maintenance seams

* fix(reply): restore type seam and secure forked transcripts
2026-03-21 22:32:21 -07:00
Vincent Koc
c96a12aeb9 Agents: add per-agent defaults and safe model fallback (#51974)
* Agents: add per-agent defaults and safe model fallback

* Docs: add per-agent thinking/reasoning/fast defaults to config reference and thinking docs

* Format get-reply directives

* Auto-reply: guard agent reasoning defaults

* Docs: update config baseline
2026-03-21 22:27:24 -07:00
Bryan Tegomoh, MD, MPH
f783101735 fix: accept session_status sessionKey=current alias (#39574)
* fix: normalize sessionKey=current in shared session resolution

Move the "current" alias handling from a narrow session_status-only
mapping into the shared session resolution layer so every session tool
(session_status, sessions_history, sessions_send) resolves it
consistently.

Changes:
- Register "current" as a canonical session key in looksLikeSessionKey
  so it is never misclassified as a sessionId
- Normalize "current" to the requester's own session key inside
  resolveSessionReference and resolveInternalSessionKey
- Add "current" normalization in session_status before local store
  lookup via the existing "main" alias scoping
- Add regression tests covering both main-session and cross-agent
  resolution paths

Fixes #39570

* fix: keep session_status current bound to requester

* fix: preserve literal current session targets

* fix: preserve literal current in session_status

* fix: defer current alias in session_status

* fix: scope session_status current to active store (#39574) (thanks @BryanTegomoh)

* fix: preserve literal current session previews (#39574) (thanks @BryanTegomoh)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-22 10:55:05 +05:30
Peter Steinberger
707eb8e1b3 test: refresh unit timing snapshot 2026-03-22 05:03:10 +00:00
Peter Steinberger
e1854dfbf6 test: trim import-heavy startup paths 2026-03-22 05:03:10 +00:00
Peter Steinberger
8727338372 perf: extract lightweight runtime seams 2026-03-22 05:03:10 +00:00
Vincent Koc
2b210703a3 fix(models): cache models.json readiness for embedded runs (#52077)
* fix(models): cache models.json readiness for embedded runs

* fix(models): harden readiness cache inputs
2026-03-21 21:58:10 -07:00
Tak Hoffman
432e8943ad fix(discord): dedupe inbound message deliveries (#51950)
* fix(discord): dedupe inbound message deliveries

* test(discord): disable debounce in tool-result dispatch spec
2026-03-21 23:55:52 -05:00
Vincent Koc
7a0dacbfba refactor(doctor): extract note emission (#52076)
* refactor(doctor): extract note emission

* test(doctor): cover optional note emission paths
2026-03-21 21:52:14 -07:00
Felippe Mercurio
8790c54635 fix(android): use scheme default port for gateway setup URLs (#43540)
* fix(android): use scheme default port for gateway setup URLs

* test(android): cover gateway endpoint default ports

* fix(android): preserve direct gateway default port

* fix(android): preserve explicit cleartext port display

* fix: preserve Android gateway setup URL ports (#43540) (thanks @fmercurio)

---------

Co-authored-by: clawdia <clawdia@fmercurio.tech>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-22 10:21:38 +05:30
Tak Hoffman
0f6dbb4390 Fix configure cache ownership for plugin resolvers (#52018)
* fix configure cache ownership

* address web-search cache review

* rekey provider wizard cache

* honor plugin cache opt-outs

* fix cache invalidation gaps

* align plugin snapshot ttl

* refresh snapshot cache keys
2026-03-21 23:41:45 -05:00
Vincent Koc
ec59974a46 refactor(doctor): extract flow finalization (#52067)
* refactor(doctor): extract flow finalization

* test(doctor): pin repair finalization to cfg
2026-03-21 21:27:51 -07:00
Vincent Koc
60f559e217 refactor(doctor): extract matrix sequencing (#52056) 2026-03-21 21:10:48 -07:00
Peter Steinberger
4c9f411f6d test(discord): stabilize tool-result reply tests 2026-03-22 04:07:38 +00:00
Peter Steinberger
7ac312b8fe fix(agents): migrate remaining media artifacts 2026-03-21 21:05:33 -07:00
Goweii
e7e4c68caf Android: update status bar appearance in OpenClawTheme (#51098)
* Android: update status bar appearance in OpenClawTheme

* fix: update Android status bar appearance (#51098) (thanks @goweii)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-22 09:33:24 +05:30
Peter Steinberger
a2472dc31b refactor(test): dedupe channel startup test setup 2026-03-22 03:50:41 +00:00
Vincent Koc
177136c964 perf(inbound): trim reply startup imports (#51988)
* perf(inbound): narrow reply startup imports

* perf(inbound): trim reply startup imports

* fix(perf): dedupe archive helpers
2026-03-21 20:49:41 -07:00
Vincent Koc
b2380b3ab1 refactor(doctor): extract repair sequencing (#51980)
* refactor(doctor): extract repair sequencing

* fix(doctor): sanitize repair runner notes
2026-03-21 20:43:16 -07:00
Peter Steinberger
89bc66feef refactor(test): dedupe startup channel test helpers 2026-03-22 03:41:25 +00:00
Peter Steinberger
36feecf018 refactor(test): dedupe bluebubbles webhook auth helpers 2026-03-22 03:24:22 +00:00
Kaneki
1e98dbcad3 Android: fix Bitmap memory leaks in PhotosHandler (#41888)
* Android: fix Bitmap memory leaks in PhotosHandler

Bitmaps created by decodeScaledBitmap and intermediate scaled copies
inside encodeJpegUnderBudget were never recycled, leaking native memory
on every photos.latest invocation (up to 20 bitmaps per call).

- latest(): wrap bitmap usage in try/finally to guarantee recycle
- decodeScaledBitmap(): recycle the decoded bitmap after scaling
- encodeJpegUnderBudget(): use try/finally to recycle intermediate
  scaled bitmaps on all exit paths (success, compress failure, and
  cannot-shrink-further early returns)

Made-with: Cursor

* Android: guard decodeScaledBitmap against scale() exceptions

* fix: note Android photos bitmap cleanup (#41888) (thanks @Kaneki-x)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-22 08:53:26 +05:30
Kaneki
2909d8cd12 Android: fix Bitmap memory leaks in CanvasController snapshots (#41889)
* Android: fix Bitmap memory leaks in CanvasController snapshots

snapshotPngBase64() and snapshotBase64() create bitmaps via
captureBitmap() and scaleForMaxWidth() but never recycle them,
leaking native memory on every canvas snapshot invocation.

Wrap both methods in nested try/finally blocks:
- outer: always recycles the captured bitmap
- inner: recycles the scaled bitmap only when it differs from the
  captured one (scaleForMaxWidth returns `this` when no scaling needed)

Made-with: Cursor

* fix: note Android canvas snapshot bitmap leak in changelog (#41889) (thanks @Kaneki-x)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-22 08:50:42 +05:30
Kaneki
88da51d91b Android: fix temp file leak in CameraHandler.handleClip (#41890)
* Android: fix temp file leak in CameraHandler.handleClip

When readBytes() throws (IOException, OOM, etc.), the recorded clip
file was never deleted because delete() only ran on the success path.

Move file.delete() into a finally block so the temp file is cleaned up
regardless of whether readBytes() succeeds or fails.

Made-with: Cursor

* fix: Android camera clip cleanup (#41890) (thanks @Kaneki-x)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-22 08:49:50 +05:30
Ayaan Zaidi
506861efd0 fix: Android contacts search wildcard escaping (#41891) (thanks @Kaneki-x) 2026-03-22 08:48:29 +05:30
kaneki
f7866c1c15 Android: escape SQL LIKE wildcards in contacts search query
The contacts search passed user input directly into a LIKE pattern
without escaping % and _ characters, causing them to act as SQL
wildcards and return incorrect results.

Add an escapeLikePattern() helper that escapes \, %, and _ with a
backslash, and add ESCAPE '\' to the selection clause so SQLite
treats them as literal characters.

Made-with: Cursor
2026-03-22 08:48:29 +05:30
Peter Steinberger
6c4eced494 refactor(test): dedupe bluebubbles webhook helpers 2026-03-22 03:15:48 +00:00
Ayaan Zaidi
eea84bc6ec fix: land Android camera bitmap leak (#41902) (thanks @Kaneki-x) 2026-03-22 08:44:06 +05:30
kaneki
d38561acbe Android: fix Bitmap memory leaks in CameraCaptureManager.snap 2026-03-22 08:44:06 +05:30
Kaneki
d6346aaf63 Android: fix sensor callback race condition in MotionHandler (#43781)
* Android: fix sensor callback race in MotionHandler using tryResume

* call completeResume before unregisterListener to avoid coroutine leak

* replace internal tryResume/completeResume with AtomicBoolean guard

* use CancellableContinuation.resume(value, onCancellation) for cancellation safety

* use non-deprecated resume overload for kotlinx.coroutines 1.10+

* fix: simplify Android motion continuation resume

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-22 08:41:02 +05:30
Kaneki
e7814f7ba0 Android: fix incomplete JS string escaping in A2UI action status (#43784)
* Android: fix incomplete JS string escaping in A2UI action status

* escape U+2028/U+2029 Unicode line terminators in JS strings

* refactor(android): serialize A2UI action status strings

* fix: serialize Android A2UI action status strings (#43784) (thanks @Kaneki-x)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-22 08:39:53 +05:30
Ayaan Zaidi
b67baae1f6 fix: make permission rationale completion single-shot 2026-03-22 08:34:02 +05:30
kaneki
6db72746fb Android: keep permission dialog cleanup on the main thread 2026-03-22 08:34:02 +05:30
kaneki
518d2dd6a9 Android: harden permission dialogs across activity teardown 2026-03-22 08:34:02 +05:30
Val Alexander
14237aa6c0 refactor(usage): drop empty detail placeholder state (#52013)
* refactor(usage): drop empty detail placeholder state

* docs(changelog): add entry for usage detail cleanup
2026-03-21 21:58:55 -05:00
Peter Steinberger
8e6a4c2d82 perf: narrow discord timeout import seam 2026-03-22 02:42:57 +00:00
Peter Steinberger
b1ab7ba3ac refactor(test): trim bluebubbles webhook fixtures 2026-03-22 02:41:44 +00:00
Peter Steinberger
4f210e98a5 refactor(test): dedupe bluebubbles monitor helpers 2026-03-22 02:18:00 +00:00
Peter Steinberger
7b344b8a8a test: refresh unit timing snapshot 2026-03-22 02:11:43 +00:00
Peter Steinberger
d81772dbc7 test: trim import-heavy startup paths 2026-03-22 02:11:43 +00:00
Peter Steinberger
2cc777539a perf: reduce plugin and memory startup overhead 2026-03-22 02:11:43 +00:00
wilsonIs
1ad3893b39 Control UI: disambiguate duplicate agent session labels (#48209)
* Control UI: disambiguate duplicate agent sessions

* Control UI: avoid prefixed session label collisions

* Control UI: align session defaults typing

---------

Co-authored-by: Val Alexander <68980965+BunsDev@users.noreply.github.com>
2026-03-21 21:11:15 -05:00
Peter Steinberger
17713ec988 refactor(test): dedupe bluebubbles webhook tests 2026-03-22 01:30:22 +00:00
Tak Hoffman
1e4688a584 CI: exempt bad-barnacle from dirty auto-close 2026-03-21 20:23:17 -05:00
Tak Hoffman
d6c05c1941 ci: surface hidden failures in smoke helpers (#51954)
* ci: surface hidden failures in smoke helpers

* changelog: add ci smoke failure-signal note
2026-03-21 20:21:48 -05:00
Vincent Koc
ab38f6471c perf(inbound): narrow reply startup imports (#51943)
* perf(inbound): narrow reply startup imports

* fix(reply): restore activation parsing semantics

Cherry-picked review fixes for activation parsing semantics and native command surface cache invalidation.

* fix(reply): preserve case-insensitive command matching

Cherry-picked review fix to lowercase only the slash-command token in commands-context while preserving argument casing.
2026-03-21 18:18:41 -07:00
Peter Steinberger
f1b2c5639a refactor(test): dedupe startup and nostr test fixtures 2026-03-22 01:12:31 +00:00
Vincent Koc
3775651480 refactor(doctor): extract preview warning collection (#51942)
* refactor(doctor): extract preview warning collection

* fix(doctor): sanitize preview empty allowlist warnings

* test(doctor): cover sanitized preview warnings
2026-03-21 18:11:28 -07:00
Val Alexander
a5309b6f93 feat(usage): improve usage overview styling and localization (#51951)
* feat(usage): add usage page styles and localization

- Introduced a new `usage.css` file for styling the usage overview page.
- Updated `en.ts` localization file to include new usage-related translations.
- Refactored the usage rendering components to utilize the new localization strings for improved user experience.
- Enhanced the `app-render-usage-tab.ts` to better structure the data passed to the rendering function.

* feat(ui): enhance styling and functionality for usage overview and chat components

- Updated `package.json` to include new built dependencies.
- Refined CSS styles across various files to improve UI consistency and accessibility, including adjustments to color themes and layout structures.
- Introduced new responsive grid layouts for usage overview and chat components, enhancing the user experience on different screen sizes.
- Added functionality to hide context notices based on token freshness in chat view.
- Implemented new rendering functions for usage statistics, improving data presentation and user interaction.

* feat(usage): enhance usage overview styling and rendering options

- Added new CSS classes for improved layout and styling of usage insight cards and error lists.
- Updated rendering functions to support customizable class names for usage insight cards and error lists, enhancing flexibility in UI presentation.
- Implemented a wide card layout and specific styling for error lists to improve visual clarity and user experience.

* fix(ui): address review feedback on usage and chat layout

* docs(changelog): add entry for usage UI improvements
2026-03-21 20:07:51 -05:00
Vincent Koc
2b4c3c2057 fix(plugin-sdk): remove relative extension boundary escapes (#51939)
* fix(plugin-sdk): remove relative extension boundary escapes

* Gate new plugin-sdk subpaths on host version

* Add changelog entry for #51939

* Fix local staging for plugin-sdk host version gate

* Raise host floor for line and googlechat plugins

---------

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-21 20:03:18 -05:00
Peter Steinberger
daa042c9a0 test: refresh unit timing snapshot 2026-03-22 00:53:12 +00:00
Peter Steinberger
d0d82ea67b test: trim import-heavy startup paths 2026-03-22 00:53:12 +00:00
Peter Steinberger
8b7f40580d perf: split telegram audit runtime seams 2026-03-22 00:53:12 +00:00
Peter Steinberger
465bd0cef1 fix(test): normalize repo-relative paths on Windows 2026-03-22 00:41:57 +00:00
Vincent Koc
45f84cf639 fix(test): normalize path separators [skip ci] 2026-03-21 17:33:35 -07:00
Tak Hoffman
c449a0a3c1 Add bad-barnacle label to prevent barnacle closures. (#51945) 2026-03-21 19:27:18 -05:00
Peter Steinberger
30ad059da8 refactor(test): dedupe setup wizard helpers 2026-03-22 00:16:31 +00:00
Vincent Koc
85722d4cf2 refactor(doctor): extract legacy and unknown-key steps (#51938) 2026-03-21 16:59:22 -07:00
Vincent Koc
865a90ccab refactor(doctor): extract config mutation state helper (#51935) 2026-03-21 16:45:33 -07:00
Peter Steinberger
57fa59ab92 refactor(test): dedupe setup wizard test helpers 2026-03-21 23:41:54 +00:00
Vincent Koc
6266b842d4 ci(actions): restore audit lane and cache Docker builds (#51933)
* ci(actions): restore secrets audit lane

* ci(actions): restore audits and cache docker builds
2026-03-21 16:36:54 -07:00
Val Alexander
36c6d44eca feat(ui): add multi-session selection and deletion (#51924)
* feat(sessions): implement multi-session deletion and selection functionality

- Added `deleteSessionsAndRefresh` function to handle deletion of multiple sessions.
- Updated session state management to track selected session keys.
- Enhanced UI to support bulk actions for selected sessions, including delete and unselect options.
- Refactored related tests to accommodate new multi-session deletion logic.
- Improved responsiveness of sessions table with new CSS rules for mobile layouts.

* feat(sessions): add page deselection functionality and enhance error handling

- Implemented `onDeselectPage` method to allow deselecting specific pages in the session view.
- Updated `deleteSessionsAndRefresh` to handle multiple deletion errors, storing them in an array and displaying a consolidated error message.
- Enhanced tests to verify the new deselection behavior and updated error handling for session deletions.
2026-03-21 18:33:05 -05:00
Vincent Koc
91f404dc7e refactor(doctor): continue doctor flow extraction (#51920)
* refactor(doctor): extract shared warning formatters

* refactor(doctor): extract provider warning previews

* style(doctor): sort telegram imports
2026-03-21 16:32:11 -07:00
Peter Steinberger
37d5cbe43a test: trim test startup overhead 2026-03-21 23:30:51 +00:00
Peter Steinberger
cf4d301a69 perf: reduce memory startup overhead 2026-03-21 23:30:15 +00:00
Vincent Koc
80441baa15 perf(core): trim provider and inbound startup imports (#51927)
* fix(telegram): fail fast on stuck getUpdates

* perf(core): trim provider and inbound startup imports
2026-03-21 16:20:42 -07:00
Vincent Koc
9854466a04 ci(actions): optimize main CI lanes (#51912)
* ci(actions): optimize main ci lanes

* ci(actions): drop unused secrets lane

* ci(actions): keep build-smoke on prs
2026-03-21 16:15:58 -07:00
Peter Steinberger
9dea537bae fix(cli): clean daemon install imports 2026-03-21 23:14:40 +00:00
Peter Steinberger
a622eecd3b refactor(test): dedupe shared test helpers 2026-03-21 23:07:51 +00:00
Peter Steinberger
29b165e456 refactor: centralize node startup tls planning 2026-03-21 15:58:42 -07:00
Vincent Koc
5b31b3400e refactor(doctor): continue provider and shared extractions (#51905)
* refactor(doctor): extract empty allowlist scanning

* refactor(doctor): extract matrix provider helpers

* refactor(doctor): extract matrix repair orchestration
2026-03-21 15:57:08 -07:00
Vincent Koc
5024967e57 fix(core): trim inbound startup churn (#51899)
* perf(core): narrow sandbox status imports for error helpers

* fix(core): trim inbound startup churn

* fix(auth): expire cached external cli sync state

* test(auth): avoid mtime sleep race in cache test
2026-03-21 15:55:19 -07:00
Vincent Koc
56b6585e2e ci(actions): skip heavy draft pr workflows 2026-03-21 15:50:32 -07:00
Vincent Koc
d88c68fec1 perf(core): narrow sandbox status imports for error helpers (#51897)
* perf(core): narrow sandbox status imports for error helpers

* fix(build): add runtime boundaries for reply understanding

Add missing lazy-load runtime shim files required by get-reply.ts.

* fix(debug): remove duplicate spacing in ingress logs

Use logIngressStage suffix spacing consistently for media and link understanding debug lines.
2026-03-21 15:40:45 -07:00
Vincent Koc
d3731be2f0 fix(config): use discord runtime api defaults 2026-03-21 15:21:42 -07:00
Gustavo Madeira Santana
5b3fce4c85 Outbound: preserve routed audioAsVoice delivery 2026-03-21 15:14:37 -07:00
Vincent Koc
21544f9e53 fix(doctor): harden follow-up repair paths (#51888)
* fix(doctor): harden follow-up repair paths

* fix(doctor): sanitize remaining warning paths
2026-03-21 15:11:01 -07:00
Vincent Koc
825d82b5c9 test(guardrails): use telegram public api in outbound targets 2026-03-21 15:08:43 -07:00
Vincent Koc
99641f01a5 perf(auth): reduce plugin auth cold-start heap (#51891)
* fix(test): recycle unit-fast ci batches

* refactor(config): narrow discord timeout import

* test(outbound): lighten target plugin stubs

* refactor(auth): narrow env api key resolution

* docs(auth): restore anthropic vertex sentinel comment

* refactor(auth): isolate console sanitizer
2026-03-21 15:07:08 -07:00
Vincent Koc
805aaa4ee8 fix(agents): avoid model catalog startup tax on telegram replies 2026-03-21 15:03:55 -07:00
Vincent Koc
5069c771e7 fix(test): recycle unit-fast CI batches (#51884)
* fix(test): recycle unit-fast ci batches

* refactor(config): narrow discord timeout import

* test(outbound): lighten target plugin stubs

* refactor(auth): narrow env api key resolution

* docs(auth): restore anthropic vertex sentinel comment
2026-03-21 14:56:29 -07:00
Vincent Koc
039ea5998e refactor(doctor): continue extracting shared doctor helpers (#51876)
* refactor(doctor): extract exec safe-bin helpers

* refactor(doctor): extract legacy tools-by-sender helpers

* refactor(doctor): extract default account warnings

* refactor(doctor): reuse canonical config path helpers
2026-03-21 14:28:14 -07:00
Bob
c2634b5e40 Agents: raise default timeout to 48h (#51874) 2026-03-21 21:54:46 +01:00
Peter Steinberger
4229ffe2b9 perf: reduce runtime and test startup overhead 2026-03-21 20:18:16 +00:00
Zhenye Dong
80959219ce fix(update): make up-to-date package status explicit (#51409)
Merged via squash.

Prepared head SHA: 75aba35882
Co-authored-by: dongzhenye <5765843+dongzhenye@users.noreply.github.com>
Co-authored-by: mcaxtr <7562095+mcaxtr@users.noreply.github.com>
Reviewed-by: @mcaxtr
2026-03-21 17:17:14 -03:00
Peter Steinberger
bfcfc17a8b refactor: tighten plugin sdk entry surface 2026-03-21 20:08:01 +00:00
Peter Steinberger
c29ba9d21a fix(docs): handle anchors in docs link audit 2026-03-21 19:58:36 +00:00
Bob
8cac327c19 ACP: recover hung bound turns (#51816)
* ACP: add hung-turn starvation repro

* ACP: recover hung bound turns

* ACP: preserve timed-out session handles

---------

Co-authored-by: Onur <2453968+osolmaz@users.noreply.github.com>
2026-03-21 20:54:30 +01:00
samzong
5c05347d11 fix(compaction): make compaction guard content-aware to prevent false cancellations in heartbeat sessions (#42119)
Merged via squash.

Prepared head SHA: 3429643315
Co-authored-by: samzong <13782141+samzong@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-21 12:27:35 -07:00
Menglin Li
ef7a5c3546 fix: use content hash for memory flush dedup instead of compactionCount (#30115) (#34222)
Merged via squash.

Prepared head SHA: bce6f0bda0
Co-authored-by: lml2468 <39320777+lml2468@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-21 12:06:25 -07:00
Matthew19990919
fb50c98d67 fix(tts): add matrix to VOICE_BUBBLE_CHANNELS (#37080)
Merged via squash.

Prepared head SHA: 89ca0355d4
Co-authored-by: Matthew19990919 <504525675@qq.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-03-21 11:29:53 -07:00
Jari Mustonen
fd2b3ed6af feat(memory): pluggable system prompt section for memory plugins (#40126)
Merged via squash.

Prepared head SHA: 5228d1937f
Co-authored-by: jarimustonen <1272053+jarimustonen@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-21 11:20:45 -07:00
Peter Steinberger
ebfc5f8240 fix: detect nvm services from installed command (#51146) 2026-03-21 11:06:31 -07:00
Peter Steinberger
40f5305cd2 fix: handle Linux nvm CA env before startup (#51146) (thanks @GodsBoy) 2026-03-21 11:06:31 -07:00
scoootscooob
d6367c2c55 refactor: route Telegram runtime through plugin sdk (#51772) 2026-03-21 10:24:56 -07:00
scoootscooob
c0e482f4bd refactor: route iMessage runtime through plugin sdk (#51770) 2026-03-21 10:21:20 -07:00
scoootscooob
6516cfa566 refactor: route Slack runtime through plugin sdk (#51766) 2026-03-21 10:11:17 -07:00
Vincent Koc
4e979ea6ca refactor(doctor): extract provider and shared config helpers (#51753)
* refactor(doctor): add shared doctor types

* refactor(doctor): add shared allowlist helpers

* refactor(doctor): extract empty allowlist warnings

* refactor(doctor): extract telegram allowfrom scanning

* refactor(doctor): extract telegram allowfrom repair

* refactor(doctor): extract discord id repair

* refactor(doctor): add shared object helpers

* refactor(doctor): extract mutable allowlist scanning

* refactor(doctor): extract open-policy allowfrom repair

* refactor(doctor): extract allowlist policy repair

* fix(doctor): unblock discord provider refactor checks

* refactor(doctor): fix provider layering in shared warnings
2026-03-21 10:09:38 -07:00
Harold Hunt
e24bf22f98 Fix Discord /codex_resume picker expiration (#51260)
Merged via squash.

Prepared head SHA: 76eb184dbe
Co-authored-by: huntharo <5617868+huntharo@users.noreply.github.com>
Co-authored-by: huntharo <5617868+huntharo@users.noreply.github.com>
Reviewed-by: @huntharo
2026-03-21 12:59:21 -04:00
Vincent Koc
f4227e2787 fix(ci): remove duplicate embedding default export 2026-03-21 09:39:37 -07:00
Vincent Koc
e61835ec5e fix(ci): restore embedding defaults and plugin boundaries 2026-03-21 09:32:14 -07:00
Pandadadadazxf
2fe0efc9e1 fix: compaction safeguard summary budget (#27727)
Merged via squash.

Prepared head SHA: a7ab64e394
Co-authored-by: Pandadadadazxf <200469161+Pandadadadazxf@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-21 09:27:51 -07:00
Val Alexander
7c520cc0ea web UI: fix context notice using accumulated inputTokens instead of prompt snapshot (#51721)
The context-usage banner in the web UI fell back to inputTokens when
totalTokens was missing. inputTokens is accumulated across all API
calls in a run (tool-use loops, compaction retries), so it overstates
actual context window utilization -- e.g. showing "100% context used
757.3k / 200k" when the real prompt snapshot is only 46k/200k (23%).

Drop the inputTokens fallback so the banner only fires when a genuine
prompt snapshot (totalTokens) is available.

Made-with: Cursor
2026-03-21 11:16:32 -05:00
Vincent Koc
15fd11032d fix(status): skip cold-start status probes 2026-03-21 08:59:19 -07:00
Vincent Koc
a267c5d9ae refactor(doctor): extract telegram provider warnings (#51704)
* refactor(doctor): extract telegram provider warnings

* docs(changelog): note doctor provider refactor
2026-03-21 08:57:06 -07:00
Vincent Koc
a90c5092f2 fix(telegram): default fresh setups to mention-gated groups 2026-03-21 08:54:23 -07:00
Vincent Koc
91b9be1549 docs(changelog): note telegram doctor first-run guidance 2026-03-21 08:46:28 -07:00
Vincent Koc
2ead75ea0e fix(doctor): add telegram first-run guidance 2026-03-21 08:46:28 -07:00
Vincent Koc
7918308b1a fix(doctor): suppress telegram fresh-install group warning 2026-03-21 08:46:28 -07:00
Vincent Koc
06b4efb1e7 fix(configure): drop duplicate wizard header 2026-03-21 08:41:57 -07:00
Vincent Koc
2e8c8a7ae6 refactor(openai): centralize provider defaults 2026-03-21 08:40:03 -07:00
Vincent Koc
931fc9989d docs(changelog): note fresh setup provider guidance 2026-03-21 08:34:44 -07:00
Vincent Koc
4c4eea97e9 fix(configure): tighten fresh setup provider UX 2026-03-21 08:34:44 -07:00
Vincent Koc
ffce904a10 fix(check): format onboard search test 2026-03-21 08:33:15 -07:00
Vincent Koc
a27aeeabf0 docs(changelog): note web search key copy 2026-03-21 08:22:52 -07:00
Vincent Koc
265386cd6b feat(plugins): register claude bundle commands natively 2026-03-21 08:14:48 -07:00
zidongdesign
2b68d20ab3 feat: notify user when context compaction starts and completes (#38805)
Merged via squash.

Prepared head SHA: 0f48c1bbf6
Co-authored-by: zidongdesign <81469543+zidongdesign@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-21 08:14:29 -07:00
Vincent Koc
1169d51aee fix(onboarding): clarify web search key copy 2026-03-21 08:13:39 -07:00
Vincent Koc
1698726c18 chore(deps): dedupe pnpm lockfile 2026-03-21 08:03:42 -07:00
Harold Hunt
5eb99a9b50 Infra: unify plugin split runtime state (#50725)
Merged via squash.

Prepared head SHA: 570b7b9459
Co-authored-by: huntharo <5617868+huntharo@users.noreply.github.com>
Co-authored-by: huntharo <5617868+huntharo@users.noreply.github.com>
Reviewed-by: @huntharo
2026-03-21 11:00:21 -04:00
Dinakar Sarbada
1643d15057 fix(matrix): pass agentId to buildMentionRegexes for agent-level mention patterns (#51272)
* fix(matrix): pass agentId to buildMentionRegexes for agent-level mention patterns

* fix(matrix): resolve conflicts from main branch

* Retrigger CI

---------

Co-authored-by: Dinakar Sarbada <dinakars777@users.noreply.github.com>
2026-03-21 07:43:59 -07:00
Vincent Koc
a3a5cad7d7 fix(onboarding): hide image-only auth providers 2026-03-21 07:42:42 -07:00
scoootscooob
f10d054745 refactor: route discord runtime through plugin sdk (#51444) 2026-03-21 07:40:51 -07:00
Echo
11aff6ed72 fix(auth): support remote Codex OAuth manual input (#51631)
* fix(auth): support remote codex oauth manual input

* fix: support remote Codex OAuth manual input (#51631) (thanks @cash-echo-bot)

---------

Co-authored-by: Cash Williams <cashwilliams@gmail.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-21 19:40:54 +05:30
Bijin
59c4059647 Add Community plugins - Wecom (#29905)
Merged via squash.

Prepared head SHA: da0f08436c
Co-authored-by: sliverp <38134380+sliverp@users.noreply.github.com>
Co-authored-by: frankekn <4488090+frankekn@users.noreply.github.com>
Reviewed-by: @frankekn
2026-03-21 22:03:24 +08:00
Tak Hoffman
4af79f20d5 fix ci main cancellation behavior 2026-03-21 08:57:37 -05:00
Harold Hunt
35e6310b22 Docs: refresh generated config baseline (#51625) 2026-03-21 09:41:56 -04:00
fuller-stack-dev
c9449d77b4 feat(gateway): persist webchat inbound images to disk (#51324)
* feat(gateway): persist webchat inbound images to disk

Images sent via the webchat control UI (chat.send RPC) were parsed into
content blocks but never written to disk, unlike WhatsApp and Telegram
handlers which call saveMediaBuffer(). This caused:

- Images lost after conversation compaction (only existed as ephemeral base64)
- Image editing/generation workflows failing for webchat-origin images
- Incomplete ~/.openclaw/media/inbound/ directory

After parseMessageWithAttachments extracts parsedImages, iterate and
persist each via saveMediaBuffer(buffer, mimeType, 'inbound'). Uses
fire-and-forget (.catch + warn log) so disk I/O never blocks the
chat.send response path.

Fixes #47930

* fix(gateway): address PR review comments on webchat image persistence

- Move saveMediaBuffer calls after sendPolicy/stop/dedupe checks so
  rejected or retried requests don't write files to disk (Codex P1)
- Await all saves and collect SavedMedia results into persistedImages
  so the persisted paths are available in scope (Greptile P1)
- Preserve Error stack trace in warn log instead of coercing to
  toString() (Greptile P2)
- Switch to Promise.all for concurrent writes

* fix(gateway): address remaining review comments on webchat image persistence

- Revert to fire-and-forget pattern (no await) to eliminate race window
  where retried requests miss the in-flight guard during image saves
- Remove unused SavedMedia import and persistedImages collection
- Use formatForLog for consistent error logging with stack traces
- Add NOTE comment about path propagation being a follow-up task

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

* fix(gateway): gate image persistence to webchat callers and defer base64 decode

* fix: drop unrelated format churn in lifecycle.test.ts

* gateway: clarify image persistence scope covers all chat.send callers

* fix(gateway): use generic chat.send log prefix for image persistence warnings

* fix(gateway): persist chat.send image refs in transcript

* fix(gateway): keep chat.send image refs off visible text

* fix(gateway): persist chat send media refs on dispatch

* fix(gateway): serialize chat send image persistence

* fix(gateway): persist chat send media after dispatch

* fix: persist chat.send inbound images across follow-ups (#51324) (thanks @fuller-stack-dev)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-21 19:05:11 +05:30
RichardCao
7bf437402d fix: trim config validation startup imports (#51574)
Merged via squash.

Prepared head SHA: 23b235e465
Co-authored-by: RichardCao <4612401+RichardCao@users.noreply.github.com>
Co-authored-by: huntharo <5617868+huntharo@users.noreply.github.com>
Reviewed-by: @huntharo
2026-03-21 09:31:39 -04:00
Tak Hoffman
6db6e117df fix(ui): use current session context usage in chat notice 2026-03-21 08:18:27 -05:00
Harold Hunt
8d733350de CI: run config docs drift check on PRs (#51618) 2026-03-21 09:13:41 -04:00
fuller-stack-dev
5137a51307 feat(github-copilot): resolve any model ID dynamically (#51325)
* feat(github-copilot): resolve any model ID dynamically instead of only known ones

* refactor(github-copilot): extract model resolution, add reasoning heuristic and tests

* fix(github-copilot): default synthetic models to text-only input

* ci: retrigger checks

* copilot: mark synthetic catch-all models as vision-capable

* fix(github-copilot): anchor reasoning regex, unexport internal constants, add mid-string test

* fix(github-copilot): default synthetic models to text-only input

* fix(github-copilot): restore image input for synthetic models with explanatory comment

* fix(github-copilot): normalize registry lookup casing, add bare o3 test case

* fix: preserve configured overrides for dynamic models

* fix: allow dynamic GitHub Copilot models (#51325) (thanks @fuller-stack-dev)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-21 17:37:50 +05:30
Ayaan Zaidi
42e708d005 fix(agents): register simple completion transports 2026-03-21 17:22:31 +05:30
Taras Lukavyi
466debb75c feat(telegram): auto-rename DM topics on first message (#51502)
* feat(telegram): auto-rename DM topics on first message

fix(telegram): use bot.api for topic rename to avoid SecretRef resolution

* fix(telegram): address security + test review feedback

- Fix test assertion: DEFAULT_PROMPT_SUBSTRING matches 'very short'
- Use RawBody instead of Body (no envelope metadata to LLM)
- Truncate user message to 500 chars for LLM prompt
- Remove user-derived content from verbose logs
- Remove redundant threadSpec.id null check
- Fix AutoTopicLabelParams type to match generateTopicLabel

* fix(telegram): use effective dm auto-topic config

* fix(telegram): detect direct auto-topic overrides

* fix: auto-rename Telegram DM topics on first message (#51502) (thanks @Lukavyi)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-21 16:53:30 +05:30
darkamenosa
cdf49f0b00 zalo: use photo_url for inbound images (#51543)
* Zalo: use photo_url for inbound images

* Tests: wait for Zalo webhook image processing
2026-03-21 17:21:44 +07:00
Josh Avant
3f7f2c8dc9 Voice Call: enforce spoken-output contract and fix stream TTS silence regression (#51500)
* voice-call: harden streaming startup and fallback behavior

* voice-call: suppress barge-in during intro

* voice-call: skip first-turn auto-response while greeting plays

* Voice-call: improve telephony audio fidelity and pacing

* voice-call: enforce spoken JSON and first-message barge skip

* voice-call: fix silent stream TTS regression

* voice-call: remove TTS timing diagnostics and document stream behavior

* voice-call: fail stream playback when stream sends are dropped

* voice-call: harden spoken contract and initial stream replay

* voice-call: suppress barge transcripts during initial greeting

* voice-call: harden stream fallback and media safety
2026-03-21 04:15:16 -05:00
Val Alexander
e9f715f27b UI: fix and optimize overview log panels (#51477) 2026-03-21 02:14:53 -05:00
Val Alexander
2fd372836e iOS: improve QR pairing flow (#51359)
- improve QR pairing UX and bootstrap token handling
- preserve repeated optimistic user messages during refresh
- add regression coverage for refresh reconciliation

Thanks @ImLukeF
2026-03-21 01:10:29 -05:00
Ayaan Zaidi
ce6a48195a test: fix whatsapp config-runtime mock store path 2026-03-21 11:39:21 +05:30
Ayaan Zaidi
8a05c05596 fix: defer plugin runtime globals until use 2026-03-21 11:14:48 +05:30
scoootscooob
43513cd1df test: refresh plugin import boundary baseline (#51434) 2026-03-20 22:36:11 -07:00
Ted Li
5bb5d7dab4 CLI: respect full timeout for loopback gateway probes (#47533)
* CLI: respect loopback gateway probe timeout

* CLI: name gateway probe budgets

* CLI: keep inactive loopback probes fast

* CLI: inline simple gateway probe caps

* Update helpers.ts

* Gateway: clamp probe timeout to timer-safe max

* fix: note loopback gateway probe timeout fix (#47533) (thanks @MonkeyLeeT)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-21 10:57:50 +05:30
scoootscooob
9fb78453e0 fix(discord): clarify startup readiness log (#51425)
Merged via squash.

Prepared head SHA: 390986dc47
Co-authored-by: scoootscooob <167050519+scoootscooob@users.noreply.github.com>
Co-authored-by: scoootscooob <167050519+scoootscooob@users.noreply.github.com>
Reviewed-by: @scoootscooob
2026-03-20 22:00:09 -07:00
scoootscooob
d78e13f545 fix(agent): clarify embedded transport errors (#51419)
Merged via squash.

Prepared head SHA: cea32a4bda
Co-authored-by: scoootscooob <167050519+scoootscooob@users.noreply.github.com>
Co-authored-by: scoootscooob <167050519+scoootscooob@users.noreply.github.com>
Reviewed-by: @scoootscooob
2026-03-20 21:47:47 -07:00
Cypherm
6b4c24c2e5 feat(telegram): support custom apiRoot for alternative API endpoints (#48842)
* feat(telegram): support custom apiRoot for alternative API endpoints

Add `apiRoot` config option to allow users to specify custom Telegram Bot
API endpoints (e.g., self-hosted Bot API servers). Threads the configured
base URL through all Telegram API call sites: bot creation, send, probe,
audit, media download, and api-fetch. Extends SSRF policy to dynamically
trust custom apiRoot hostname for media downloads.

Closes #28535

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

* fix(telegram): thread apiRoot through allowFrom lookups

* fix(telegram): honor lookup transport and local file paths

* refactor(telegram): unify username lookup plumbing

* fix(telegram): restore doctor lookup imports

* fix: document Telegram apiRoot support (#48842) (thanks @Cypherm)

---------

Co-authored-by: Cypherm <28184436+Cypherm@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-21 10:10:38 +05:30
wesley
598f1826d8 fix(subagent): include partial progress when subagent times out (#40700)
* fix(subagent): preserve timeout partial progress reporting

* refactor: unify subagent output selection

* test: cover distilled subagent timeout output

* fix: remove timeout-only subagent path

---------

Co-authored-by: Wesley <imwyvern@users.noreply.github.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-21 08:44:38 +05:30
Tyler Yust
5e417b44e1 Outbound: skip broadcast channel scan when channel is explicit 2026-03-20 18:21:01 -07:00
Tyler Yust
b71686ab44 Enhance web search provider config validation and compatibility handling
- Added a test to ensure no warnings for legacy Brave config when bundled web search allowlist compatibility is applied.
- Updated validation logic to incorporate compatibility configuration for bundled web search plugins.
- Refactored the ensureRegistry function to utilize the new compatibility handling.
2026-03-20 18:20:50 -07:00
Vincent Koc
c3be293dd5 fix(slack): unify slash conversation-runtime mock 2026-03-20 18:19:07 -07:00
Danh Doan
e78129a4d9 feat(context-engine): pass incoming prompt to assemble (#50848)
Merged via squash.

Prepared head SHA: 282dc9264d
Co-authored-by: danhdoan <12591333+danhdoan@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-20 17:03:21 -07:00
Sally O'Malley
6a6f1b5351 changelog (#51322)
Signed-off-by: sallyom <somalley@redhat.com>
2026-03-20 19:30:33 -04:00
Josh Lehman
751d5b7849 feat: add context engine transcript maintenance (#51191)
Merged via squash.

Prepared head SHA: b42a3c28b4
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-20 16:28:27 -07:00
Peter Steinberger
6526074c85 test: trim singleton cold-start reloads 2026-03-20 23:14:28 +00:00
Peter Steinberger
0a842de354 test: widen low-profile singleton batching 2026-03-20 23:02:33 +00:00
Josh Lehman
2364e45fe4 test: align extension runtime mocks with plugin-sdk (#51289)
* test: align extension runtime mocks with plugin-sdk

Update stale extension tests to mock the plugin-sdk runtime barrels that production code now imports, and harden the Signal tool-result harness around system-event assertions so the channels lane matches current extension boundaries.

Regeneration-Prompt: |
  Verify the failing channels-lane tests against current origin/main in an isolated worktree before changing anything. If the failures reproduce on main, keep the fix test-only unless production behavior is clearly wrong. Recent extension refactors moved Telegram, WhatsApp, and Signal code onto plugin-sdk runtime barrels, so update stale tests that still mock old core module paths to intercept the seams production code now uses. For Signal reaction notifications, avoid brittle assertions that depend on shared queued system-event state when a direct harness spy on enqueue behavior is sufficient. Preserve scope: only touch the failing tests and their local harness, then rerun the reproduced targeted tests plus the full channels lane and repo check gate.

* test: fix extension test drift on main

* fix: lazy-load bundled web search plugin registry

* test: make matrix sweeper failure injection portable

* fix: split heavy matrix runtime-api seams

* fix: simplify bundled web search id lookup

* test: tolerate windows env key casing
2026-03-20 15:59:53 -07:00
Vincent Koc
e635cedb85 test(openai): cover bundle media surfaces 2026-03-20 15:53:12 -07:00
Vincent Koc
d54ebed7c8 test(openai): add plugin entry live coverage 2026-03-20 15:53:12 -07:00
Vincent Koc
d1d46c6cfb test(openai): broaden live model coverage 2026-03-20 15:53:12 -07:00
Vincent Koc
f1802a5bc7 test(openai): add live provider probe 2026-03-20 15:53:12 -07:00
Sally O'Malley
6e20c4baa0 feat: add anthropic-vertex provider for Claude via GCP Vertex AI (#43356)
Reuse pi-ai's Anthropic client injection seam for streaming, and add
the OpenClaw-side provider discovery, auth, model catalog, and tests
needed to expose anthropic-vertex cleanly.

Signed-off-by: sallyom <somalley@redhat.com>
2026-03-20 18:48:42 -04:00
Vincent Koc
42ca447189 test(openrouter): add live plugin coverage 2026-03-20 15:36:34 -07:00
Peter Steinberger
fac64c2392 test: widen unit timing snapshot coverage 2026-03-20 22:33:49 +00:00
Peter Steinberger
39a4fe576d test: normalize perf manifest paths 2026-03-20 22:06:46 +00:00
Josh Lehman
c3972982b5 fix: sanitize malformed replay tool calls (#50005)
Merged via squash.

Prepared head SHA: 64ad5563f7
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-20 15:03:30 -07:00
Peter Steinberger
cadbaa34c1 test: widen low-profile scheduler peeling 2026-03-20 21:30:44 +00:00
Peter Steinberger
994b42a5a5 test: parallelize safe audit case tables 2026-03-20 21:16:01 +00:00
Peter Steinberger
aed1f6d807 test: parallelize low-profile deferred lanes 2026-03-20 21:07:56 +00:00
Peter Steinberger
09cf6d80ec test: batch thread-only unit lanes 2026-03-20 20:51:38 +00:00
Josh Avant
7abfff756d Exec: harden host env override handling across gateway and node (#51207)
* Exec: harden host env override enforcement and fail closed

* Node host: enforce env override diagnostics before shell filtering

* Env overrides: align Windows key handling and mac node rejection
2026-03-20 15:44:15 -05:00
Josh Avant
c7134e629c LINE: harden Express webhook parsing to verified raw body (#51202)
* LINE: enforce signed-raw webhook parsing

* LINE: narrow scope and add buffer regression

* changelog

Signed-off-by: joshavant <830519+joshavant@users.noreply.github.com>

---------

Signed-off-by: joshavant <830519+joshavant@users.noreply.github.com>
2026-03-20 15:32:55 -05:00
Vincent Koc
11d71ca352 pairing: keep setup codes bootstrap-token only (#51259) 2026-03-20 13:27:39 -07:00
Peter Steinberger
5a5e84ca1d test: drop duplicate web search helper 2026-03-20 20:25:24 +00:00
Peter Steinberger
fa71ad7c5d test: repair latest-main web search regressions 2026-03-20 20:17:11 +00:00
Josh Lehman
23fef04c4e test: fix setup finalize web search mocks (#51253) 2026-03-20 13:07:22 -07:00
Peter Steinberger
1b18742e8e test: peel more slow unit files out of unit-fast 2026-03-20 20:04:52 +00:00
Teddy Tennant
a20ba74978 test: add SSRF guard coverage for URL credential bypass vectors (#50523)
* security: add SSRF guard tests for URL credential bypass vectors

* test(security): strengthen SSRF redirect guard coverage

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-20 12:45:06 -07:00
Gustavo Madeira Santana
3da66718f4 Web: derive search provider metadata from plugin contracts (#50935)
Merged via squash.

Prepared head SHA: e1c7d72833
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-03-20 12:41:04 -07:00
Peter Steinberger
acf32287b4 test: trim more extension startup from unit tests 2026-03-20 19:28:32 +00:00
Jaaneek
916f496b51 Add Grok 4.20 reasoning and non-reasoning to xAI model catalog (#50772)
Merged via squash.

Prepared head SHA: 095e645ea5
Co-authored-by: Jaaneek <25470423+Jaaneek@users.noreply.github.com>
Co-authored-by: huntharo <5617868+huntharo@users.noreply.github.com>
Reviewed-by: @huntharo
2026-03-20 15:28:30 -04:00
Peter Steinberger
f6b3245a7b fix: pass full sdk gate 2026-03-20 19:24:10 +00:00
Peter Steinberger
62ddc9d9e0 refactor: consolidate plugin sdk surface 2026-03-20 19:24:10 +00:00
Vincent Koc
46854a84a4 test(plugin-sdk): cover legacy root diagnostic listeners 2026-03-20 12:23:02 -07:00
Peter Steinberger
7b00a0620a test: stabilize gateway alias coverage 2026-03-20 19:17:44 +00:00
Gustavo Madeira Santana
a05da76718 Matrix: dedupe replayed inbound events on restart (#50922)
Merged via squash.

Prepared head SHA: 10d9770aa6
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-03-20 12:13:24 -07:00
Vincent Koc
5408a3d1a4 docs(contributing): clarify accepted PR scope 2026-03-20 12:04:16 -07:00
Peter Steinberger
39053bddd7 test: decouple zalo outbound payload contract from channel runtime 2026-03-20 19:02:07 +00:00
Peter Steinberger
a7401366ef test: trim more channel-heavy startup in unit tests 2026-03-20 18:50:52 +00:00
Vincent Koc
083f825122 docs: expand community plugins (always visible), add Codex App Server/Lossless Claw/Opik, A-Z order 2026-03-20 11:40:50 -07:00
Peter Steinberger
b26edfe1ff test: trim plugin-heavy unit test imports 2026-03-20 18:35:39 +00:00
Vincent Koc
740b345a2e docs: sort Tools nav group alphabetically 2026-03-20 11:33:51 -07:00
Vincent Koc
483926a6fb docs: rewrite sdk-migration and bundles, fold agent-tools into building-plugins, remove cookbook from nav, remove dead WeChat listing 2026-03-20 11:32:11 -07:00
Vincent Koc
2e0b445b46 docs: use expandable Accordions for community plugins, keep A-Z order 2026-03-20 11:27:45 -07:00
Tak Hoffman
16e055c083 restore extension-api backward compatibility with migration warning 2026-03-20 13:27:30 -05:00
Vincent Koc
e4d0fdcc15 docs: rewrite community plugins page with Cards, Steps, and quality bar table 2026-03-20 11:23:46 -07:00
Vincent Koc
fb293fa36f docs: rewrite plugins install/configure page with Steps, Accordions, and clear hierarchy 2026-03-20 11:20:36 -07:00
Vincent Koc
a4a5ed8948 docs: retitle plugin internals/agent-tools/cookbook, collapse Browser into Tools, reorder Plugins group 2026-03-20 11:17:49 -07:00
Vincent Koc
4edab304db docs: reorder Tools & Plugins nav, move Media/devices to Gateway tab, rewrite 4 problem pages with Mintlify components 2026-03-20 11:10:45 -07:00
Vincent Koc
3d097f1052 docs: rewrite tools landing page with Tools/Skills/Plugins explainer using Steps 2026-03-20 11:02:01 -07:00
Vincent Koc
e18ab85f08 docs(agents): clarify plugin nomenclature 2026-03-20 10:59:29 -07:00
Vincent Koc
5f600e117d docs: restructure Tools & Plugins section, rename building-extensions to building-plugins, rewrite tools landing page and SDK migration 2026-03-20 10:55:56 -07:00
Ayaan Zaidi
35ac1f6e07 fix: add changelog for telegram account routing fix (#50853) (thanks @hclsys) 2026-03-20 23:24:40 +05:30
HCL
4e45a663e7 fix(telegram): prevent silent wrong-bot routing when accountId not in config
When a non-default accountId is specified but not found in the accounts
config, resolveTelegramToken() falls through to channel-level defaults
(botToken, tokenFile, env) — silently routing messages via the wrong
bot's token. This is a cross-bot message leak with no error or warning.

Root cause: extensions/telegram/src/token.ts:44-46, resolveAccountCfg()
returns undefined for unknown accountIds but code continues to fallbacks.
Introduced in e5bca0832f when Telegram moved to extensions/.

Fix: return { token: "", source: "none" } with a diagnostic log when
a non-default accountId is not found. Existing behavior for known
accounts (with or without per-account tokens) preserved.

Test: added "does not fall through when non-default accountId not in
config" — 1/1 new, 10/10 existing unaffected.

Closes #49383

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: HCL <chenglunhu@gmail.com>
2026-03-20 23:24:40 +05:30
Vincent Koc
c64893a9c2 fix(config): use static channel metadata in docs baseline (#51161) 2026-03-20 10:52:40 -07:00
Vincent Koc
ad4536fd7e docs: rename Extensions to Plugins, rewrite building guide as capability-agnostic, move voice-call to Channels 2026-03-20 10:45:56 -07:00
Peter Steinberger
1cabb053ad test: lazy-load default setup registry 2026-03-20 17:43:49 +00:00
Vincent Koc
23a119c6ea test(msteams): clear remaining rebase conflict hunk 2026-03-20 10:38:55 -07:00
Vincent Koc
42801f6178 fix(plugin-sdk): dedupe rebased zalo export entries 2026-03-20 10:38:55 -07:00
Vincent Koc
5b7ae24e30 test(msteams): align adapter doubles with interfaces 2026-03-20 10:38:55 -07:00
Vincent Koc
a2e1991ed3 refactor(plugin-sdk): route bundled runtime barrels through public subpaths 2026-03-20 10:38:55 -07:00
Vincent Koc
fb3550ef5e test(sessions): stabilize pruning integration setup 2026-03-20 10:38:55 -07:00
Vincent Koc
58889f984f docs: set sidebar title to SDK Migration 2026-03-20 10:32:51 -07:00
Vincent Koc
06311f89e0 docs: escape angle brackets in sdk-migration to fix Mintlify MDX build 2026-03-20 10:32:01 -07:00
Peter Steinberger
fa275fddf8 docs: refresh config baseline 2026-03-20 17:29:37 +00:00
Vincent Koc
96e1c37685 docs: improve Building Extensions with Mintlify Steps, Accordion, and Warning components 2026-03-20 10:24:51 -07:00
Vincent Koc
a39c440d39 fix(config): share json compatibility parsing 2026-03-20 10:17:53 -07:00
Harold Hunt
4838e3934b Tests: default CI unit lanes to forks (#51145) 2026-03-20 13:15:55 -04:00
Saurabh Mishra
4266e260e1 fix: emit message:sent hook on Telegram streaming preview finalization (#50917)
* fix: emit message:sent hook on Telegram streaming preview finalization

* fix: include messageId in preview-delivered hook callback

* fix: skip message:sent hook for preview-retained paths

* fix: correct JSDoc for onPreviewDelivered callback

* fix: pass visible preview text on regressive-skip path

* fix: remove dead fallbacks and add stopCreatesFirstPreview test

* Update extensions/telegram/src/lane-delivery-text-deliverer.ts

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* fix: align telegram preview sent hooks (#50917) (thanks @bugkill3r)

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-20 22:42:04 +05:30
Peter Steinberger
85a5d64d8f test: speed up isolated test lanes 2026-03-20 17:11:23 +00:00
Vincent Koc
93fbe26adb fix(config): tighten json and json5 parsing paths (#51153) 2026-03-20 10:10:57 -07:00
Vincent Koc
87eeab7034 docs: add plugin SDK migration guide, link deprecation warning to docs 2026-03-20 10:05:06 -07:00
Peter Steinberger
fcabecc9a4 fix: remove duplicate plugin sdk exports 2026-03-20 16:52:10 +00:00
Peter Steinberger
18fa2992f9 fix: restore plugin sdk runtime barrels 2026-03-20 16:46:34 +00:00
Peter Steinberger
cb89325cd8 fix: restore latest main gate 2026-03-20 16:46:34 +00:00
Peter Steinberger
4c614c230d fix: restore local gate 2026-03-20 16:46:14 +00:00
Vincent Koc
aa78a0c00e refactor(plugin-sdk): formalize runtime contract barrels 2026-03-20 09:30:34 -07:00
Vincent Koc
9b6f286ac2 refactor(channels): share route format and binding helpers 2026-03-20 09:30:34 -07:00
Vincent Koc
faa9faa767 refactor(web-search): share provider clients and config helpers 2026-03-20 09:30:34 -07:00
Vincent Koc
d3ffa1e4e7 refactor(errors): share api error payload parsing 2026-03-20 09:30:33 -07:00
Vincent Koc
dbc9d3dd70 fix(plugin-sdk): restore root diagnostic compat 2026-03-20 09:27:37 -07:00
Peter Steinberger
50ce9ac1c6 refactor: privatize bundled sdk facades 2026-03-20 15:56:14 +00:00
Peter Steinberger
f6948ce405 refactor: shrink sdk helper surfaces 2026-03-20 15:43:14 +00:00
Peter Steinberger
ba1bb8505f refactor: install optional channels for directory 2026-03-20 15:37:56 +00:00
sudie-codes
06845a1974 fix(msteams): resolve Graph API chat ID for DM file uploads (#49585)
Fixes #35822 — Bot Framework conversation.id format is incompatible with
Graph API /chats/{chatId}. Added resolveGraphChatId() to look up the
Graph-native chat ID via GET /me/chats, cached in the conversation store.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 10:08:26 -05:00
sudie-codes
7c3af3726f msteams: extend MSTeamsAdapter and MSTeamsActivityHandler types; implement self() (#49929)
- Add updateActivity/deleteActivity to MSTeamsAdapter
- Add onReactionsAdded/onReactionsRemoved to MSTeamsActivityHandler
- Implement directory self() to return bot identity from appId credential
- Add tests for self() in channel.directory.test.ts
2026-03-20 10:08:23 -05:00
sudie-codes
897cda7d99 msteams: fix sender allowlist bypass when route allowlist is configured (GHSA-g7cr-9h7q-4qxq) (#49582)
When a route-level (teams/channel) allowlist was configured but the sender
allowlist (allowFrom/groupAllowFrom) was empty, resolveSenderScopedGroupPolicy
would downgrade the effective group policy from "allowlist" to "open", allowing
any Teams user to interact with the bot.

The fix: when channelGate.allowlistConfigured is true and effectiveGroupAllowFrom
is empty, preserve the configured groupPolicy ("allowlist") rather than letting
it be downgraded to "open". This ensures an empty sender allowlist with an active
route allowlist means deny-all rather than allow-all.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 10:08:19 -05:00
John Scianna
5607da90d5 feat: pass modelId to context engine assemble() (#47437)
Merged via squash.

Prepared head SHA: d708ddb222
Co-authored-by: jscianna <9017016+jscianna@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-20 08:05:02 -07:00
Johnson Shi
dc86b6d72a docs(azure): replace ARM template deployment with pure az CLI commands (#50700)
* docs(azure): replace ARM template deployment with pure az CLI commands

Rewrites the Azure install guide to use individual az CLI commands
instead of referencing ARM templates in infra/azure/templates/ (removed
upstream). Each Azure resource (NSG, VNet, subnets, VM, Bastion) is now
created with explicit az commands, preserving the same security posture
(Bastion-only SSH, no public IP, NSG hardening).

Also addresses BradGroux review feedback from #47898:
- Add cost considerations section (Bastion ~$140/mo, VM ~$55/mo)
- Add cleanup/teardown section (az group delete)
- Remove stale /install/azure/azure redirect from docs.json

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs(azure): split into multiple Steps blocks for richer TOC

Add Quick path and What you need sections. Split the single Steps
block into three (Configure deployment, Deploy Azure resources,
Install OpenClaw) so H2 headers appear in the Mintlify sidebar TOC.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs(azure): remove Quick path section

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs(azure): fix cost section LaTeX rendering, remove comparison

Escape dollar signs to prevent Mintlify LaTeX interpretation.
Also escape underscores in VM SKU name within bold text.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs(azure): add caveat that deallocated VM stops Gateway

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs(azure): simplify install step with clearer description

Download then run pattern (no sudo). Clarify that installer handles
Node LTS, dependencies, OpenClaw install, and onboarding wizard.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs(azure): add Bastion provisioning latency note

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs(azure): use deployment variables in cost and cleanup sections

Replace hardcoded rg-openclaw/vm-openclaw with variables in
deallocate/start and group delete commands so users who customized
names in step 3 get correct commands.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs(azure): fix formatting (oxfmt)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-20 09:23:21 -05:00
Fabian Williams
99e53612cb docs: add delegate architecture guide for organizational deployments (#43261)
* docs: add delegate architecture guide for organizational deployments

Adds a guide for running OpenClaw as a named delegate for organizations.
Covers three capability tiers (read-only, send-on-behalf, proactive),
M365 and Google Workspace delegation setup, security guardrails, and
integration with multi-agent routing.

AI-assisted: Claude Code (Opus 4.6)
Based on: Production deployment at a 501(c)(3) nonprofit

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

* docs: address review — add Google DWD warning, fix canvas in deny list

- Add security warning for Google Workspace domain-wide delegation
  matching the existing M365 application access policy warning
- Add "canvas" to the security guardrails tool deny list for
  consistency with the full example and multi-agent.md

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

* docs: fix Tier 1 description to match read-only permissions

Remove "draft replies (saved to Drafts folder)" from Tier 1 since
saving drafts requires write access. Tier 1 is strictly read-only —
the agent summarizes and flags via chat, human acts on the mailbox.

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

* style: fix oxfmt formatting for delegate-architecture and docs.json

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

* docs: fix broken links to /automation/standing-orders

Standing orders is a deployment pattern, not an existing doc page.
Replaced with inline descriptions and links to /automation/cron-jobs
and #security-guardrails anchor.

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

* docs: move hardening to prerequisites before identity provider setup

Restructure per community feedback: isolation, tool restrictions,
sandbox, hard blocks, and audit trail now come BEFORE granting any
credentials. The most dangerous step (tenant-wide permissions) no
longer precedes the most important step (scoping and isolation).

Also strengthened M365 and Google Workspace security warnings with
actionable verification steps.

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

* docs: add standing orders guide and fix broken links

Add docs/automation/standing-orders.md covering:
- Why standing orders (agent autonomy vs human bottleneck)
- Anatomy of a standing order (scope, triggers, gates, escalation)
- Integration with cron jobs for time-based enforcement
- Execute-Verify-Report pattern for execution discipline
- Three production-tested examples (content, finance, monitoring)
- Multi-program architecture for complex agents
- Best practices (do's and don'ts)

Update delegate-architecture.md to link standing orders references
to the new page instead of dead links.

Add standing-orders to Automation nav group in docs.json (en + zh-CN).

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

* docs: address review feedback on standing-orders

- P1: Clarify that standing orders should go in AGENTS.md (auto-injected)
  rather than arbitrary subdirectory files. Add Tip callout explaining
  which workspace files are bootstrapped.
- P2: Remove dead /concepts/personality-files link, replace with
  /concepts/agent-workspace which covers bootstrap files.

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 09:23:17 -05:00
Thirumalesh
c6968c39d6 feat(compaction): truncate session JSONL after compaction to prevent unbounded growth (#41021)
Merged via squash.

Prepared head SHA: fa50b63580
Co-authored-by: thirumaleshp <85149081+thirumaleshp@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-20 07:15:09 -07:00
Ayaan Zaidi
4c60956d8e build(android): update Gradle tooling 2026-03-20 17:12:10 +05:30
Ayaan Zaidi
3bda64f75c perf(android): reduce tab-switch CPU churn 2026-03-20 17:10:18 +05:30
caesargattuso
57f1cf66ad fix(gateway): skip seq-gap broadcast for stale post-lifecycle events (#43751)
* fix: stop stale gateway seq-gap errors (#43751) (thanks @caesargattuso)

* fix: keep agent.request run ids session-scoped

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-20 14:56:54 +05:30
Bijin
192f859325 Add Community plugins - openclaw-dingtalk (#29913)
Merged via squash.

Prepared head SHA: e8e99997cb
Co-authored-by: sliverp <38134380+sliverp@users.noreply.github.com>
Co-authored-by: frankekn <4488090+frankekn@users.noreply.github.com>
Reviewed-by: @frankekn
2026-03-20 16:58:51 +08:00
Bijin
6cb2fc501a Community plugins - Add QQbot (#29898)
Merged via squash.

Prepared head SHA: c776a12d15
Co-authored-by: sliverp <38134380+sliverp@users.noreply.github.com>
Co-authored-by: frankekn <4488090+frankekn@users.noreply.github.com>
Reviewed-by: @frankekn
2026-03-20 16:51:32 +08:00
Vincent Koc
df536c3248 test(signal): harden tool-result infra-runtime mock 2026-03-20 01:33:16 -07:00
Vincent Koc
d774b3f274 fix(ci): isolate jiti-mocked test files 2026-03-20 01:24:32 -07:00
Vincent Koc
dc06e4fd22 ci: collapse extra workflow guards into check-additional 2026-03-20 01:20:12 -07:00
Vincent Koc
0fae764f10 test(plugins): use sync jiti regression path 2026-03-20 01:12:05 -07:00
Vincent Koc
95f890a8b2 test(plugins): relax jiti error string assertions 2026-03-20 01:07:29 -07:00
Vincent Koc
f0a0a6a5b4 test(plugins): isolate git path alias regression 2026-03-20 00:57:25 -07:00
Vincent Koc
68a274c7b3 fix(ci): isolate loader git-path regression env roots 2026-03-20 00:43:03 -07:00
Vincent Koc
d25f6f1833 fix(ci): restore full loader regression coverage 2026-03-20 00:38:11 -07:00
Vincent Koc
f1e012e0fc fix(telegram): serialize thread binding persists 2026-03-20 00:30:11 -07:00
Vincent Koc
9f8af3604d fix(ci): split slow plugin loader regression test 2026-03-20 00:28:04 -07:00
Vincent Koc
faa8e27291 fix(ci): share compat matrix and restore skill python gating 2026-03-20 00:27:50 -07:00
Ayaan Zaidi
8ac4d13a6f style(docs): format plugin table 2026-03-20 12:56:32 +05:30
Ayaan Zaidi
0c2e6fe97f ci(android): use explicit flavor debug tasks 2026-03-20 12:55:52 +05:30
Ayaan Zaidi
f09f98532c feat(android): hide restricted capabilities in play builds 2026-03-20 12:45:25 +05:30
Ayaan Zaidi
ecec0d5b2c build(android): add play and third-party release flavors 2026-03-20 12:45:25 +05:30
Vincent Koc
dfc157e1a2 test(plugins): trim loader regression harness churn 2026-03-20 00:06:12 -07:00
Vincent Koc
3a72d2d6de fix(config): split config doc baseline coverage 2026-03-20 00:06:12 -07:00
Vincent Koc
e56dde815e fix(web-search): split runtime provider resolution 2026-03-20 00:06:12 -07:00
Vincent Koc
397b0d85f5 fix(tui): split assistant error formatting seam 2026-03-20 00:06:12 -07:00
Saurabh Mishra
709c730e2a fix: standardize 'MS Teams' to 'Microsoft Teams' across docs (#50863)
* fix: standardize 'MS Teams' to 'Microsoft Teams' across docs

* Apply suggestion from @greptile-apps[bot]

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-03-19 23:54:47 -07:00
Vincent Koc
a562fb5550 refactor(web-search): share scoped provider config plumbing 2026-03-19 23:52:53 -07:00
Vincent Koc
96f21c37b4 fix(tools): persist remaining doctor compatibility aliases 2026-03-19 23:42:53 -07:00
Vincent Koc
6c7526f8a0 fix(web-search): share unsupported filter handling 2026-03-19 23:41:02 -07:00
Vincent Koc
ce878a9eb1 fix(test): batch unit-fast worker lifetimes 2026-03-19 23:30:48 -07:00
Vincent Koc
36a59d5c79 fix(discord): drop stale carbon deploy option 2026-03-19 23:30:48 -07:00
Vincent Koc
9af42c6590 fix(config): persist doctor compatibility migrations 2026-03-19 23:28:11 -07:00
Shakker
098a0d0d0d chore(docs): refresh generated config baseline 2026-03-20 06:17:08 +00:00
Shakker
f2849c2417 fix(feishu): stabilize lifecycle replay tests 2026-03-20 06:13:27 +00:00
Shakker
8d805a02fd fix(zalouser): decouple tests from zca-js runtime 2026-03-20 06:13:27 +00:00
Shakker
5036ed2699 fix(secrets): cover tavily in runtime coverage tests 2026-03-20 06:13:27 +00:00
Shakker
06fc498d54 chore(docs): refresh secretref credential matrix 2026-03-20 06:13:27 +00:00
Shakker
94ab044387 fix(ci): split unit-fast into bounded shared-worker lanes 2026-03-20 06:13:27 +00:00
Shakker
4d9ae5899d chore(ci): refresh Linux unit memory hotspots from PR failures 2026-03-20 06:13:27 +00:00
Shakker
b90eef50ec fix(ci): widen Linux memory-hotspot isolation cap 2026-03-20 06:13:27 +00:00
Shakker
829beced04 fix(ci): avoid Windows shell arg overflow in unit-fast 2026-03-20 06:13:27 +00:00
Shakker
3db2cfef07 chore(ci): refresh unit memory hotspot manifest 2026-03-20 06:13:27 +00:00
Shakker
d689b3fc89 fix(ci): prioritize memory-heavy unit scheduling 2026-03-20 06:13:27 +00:00
Shakker
254ea0c65e fix(ci): parse GitHub Actions memory hotspot logs 2026-03-20 06:13:27 +00:00
Shakker
9c7da58770 fix(ci): auto-isolate memory-heavy unit tests 2026-03-20 06:13:27 +00:00
Shakker
fe863c5400 chore(ci): seed unit memory hotspot manifest 2026-03-20 06:13:27 +00:00
Ayaan Zaidi
a73e517ae3 build(protocol): regenerate swift talk models 2026-03-20 11:12:53 +05:30
Ayaan Zaidi
2afd65741c fix: preserve talk provider and speaking state 2026-03-20 11:08:21 +05:30
Ayaan Zaidi
61965e500f fix: route Android Talk synthesis through the gateway (#50849) 2026-03-20 11:01:24 +05:30
Ayaan Zaidi
47e412bd0b fix(review): preserve talk directive overrides 2026-03-20 11:01:24 +05:30
Ayaan Zaidi
4a0341ed03 fix(review): address talk cleanup feedback 2026-03-20 11:01:24 +05:30
Ayaan Zaidi
4386a0ace8 refactor(android): remove legacy elevenlabs talk stack 2026-03-20 11:01:24 +05:30
Ayaan Zaidi
e3afaca1a6 refactor(android): route talk playback through gateway 2026-03-20 11:01:24 +05:30
Ayaan Zaidi
f7fe75a68b refactor(android): simplify talk config parsing 2026-03-20 11:01:24 +05:30
Ayaan Zaidi
4ac355babb feat(gateway): add talk speak rpc 2026-03-20 11:01:24 +05:30
Ayaan Zaidi
84ee6fbb76 feat(tts): add in-memory speech synthesis 2026-03-20 11:01:24 +05:30
Lakshya Agarwal
b36e456b09 feat: add Tavily as a bundled web search plugin with search and extract tools (#49200)
Merged via squash.

Prepared head SHA: ece9226e88
Co-authored-by: lakshyaag-tavily <266572148+lakshyaag-tavily@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-03-20 01:06:26 -04:00
Gustavo Madeira Santana
914fc265c5 Docs(matrix): add changelog entry for allowBots/allowPrivateNetwork 2026-03-20 00:22:52 -04:00
Gustavo Madeira Santana
1ba70c3707 Docs: switch MiniMax defaults to M2.7 2026-03-20 00:05:04 -04:00
ernestodeoliveira
80110c550f fix(telegram): warn when setup leaves dmPolicy as pairing without allowFrom (#50710)
* fix(telegram): warn when setup leaves dmPolicy as pairing without allowFrom

* fix(telegram): scope setup warning to account config

* fix(telegram): quote setup allowFrom example

* fix: warn on insecure Telegram setup defaults (#50710) (thanks @ernestodeoliveira)

---------

Co-authored-by: Claude Code <claude-code@openclaw.ai>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-20 09:29:33 +05:30
Shakker
991eb2ef03 fix(ci): isolate missing unit-fast heap hotspots 2026-03-20 03:50:46 +00:00
Shakker
4aef83016f fix(matrix): mock configured bot ids in monitor tests 2026-03-20 03:50:06 +00:00
Shakker
03c86b3dee fix(secrets): mock bundled web search providers in runtime tests 2026-03-20 03:48:13 +00:00
Shakker
62e6eb117e chore(docs): refresh generated config baseline 2026-03-20 03:34:11 +00:00
Shakker
218f8d74b6 fix(secrets): use bundled web search fast path during reload 2026-03-20 03:28:08 +00:00
Shakker
2d24f35016 fix(plugins): add bundled web search provider metadata 2026-03-20 03:28:08 +00:00
Gustavo Madeira Santana
9c21637fe9 Docs: clarify Matrix private-network homeserver setup 2026-03-19 23:24:51 -04:00
Gustavo Madeira Santana
f62be0ddcf Matrix: guard private-network homeserver access 2026-03-19 23:24:50 -04:00
Gustavo Madeira Santana
ab97cc3f11 Matrix: add allowBots bot-to-bot policy 2026-03-19 23:24:50 -04:00
Josh Avant
de9f2dc227 Gateway: harden OpenResponses file-context escaping (#50782) 2026-03-19 22:02:13 -05:00
Jinhao Dong
4f00b3b534 feat(xiaomi): add MiMo V2 Pro and MiMo V2 Omni models, switch to OpenAI completions API (#49214)
Merged via squash.

Prepared head SHA: 6b672f36cf
Co-authored-by: DJjjjhao <50042705+DJjjjhao@users.noreply.github.com>
Co-authored-by: grp06 <1573959+grp06@users.noreply.github.com>
Reviewed-by: @grp06
2026-03-19 19:26:47 -07:00
Harold Hunt
f1ce679929 Discord: reconcile native commands without restart churn (#46597)
Merged via squash.

Prepared head SHA: 37090daad4
Co-authored-by: huntharo <5617868+huntharo@users.noreply.github.com>
Reviewed-by: @huntharo
2026-03-19 22:23:21 -04:00
Harold Hunt
65594f972c Gateway: unify plugin interactive callback state (#50722)
Merged via squash.

Prepared head SHA: 7a2740b18a
Co-authored-by: huntharo <5617868+huntharo@users.noreply.github.com>
Co-authored-by: huntharo <5617868+huntharo@users.noreply.github.com>
Reviewed-by: @huntharo
2026-03-19 22:09:38 -04:00
Shakker
61ae7e033b fix(ci): isolate remaining unit-fast OOM hotspots 2026-03-20 01:58:21 +00:00
Shakker
1fb30fbf78 fix(test): stub pnpm in pre-commit hook fixture 2026-03-20 01:58:21 +00:00
Vincent Koc
a2174f1ff1 fix(hooks): skip repo check outside workspace 2026-03-19 18:56:43 -07:00
Shakker
cf2a66b508 chore(docs): refresh generated config baseline 2026-03-20 01:52:27 +00:00
Vincent Koc
e009920256 fix(ci): isolate remaining stale OOM hotspots 2026-03-19 18:49:12 -07:00
Shakker
a19f058145 fix(test): mock zalouser runtime in outbound payload contract 2026-03-20 01:45:20 +00:00
Shakker
f91fad1710 fix(ci): isolate high-heap unit suites from unit-fast 2026-03-20 01:36:39 +00:00
Shakker
ac18a734ac fix(ci): cap top-level test lane concurrency 2026-03-20 01:36:12 +00:00
Shakker
55e12bd236 fix(plugins): stabilize bundle MCP path assertions 2026-03-20 01:11:58 +00:00
Shakker
c95d1c101b fix(cron): avoid async context token warmup in isolated runs 2026-03-20 01:11:58 +00:00
joshavant
6309b1da6c Gateway: preserve interactive pairing visibility on supersede 2026-03-19 19:57:45 -05:00
Gustavo Madeira Santana
a953cb5209 Matrix: fix runtime API duplicate exports 2026-03-19 20:53:35 -04:00
Vincent Koc
d518260bb8 fix(status): slim json startup path 2026-03-19 16:55:13 -07:00
Harold Hunt
41628770f5 Tests: trim command secret gateway imports (#50663)
Merged via squash.

Prepared head SHA: 7f64fd3ee1
Co-authored-by: huntharo <5617868+huntharo@users.noreply.github.com>
Co-authored-by: huntharo <5617868+huntharo@users.noreply.github.com>
Reviewed-by: @huntharo
2026-03-19 19:53:02 -04:00
Vincent Koc
aa172f2169 fix(matrix): keep runtime api import-safe 2026-03-19 16:39:27 -07:00
Vincent Koc
c38295c7a2 test(ci): tighten startup memory thresholds 2026-03-19 16:28:00 -07:00
Vincent Koc
0f69b5c11a fix(status): keep startup paths free of plugin warmup 2026-03-19 16:26:58 -07:00
Josh Avant
8e132aed6e Hardening: refresh stale device pairing requests and pending metadata (#50695)
* Docs: clarify device pairing supersede behavior

* Device pairing: supersede pending requests on auth changes
2026-03-19 18:26:06 -05:00
Vincent Koc
9486f6e379 fix(build): suppress singleton smoke deprecation noise 2026-03-19 16:07:53 -07:00
Vincent Koc
f3971571fe fix(plugins): fail strict bootstrap on plugin load errors 2026-03-19 16:07:53 -07:00
Vincent Koc
009f494cd9 fix(plugin-sdk): stop library import warmup side effects 2026-03-19 16:07:53 -07:00
Tak Hoffman
192151610f fix(status): skip plugin compatibility scan on empty json path 2026-03-19 18:06:03 -05:00
Vincent Koc
20001a50c5 fix(build): suppress known-safe bottleneck eval warnings 2026-03-19 15:45:56 -07:00
Tak Hoffman
801e4bede6 Git: run pnpm check in pre-commit hook 2026-03-19 17:41:33 -05:00
Vincent Koc
bbfeb0b6f9 fix(ci): cache node in install smoke image 2026-03-19 15:38:16 -07:00
Vincent Koc
c3b05fc4d9 docs: add missing title, remove stale description fields from frontmatter 2026-03-19 15:26:26 -07:00
Vincent Koc
14eb49c18a test(feishu): fix lifecycle mock typing 2026-03-19 15:26:14 -07:00
Vincent Koc
d80b83e8e3 fix(plugins): scope sdk aliases to loaded module paths 2026-03-19 15:25:54 -07:00
Vincent Koc
a245916dcb fix(ci): repair test-parallel heap snapshot parsing 2026-03-19 15:25:29 -07:00
Vincent Koc
ac850e815b fix(ci): replace tlon git api dependency 2026-03-19 15:25:29 -07:00
Tak Hoffman
2884ac13b2 test: add Zalo pairing lifecycle regression 2026-03-19 17:13:38 -05:00
Josh Lehman
35bc00c55b test: reduce low-memory Vitest pressure (#50652)
* test: reduce low-memory Vitest pressure

Reuse the bundled config baseline inside doc-baseline tests, keep that hotspot out of the shared unit-fast lane, and make OPENCLAW_TEST_PROFILE=low default to process forks instead of vmForks.

* test: keep low-profile vmForks in CI

Scope the low-profile forks fallback to local runs so the existing CI contracts lane keeps its current pool behavior.
2026-03-19 15:02:48 -07:00
Harold Hunt
bbd62469fa Tests: Add tooling / skill for detecting and fixing memory leaks in tests (#50654)
* Tests: add periodic heap snapshot tooling

* Skills: add test heap leak workflow

* Apply suggestion from @greptile-apps[bot]

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* Update scripts/test-parallel.mjs

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-03-19 14:59:13 -07:00
Tak Hoffman
da8fb70525 test: fix Feishu lifecycle type checks 2026-03-19 16:54:39 -05:00
Tak Hoffman
73e08775d7 test: add voice-call hangup-once lifecycle regression 2026-03-19 16:50:36 -05:00
Tak Hoffman
566e4cf77b test: add Zalo reply-once lifecycle regression 2026-03-19 16:50:36 -05:00
Vincent Koc
5841e3b493 fix(ci): split redact snapshot schema coverage 2026-03-19 14:49:01 -07:00
Vincent Koc
aeb2adf240 fix(ci): split redact snapshot restore coverage 2026-03-19 14:49:01 -07:00
Vincent Koc
38807fff20 fix(ci): split plugin sdk bundle coverage 2026-03-19 14:49:01 -07:00
Vincent Koc
ec2278192d fix(ci): reduce test runtime retention hotspots 2026-03-19 14:49:01 -07:00
Vincent Koc
d03c110a0a fix(ci): split secrets runtime integration coverage 2026-03-19 14:49:01 -07:00
Vincent Koc
a54d3dc679 test(feishu): fix bot-menu binding mock typing 2026-03-19 14:49:01 -07:00
Tak Hoffman
628b55a825 test: add Feishu ACP failure lifecycle regression 2026-03-19 16:33:04 -05:00
Tak Hoffman
c7cebd608b test: add Feishu broadcast lifecycle regression 2026-03-19 16:33:03 -05:00
Tak Hoffman
7d50e7fa85 test: add Feishu card-action lifecycle regression 2026-03-19 16:33:03 -05:00
Vincent Koc
3c806a9692 fix(ci): stabilize bundle hooks and mcp path seams 2026-03-19 14:26:52 -07:00
Vincent Koc
247a19a694 fix(hooks): bypass stale plugin bundle caches 2026-03-19 14:26:52 -07:00
Vincent Koc
83a267e2f3 fix(ci): reset deep test runtime state 2026-03-19 14:23:32 -07:00
Josh Lehman
ae02f40144 fix: load matrix legacy helper through native ESM when possible (#50623)
* fix(matrix): load legacy helper natively when possible

* fix(matrix): narrow jiti fallback to source helpers

* fix(matrix): fall back to jiti for source-style helper wrappers
2026-03-19 14:21:42 -07:00
Vincent Koc
8412498c2c docs: convert FAQ to Mintlify accordion format, fix TOC link, enrich help index 2026-03-19 14:18:39 -07:00
Tak Hoffman
0e825ece05 test: add Feishu bot-menu lifecycle regression 2026-03-19 16:16:46 -05:00
Vincent Koc
7f52a8a3a5 fix(ci): isolate top unit-fast OOM offenders 2026-03-19 14:15:52 -07:00
Josh Avant
1878272f67 CLI: prune inactive gateway auth credentials on mode set (#50639) 2026-03-19 16:05:43 -05:00
Tak Hoffman
ca757b6b77 test: add Feishu reply-once lifecycle regression 2026-03-19 16:04:53 -05:00
Vincent Koc
98298f7931 fix(ci): trace test runner memory retention 2026-03-19 14:02:19 -07:00
Vincent Koc
b7c39aa4d4 fix(ci): isolate config doc baseline heap pressure 2026-03-19 13:56:40 -07:00
Vincent Koc
f1be7d4cb3 fix(ci): isolate memory OOM hotspots from unit-fast 2026-03-19 13:44:35 -07:00
Vincent Koc
a94e21e0a7 docs(install): update container setup paths 2026-03-19 13:40:26 -07:00
Vincent Koc
46ccbacbd9 refactor(scripts): move container setup entrypoints 2026-03-19 13:40:26 -07:00
Vincent Koc
3b79494cbf fix(runtime): lazy-load setup shims and align contracts 2026-03-19 13:33:32 -07:00
Vincent Koc
7bbd01379e fix(deps): use https git sources for extension installs 2026-03-19 13:33:32 -07:00
Vincent Koc
ca74eb37da fix(extensions): repair matrix contracts and test boundaries 2026-03-19 13:33:32 -07:00
Vincent Koc
0aa4950d21 fix(core): restore session reset defaults and type seams 2026-03-19 13:33:32 -07:00
Vincent Koc
7bc7dd055a docs: sort Linux Server (vps) alphabetically in Hosting nav 2026-03-19 13:31:55 -07:00
Vincent Koc
3de8c3d053 docs: move Oracle, DigitalOcean, Raspberry Pi to Install > Hosting, rewrite with Steps 2026-03-19 13:29:39 -07:00
Vincent Koc
8dea2b124b docs: rename VPS to Linux Server, update provider links for moved pages 2026-03-19 13:29:39 -07:00
Vincent Koc
003ca0123d test(ci): trim embedding harness churn 2026-03-19 12:22:41 -07:00
Vincent Koc
36df0095c4 test(ci): trim memory dedupe harness churn 2026-03-19 12:22:41 -07:00
Vincent Koc
0fd3632d68 test(ci): trim memory atomic harness churn 2026-03-19 12:22:41 -07:00
Vincent Koc
22528af34d test(ci): trim gateway plugin harness churn 2026-03-19 12:22:41 -07:00
Vincent Koc
f60017d725 test(ci): trim memory cli harness churn 2026-03-19 12:22:41 -07:00
Vincent Koc
7a596b2305 test(ci): trim threading harness churn 2026-03-19 12:22:41 -07:00
Vincent Koc
60253111a3 test(ci): trim context isolation harness churn 2026-03-19 12:22:41 -07:00
Vincent Koc
962a8fea90 test(ci): trim thread lane harness churn 2026-03-19 12:22:41 -07:00
Vincent Koc
14e84cf0b3 test(ci): trim runtime test harness churn 2026-03-19 12:22:41 -07:00
Vincent Koc
9117836981 docs: deep rewrite Docker page (851→375 lines), trim sandbox duplication, add Steps 2026-03-19 12:07:42 -07:00
Vincent Koc
ebb6738e9d docs: improve VPS hub page and convert Podman to Mintlify Steps 2026-03-19 12:07:42 -07:00
Vincent Koc
34adde2e41 docs: rewrite ansible, bun, nix install pages with Mintlify Steps and improved readability 2026-03-19 12:07:42 -07:00
Vincent Koc
815d603ce2 chore: Delete infra directory 2026-03-19 12:05:32 -07:00
Vincent Koc
a6021cf78f docs: add Discord link to navbar 2026-03-19 11:58:25 -07:00
Vincent Koc
e466b55661 docs: convert Fly, Hetzner, GCP, Azure hosting pages to Mintlify Steps 2026-03-19 11:56:56 -07:00
Vincent Koc
7187d1da06 docs: rewrite updating.md (276→128 lines) and migrating.md (193→107 lines) for readability 2026-03-19 11:56:56 -07:00
Vincent Koc
517570d0fb docs: restructure Install nav — shorter group names, A-Z order, fix hosting titles, move dev channels to Maintenance 2026-03-19 11:56:56 -07:00
Tak Hoffman
66894db1b6 test: guard pi package graph alignment 2026-03-19 13:50:26 -05:00
Vincent Koc
3496ecc2ec chore: Delete changelog/fragments directory 2026-03-19 11:44:33 -07:00
Vincent Koc
e5b50ba0d5 docs: fix remaining install issues — stale versions, Docker TOC, ARM note, frontmatter 2026-03-19 11:42:57 -07:00
Vincent Koc
30ddeabfdc docs: fix install section — broken anchors, wrong commands, json5 fences, add next-steps sections 2026-03-19 11:38:51 -07:00
Vincent Koc
071319545f docs: deduplicate chat tokens across hosting pages, remove Nix packaging note 2026-03-19 11:37:47 -07:00
Vincent Koc
e1a39c6ba5 docs: rewrite install index for readability — flat structure, clearer hierarchy, better hosting cards 2026-03-19 11:30:48 -07:00
Vincent Koc
22c1bda2a0 docs: clarify native Windows support alongside WSL2 across getting-started, windows, and onboarding-overview 2026-03-19 11:28:53 -07:00
Vincent Koc
cb78f38da9 docs: clarify subscription auth and custom provider examples in features 2026-03-19 11:26:07 -07:00
Vincent Koc
e121aad2c1 docs: improve Get Started readability — rewrite getting-started, onboarding-overview, features, and openclaw pages 2026-03-19 11:24:30 -07:00
Vincent Koc
392047b49f docs: collapse Get Started tab into 3 groups (Option C) 2026-03-19 11:10:56 -07:00
Vincent Koc
6b9ebffebb test(ci): trim command secret gateway harness churn 2026-03-19 11:08:33 -07:00
Vincent Koc
feb9a3b5b2 fix(ci): harden test gating under load 2026-03-19 11:08:33 -07:00
Vincent Koc
51519b4086 fix(ci): fail on fatal test runner output 2026-03-19 11:08:33 -07:00
Vincent Koc
0a8885d6c1 fix(ci): restore hook and guardrail tests 2026-03-19 11:08:32 -07:00
Vincent Koc
cb552bcc42 docs: fix duplicate redirect source, fix faq heading dash-vs-comma for valid anchor 2026-03-19 11:05:10 -07:00
Vincent Koc
d9e9a9e819 fix(pi): align package graph and declare compaction summaries 2026-03-19 11:02:18 -07:00
Vincent Koc
13be4b4cc2 docs: add Groq provider page 2026-03-19 10:57:59 -07:00
Vincent Koc
b28cf6a8a4 docs: split memory.md into concept intro + reference page 2026-03-19 10:57:47 -07:00
Vincent Koc
d57c327d45 docs: sub-group CLI reference into 8 clusters 2026-03-19 10:57:34 -07:00
Vincent Koc
089c8bc65e docs: Phase 3 IA restructure — move pi to Reference, merge Models groups, move install/node to Install, move prose to Skills, migrate brave-search/perplexity/tts into tools/ 2026-03-19 10:42:46 -07:00
Vincent Koc
faf81c5574 docs: clarify Pi agent core relationship in runtime boundaries 2026-03-19 10:35:09 -07:00
Vincent Koc
a18f7d7d35 docs: add orphan pages to nav, fix Twitch URL, normalize json5 fences, fix msteams config 2026-03-19 10:33:03 -07:00
Vincent Koc
9f2a01d972 docs: replace stale claude-sonnet-4-5 with 4-6, normalize Node version, remove stale dates 2026-03-19 10:33:03 -07:00
Vincent Koc
0b11ee48f8 docs: fix 26 broken anchor links across 18 files 2026-03-19 10:33:02 -07:00
Vincent Koc
624d536551 docs: remove quickstart stub from hubs, add redirect to getting-started 2026-03-19 10:32:30 -07:00
Vincent Koc
1dd857f6a6 docs: add API key prereq, first-message step, fix landing page quick start 2026-03-19 10:32:30 -07:00
Vincent Koc
65a2917c8f docs: remove pi-mono jargon, fix features list, update Perplexity config path 2026-03-19 10:32:30 -07:00
fuller-stack-dev
36f394c299 fix(gateway): increase WS handshake timeout from 3s to 10s (#49262)
* fix(gateway): increase WS handshake timeout from 3s to 10s

The 3-second default is too aggressive when the event loop is under load
(concurrent sessions, compaction, agent turns), causing spurious
'gateway closed (1000)' errors on CLI commands like `openclaw cron list`.

Changes:
- Increase DEFAULT_HANDSHAKE_TIMEOUT_MS from 3_000 to 10_000
- Add OPENCLAW_HANDSHAKE_TIMEOUT_MS env var for user override (no VITEST gate)
- Keep OPENCLAW_TEST_HANDSHAKE_TIMEOUT_MS as fallback for existing tests

Fixes #46892

* fix: restore VITEST guard on test env var, use || for empty-string fallback, fix formatting

* fix: cover gateway handshake timeout env override (#49262) (thanks @fuller-stack-dev)

---------

Co-authored-by: Wilfred <wilfred@Wilfreds-Mac-mini.local>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-19 22:46:40 +05:30
Vincent Koc
3dfd8eef7f ci(node22): drop duplicate config docs check from compat lane 2026-03-19 09:56:42 -07:00
Harold Hunt
401ffb59f5 CLI: support versioned plugin updates (#49998)
Merged via squash.

Prepared head SHA: 545ea60fa2
Co-authored-by: huntharo <5617868+huntharo@users.noreply.github.com>
Reviewed-by: @huntharo
2026-03-19 12:51:10 -04:00
Vincent Koc
7fb142d115 test(whatsapp): override config-runtime mock exports safely 2026-03-19 09:42:13 -07:00
Vincent Koc
639f78d257 style(format): restore import order drift 2026-03-19 09:38:42 -07:00
Vincent Koc
dcbcecfb85 fix(ci): resolve Claude marketplace shortcuts from OS home 2026-03-19 09:38:42 -07:00
Ayaan Zaidi
f1e4f8e8d2 fix: add changelog attribution for Azure Foundry custom providers (#50535) 2026-03-19 22:07:19 +05:30
Ayaan Zaidi
91104ac740 fix(onboard): respect services.ai custom provider compatibility 2026-03-19 22:07:19 +05:30
Ayaan Zaidi
5b1836d700 fix(onboard): raise azure probe output floor 2026-03-19 21:53:27 +05:30
Ayaan Zaidi
7a57082466 fix(provider): onboard azure custom endpoints via responses 2026-03-19 21:53:27 +05:30
Vincent Koc
9d772d6eab fix(ci): normalize bundle mcp paths and skip explicit channel scans 2026-03-19 09:16:45 -07:00
Gustavo Madeira Santana
ff6541f69d Matrix: fix Jiti runtime API boundary 2026-03-19 11:40:44 -04:00
Tak Hoffman
5a41229a6d docs: simplify AGENTS validation policy 2026-03-19 10:34:04 -05:00
Tak Hoffman
e1b5ffadca docs: clarify scoped-test validation policy 2026-03-19 10:29:39 -05:00
Tak Hoffman
fb18034011 test: add macmini test profile 2026-03-19 10:29:39 -05:00
xubaolin
bfe979dd5b refactor: add Android LocationHandler test seam (#50027) (thanks @xu-baolin) 2026-03-19 20:57:43 +05:30
Gustavo Madeira Santana
12ad809e79 Matrix: fix runtime encryption loading 2026-03-19 11:08:17 -04:00
Gustavo Madeira Santana
8268c28053 Matrix: isolate thread binding manager stateDir reuse 2026-03-19 11:08:16 -04:00
Vincent Koc
44cd4fb55f fix(ci): repair main type and boundary regressions 2026-03-19 08:00:33 -07:00
Gustavo Madeira Santana
0c4fdf1284 Format: apply import ordering cleanup 2026-03-19 10:33:16 -04:00
Gustavo Madeira Santana
f4f0b171d3 Matrix: isolate credential write runtime 2026-03-19 10:33:16 -04:00
Liu Ricardo
8c01347989 test(contracts): cover matrix session binding adapters (#50369)
Merged via squash.

Prepared head SHA: 25412dbc2c
Co-authored-by: ChroniCat <220139611+ChroniCat@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-03-19 10:26:37 -04:00
Gustavo Madeira Santana
c7cbc8cc0b CI: validate plugin runtime deps in install smoke 2026-03-19 09:44:27 -04:00
Vincent Koc
79d7fdce93 test(telegram): inject media loader in delivery replies 2026-03-19 06:30:59 -07:00
Vincent Koc
a0445b192e test(signal): mock daemon readiness in monitor suite 2026-03-19 06:30:59 -07:00
Vincent Koc
1c1a3b6a75 fix(discord): break plugin-sdk account helper cycle 2026-03-19 06:30:59 -07:00
Gustavo Madeira Santana
dd10f290e8 Matrix: wire thread binding command support 2026-03-19 09:24:31 -04:00
Johnson Shi
191e1947c1 docs: add Azure VM deployment guide with in-repo ARM templates and bootstrap script (#47898)
* docs: add Azure Linux VM install guide

* docs: move Azure guide into dedicated docs/install/azure layout

* docs: polish Azure guide onboarding and reference links

* docs: address Azure review feedback on bootstrap safety

* docs: format azure ARM template

* docs: flatten Azure install docs and move ARM assets
2026-03-19 08:15:06 -05:00
Harold Hunt
5508374669 fix(plugins): share split-load singleton state (openclaw#50418) thanks @huntharo
Co-authored-by: huntharo <5617868+huntharo@users.noreply.github.com>
2026-03-19 09:10:24 -04:00
Gustavo Madeira Santana
7f86be1037 Matrix: accept messageId alias for poll votes 2026-03-19 08:50:49 -04:00
Tyler Yust
20728e1035 fix: stop newline block streaming from sending per paragraph 2026-03-19 05:40:12 -07:00
Tyler Yust
47b02435c1 fix: honor BlueBubbles chunk mode and envelope timezone 2026-03-19 05:40:12 -07:00
Gustavo Madeira Santana
75e6c8fe9c Matrix: persist clean shutdown sync state 2026-03-19 08:31:44 -04:00
Gustavo Madeira Santana
16129272dc Tests: update Matrix agent bind fixtures 2026-03-19 08:31:38 -04:00
Gustavo Madeira Santana
f8eb23de1c CLI: fix check failures 2026-03-19 08:29:57 -04:00
Gustavo Madeira Santana
34ee75b174 Matrix: restore doctor migration previews 2026-03-19 08:09:52 -04:00
Gustavo Madeira Santana
4443cc771a Matrix: wire startup migration into doctor and gateway 2026-03-19 08:03:57 -04:00
Gustavo Madeira Santana
f69450b170 Matrix: fix typecheck and boundary drift 2026-03-19 08:03:56 -04:00
Nimrod Gutman
c4a4050ce4 fix(macos): align exec command parity (#50386)
* fix(macos): align exec command parity

* fix(macos): address exec review follow-ups
2026-03-19 13:51:17 +02:00
Vincent Koc
009a10bce2 fix(ci): avoid ssh-only git dependency fetches 2026-03-19 01:57:34 -07:00
Vincent Koc
c37a92ca6e fix(cli): clarify source archive install failures 2026-03-19 01:49:28 -07:00
Ayaan Zaidi
040c43ae21 feat(android): benchmark script 2026-03-19 13:13:14 +05:30
Peter Steinberger
f3097b4c09 refactor: install optional channels for remove 2026-03-19 07:20:55 +00:00
Ayaan Zaidi
0443ee82be fix(android): auto-connect gateway on app open 2026-03-19 12:49:18 +05:30
Peter Steinberger
22943f24a9 refactor: prune bundled sdk facades 2026-03-19 07:17:04 +00:00
Shaun Tsai
bcc725ffe2 fix(agents): strip prompt cache for non-OpenAI responses endpoints (#49877) thanks @ShaunTsai
Fixes #48155

Co-authored-by: Shaun Tsai <13811075+ShaunTsai@users.noreply.github.com>
Co-authored-by: frankekn <4488090+frankekn@users.noreply.github.com>
2026-03-19 15:12:29 +08:00
Josh Avant
b965ef3802 Channels: stabilize lane harness and monitor tests (#50167)
* Channels: stabilize lane harness regressions

* Signal tests: stabilize tool-result harness dispatch

* Telegram tests: harden polling restart assertions

* Discord tests: stabilize channel lane harness coverage

* Slack tests: align slash harness runtime mocks

* Telegram tests: harden dispatch and pairing scenarios

* Telegram tests: fix SessionEntry typing in bot callback override case

* Slack tests: avoid slash runtime mock deadlock

* Tests: address bot review follow-ups

* Discord: restore accounts runtime-api seam

* Tests: stabilize Discord and Telegram channel harness assertions

* Tests: clarify Discord mock seam and remove unused Telegram import

* changelog

Signed-off-by: joshavant <830519+joshavant@users.noreply.github.com>

---------

Signed-off-by: joshavant <830519+joshavant@users.noreply.github.com>
2026-03-19 01:47:48 -05:00
Gustavo Madeira Santana
ddd921ff0b Docs: add new Matrix plugin changelog entry 2026-03-19 02:21:34 -04:00
Gustavo Madeira Santana
c5c2416ec2 Matrix: restore local sdk barrel imports 2026-03-19 02:03:17 -04:00
Gustavo Madeira Santana
94693f7ff0 Matrix: rebuild plugin migration branch 2026-03-19 01:58:29 -04:00
Gustavo Madeira Santana
513b4869d8 Discord: stabilize provider registry coverage 2026-03-19 01:53:55 -04:00
Ayaan Zaidi
1d3e596021 fix(pairing): include shared auth in setup codes 2026-03-19 11:20:31 +05:30
Ayaan Zaidi
608b9a9af2 fix(android): show copyable gateway diagnostics 2026-03-19 10:47:12 +05:30
Gustavo Madeira Santana
a2fa799a5c Tests: stabilize poll fallback coverage 2026-03-19 01:15:03 -04:00
Gustavo Madeira Santana
03f18ec043 Outbound: remove channel-specific message action fallbacks 2026-03-19 01:08:23 -04:00
Gustavo Madeira Santana
eaee01042b Plugin SDK: move generic message tool schemas out of core 2026-03-19 01:08:23 -04:00
Gustavo Madeira Santana
b48194a07e Plugins: move message tool schemas into channel plugins 2026-03-19 01:08:23 -04:00
Gustavo Madeira Santana
8467fb6601 Outbound: move target display fallbacks behind plugins 2026-03-19 01:08:22 -04:00
Ayaan Zaidi
d978ace90b fix: isolate CLI startup imports (#50212)
* fix: isolate CLI startup imports

* fix: clarify CLI preflight behavior

* fix: tighten main-module detection

* fix: isolate CLI startup imports (#50212)
2026-03-19 10:34:29 +05:30
Josh Avant
68bc6effc0 Telegram: stabilize pairing/session/forum routing and reply formatting tests (#50155)
* Telegram: stabilize Area 2 DM and model callbacks

* Telegram: fix dispatch test deps wiring

* Telegram: stabilize area2 test harness and gate flaky sticker e2e

* Telegram: address review feedback on config reload and tests

* Telegram tests: use plugin-sdk reply dispatcher import

* Telegram tests: add routing reload regression and track sticker skips

* Telegram: add polling-session backoff regression test

* Telegram tests: mock loadWebMedia through plugin-sdk path

* Telegram: refresh native and callback routing config

* Telegram tests: fix compact callback config typing
2026-03-19 00:01:14 -05:00
Tak Hoffman
53a34c39f6 Fix windows ACL os mock typing 2026-03-18 23:49:53 -05:00
Tak Hoffman
3261a2a0b1 Tighten bug report grounding guidance 2026-03-18 23:46:45 -05:00
Tak Hoffman
74b9ad010a test: preserve node os exports in windows acl mock 2026-03-18 23:38:25 -05:00
Josh Avant
a2a9a553e1 Stabilize plugin loader and Docker extension smoke (#50058)
* Plugins: stabilize Area 6 loader and Docker smoke

* Docker: fail fast on extension npm install errors

* Tests: stabilize loader non-native Jiti boundary CI timeout

* Tests: stabilize plugin loader Jiti source-runtime coverage

* Docker: keep extension deps on lockfile graph

* Tests: cover tsx-cache renamed package cwd fallback

* Tests: stabilize plugin-sdk export subpath assertions

* Plugins: align tsx-cache alias fallback with subpath fallback

* Tests: normalize guardrail path checks for Windows

* Plugins: restrict plugin-sdk cwd fallback to trusted roots

* Tests: exempt outbound-session from extension import guard

* Tests: tighten guardrails and cli-entry trust coverage

* Tests: guard optional loader fixture exports

* Tests: make loader fixture package exports null-safe

* Tests: make loader fixture package exports null-safe

* Tests: make loader fixture package exports null-safe

* changelog

Signed-off-by: joshavant <830519+joshavant@users.noreply.github.com>

---------

Signed-off-by: joshavant <830519+joshavant@users.noreply.github.com>
2026-03-18 23:35:32 -05:00
Tak Hoffman
3abffe0967 fix: stabilize windows temp and path handling 2026-03-18 23:29:14 -05:00
Gustavo Madeira Santana
afa95fade0 Tests: align fixtures with current gateway and model types 2026-03-19 00:25:24 -04:00
Gustavo Madeira Santana
83d284610c Diffs: route plugin context through artifacts 2026-03-19 00:24:00 -04:00
Tak Hoffman
a98ffa41d0 build: make whatsapp plugin publishable 2026-03-18 23:22:44 -05:00
Tak Hoffman
16567ba4e7 test: align whatsapp expectations with current contracts 2026-03-18 23:17:48 -05:00
Tak Hoffman
b8b1e2cf50 AGENTS.md: split GHSA advisory workflow into its own skill 2026-03-18 23:11:18 -05:00
Tak Hoffman
f6c57edd5c Tests: tighten channel import guardrails 2026-03-18 23:08:02 -05:00
Tak Hoffman
79e13e0a5e AGENTS.md: forbid merge commits on main 2026-03-18 23:01:22 -05:00
Tak Hoffman
5b7b5529f1 Plugins: remove shared extension boundary debt 2026-03-18 22:58:40 -05:00
Tak Hoffman
126839380c Tests: fix current check failures 2026-03-18 22:58:40 -05:00
Tak Hoffman
74756b91b7 AGENTS.md: block test-baseline silencing edits 2026-03-18 22:55:27 -05:00
Tak Hoffman
f7675eca6b AGENTS.md: split local and safety notes 2026-03-18 22:55:27 -05:00
Tak Hoffman
59269f3534 AGENTS.md: extract repo workflows into skills 2026-03-18 22:55:27 -05:00
Peter Steinberger
25015161fe refactor: install optional channel capabilities on demand 2026-03-19 03:39:15 +00:00
Peter Steinberger
19126033dd build: regenerate protocol swift models 2026-03-19 03:38:35 +00:00
Peter Steinberger
b7ca56f662 refactor: install heavy plugins on demand 2026-03-19 03:37:30 +00:00
Peter Steinberger
83c5bc946d fix: restore full gate stability 2026-03-19 03:36:03 +00:00
lixuankai
c86de678f3 feat(android): support android node sms.search (#48299)
* feat(android): support android node sms.search

* feat(android): support android node sms.search

* fix(android): split sms search permissions

* fix: document android sms.search landing (#48299) (thanks @lixuankai)

---------

Co-authored-by: lixuankai <lixuankai@oppo.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-19 08:52:15 +05:30
Peter Steinberger
58cf9b865f refactor: route extension seams through public apis 2026-03-19 03:20:10 +00:00
Tak Hoffman
8404f56841 Docs: trialing stronger AGENTS.md rules 2026-03-18 22:18:52 -05:00
Peter Steinberger
30a94dfd3b refactor: untangle whatsapp runtime boundary 2026-03-19 03:13:48 +00:00
Peter Steinberger
510f4276b5 refactor: tighten sdk reply pipeline contract 2026-03-19 03:13:15 +00:00
clay-datacurve
7b61ca1b06 Session management improvements and dashboard API (#50101)
* fix: make cleanup "keep" persist subagent sessions indefinitely

* feat: expose subagent session metadata in sessions list

* fix: include status and timing in sessions_list tool

* fix: hide injected timestamp prefixes in chat ui

* feat: push session list updates over websocket

* feat: expose child subagent sessions in subagents list

* feat: add admin http endpoint to kill sessions

* Emit session.message websocket events for transcript updates

* Estimate session costs in sessions list

* Add direct session history HTTP and SSE endpoints

* Harden dashboard session events and history APIs

* Add session lifecycle gateway methods

* Add dashboard session API improvements

* Add dashboard session model and parent linkage support

* fix: tighten dashboard session API metadata

* Fix dashboard session cost metadata

* Persist accumulated session cost

* fix: stop followup queue drain cfg crash

* Fix dashboard session create and model metadata

* fix: stop guessing session model costs

* Gateway: cache OpenRouter pricing for configured models

* Gateway: add timeout session status

* Fix subagent spawn test config loading

* Gateway: preserve operator scopes without device identity

* Emit user message transcript events and deduplicate plugin warnings

* feat: emit sessions.changed lifecycle event on subagent spawn

Adds a session-lifecycle-events module (similar to transcript-events)
that emits create events when subagents are spawned. The gateway
server.impl.ts listens for these events and broadcasts sessions.changed
with reason=create to SSE subscribers, so dashboards can pick up new
subagent sessions without polling.

* Gateway: allow persistent dashboard orchestrator sessions

* fix: preserve operator scopes for token-authenticated backend clients

Backend clients (like agent-dashboard) that authenticate with a valid gateway
token but don't present a device identity were getting their scopes stripped.
The scope-clearing logic ran before checking the device identity decision,
so even when evaluateMissingDeviceIdentity returned 'allow' (because
roleCanSkipDeviceIdentity passed for token-authed operators), scopes were
already cleared.

Fix: also check decision.kind before clearing scopes, so token-authenticated
operators keep their requested scopes.

* Gateway: allow operator-token session kills

* Fix stale active subagent status after follow-up runs

* Fix dashboard image attachments in sessions send

* Fix completed session follow-up status updates

* feat: stream session tool events to operator UIs

* Add sessions.steer gateway coverage

* Persist subagent timing in session store

* Fix subagent session transcript event keys

* Fix active subagent session status in gateway

* bump session label max to 512

* Fix gateway send session reactivation

* fix: publish terminal session lifecycle state

* feat: change default session reset to effectively never

- Change DEFAULT_RESET_MODE from "daily" to "idle"
- Change DEFAULT_IDLE_MINUTES from 60 to 0 (0 = disabled/never)
- Allow idleMinutes=0 through normalization (don't clamp to 1)
- Treat idleMinutes=0 as "no idle expiry" in evaluateSessionFreshness
- Default behavior: mode "idle" + idleMinutes 0 = sessions never auto-reset
- Update test assertion for new default mode

* fix: prep session management followups (#50101) (thanks @clay-datacurve)

---------

Co-authored-by: Tyler Yust <TYTYYUST@YAHOO.COM>
2026-03-19 12:12:30 +09:00
Tak Hoffman
a837ebdd67 Docs: update AGENTS.md import boundaries 2026-03-18 22:06:44 -05:00
Tyler Yust
a290f5e50f fix: persist outbound sends and skip stale cron deliveries (#50092)
* fix(bluebubbles): auto-create chats for new numbers, persist outbound messages to session transcripts

Two fixes for BlueBubbles message tool behavior:

1. **Attachment sends to new phone numbers**: sendBlueBubblesAttachment now
   auto-creates a new DM chat (via /api/v1/chat/new) when no existing chat
   is found for a handle target, matching the behavior already present in
   sendMessageBlueBubbles for text sends. The existing createNewChatWithMessage
   is refactored into a reusable createChatForHandle that returns the chatGuid.

2. **Outbound message session persistence**: Ensures outbound messages sent
   via the message tool are reliably tracked in session transcripts:
   - ensureOutboundSessionEntry now falls back to directly creating a session
     store entry when recordSessionMetaFromInbound returns null, guaranteeing
     a sessionId exists for the subsequent mirror append.
   - appendAssistantMessageToSessionTranscript now normalizes the session key
     (lowercased) when looking up the store, preventing case mismatches
     between the store keys and the mirror sessionKey.

Tests added for all changes.

* test(slack): verify outbound session tracking and new target sends for Slack

The shared infrastructure changes from the BlueBubbles fix (session key
normalization in transcript.ts and fallback session entry creation in
outbound-session.ts) already cover Slack. Slack's sendMessageSlack uses
conversations.open to auto-create DM channels for new user targets.

Add tests confirming:
- Slack user DM and channel session route resolution (outbound.test.ts)
- Slack session key normalization for transcript append (sessions.test.ts)
- Slack outbound sendText/sendMedia to new user and channel targets (channel.test.ts)

* fix(cron): skip stale delayed deliveries

* fix: prep PR #50092
2026-03-19 11:40:34 +09:00
Tyler Yust
ffc1d5459c fix: resolve failing tests on main (warning filter + slack mocks) 2026-03-18 19:31:12 -07:00
clawdia
6ae68faf5f fix(whatsapp): use globalThis singleton for active-listener Map (#47433)
Merged via squash.

Prepared head SHA: 1c43dbff39
Co-authored-by: clawdia67 <261743618+clawdia67@users.noreply.github.com>
Co-authored-by: mcaxtr <7562095+mcaxtr@users.noreply.github.com>
Reviewed-by: @mcaxtr
2026-03-18 22:16:31 -03:00
Josh Avant
0f0cecd2e8 Discord: enforce strict DM component allowlist auth (#49997)
* Discord: enforce strict DM component allowlist auth

* Discord: align model picker fallback routing

* changelog

Signed-off-by: joshavant <830519+joshavant@users.noreply.github.com>

---------

Signed-off-by: joshavant <830519+joshavant@users.noreply.github.com>
2026-03-18 20:11:47 -05:00
Peter Steinberger
7b151afeeb test: align plugin-sdk subpath guardrail with current exports (#49249) 2026-03-18 18:02:44 -07:00
Peter Steinberger
371b3d22f5 fix: export imessage-core plugin-sdk subpath (#49249) 2026-03-18 18:02:44 -07:00
Peter Steinberger
42b9212eb2 fix: preserve interactive Ollama model selection (#49249) (thanks @BruceMacD) 2026-03-18 18:02:44 -07:00
Bruce MacDonald
f8c70bf1f1 fix(ollama): don't auto-pull glm-4.7-flash during Local mode onboarding 2026-03-18 18:02:44 -07:00
Vincent Koc
de86e25fd4 fix(ci): skip extension lanes with no tests 2026-03-18 17:52:28 -07:00
Vincent Koc
8884643f40 fix(plugin-sdk): restore imessage-core export 2026-03-18 17:49:51 -07:00
Peter Steinberger
002cc07322 refactor: tighten plugin sdk channel surfaces 2026-03-19 00:46:36 +00:00
Vincent Koc
f19cb738af fix(plugin-sdk): restore public runtime subpaths 2026-03-18 17:38:49 -07:00
Peter Steinberger
4cc0bb07c1 refactor: unify plugin sdk pairing flows 2026-03-19 00:31:03 +00:00
Vincent Koc
b736a92e19 fix(ci): gate extension relative package escapes 2026-03-18 17:27:57 -07:00
Peter Steinberger
c70837f07d refactor: converge plugin sdk channel helpers 2026-03-19 00:25:19 +00:00
Peter Steinberger
62b7b350c9 refactor: move bundled channel deps to plugin packages 2026-03-19 00:24:44 +00:00
Vincent Koc
9a9db87952 fix(release): isolate config doc surfaces and sdk exports 2026-03-18 17:14:15 -07:00
Peter Steinberger
60a55c9cbe fix(committer): accept argv and shell path blobs 2026-03-19 00:10:25 +00:00
Peter Steinberger
d7018aaf19 refactor: move bundled extension deps to plugin packages 2026-03-19 00:04:50 +00:00
Peter Steinberger
07d9f725b6 refactor: unify plugin sdk primitives 2026-03-18 23:58:56 +00:00
Vincent Koc
bea90b72e6 docs: update development-channels with --tag, --dry-run, status, and main warning 2026-03-18 16:41:43 -07:00
Vincent Koc
5f97645382 docs: update development-channels with --tag, --dry-run, and status sections 2026-03-18 16:41:43 -07:00
Peter Steinberger
46f49eb6eb refactor: shrink plugin sdk public surface 2026-03-18 23:31:08 +00:00
Peter Steinberger
6e044ace28 fix: keep bundled runtime deps out of release pack 2026-03-18 23:18:36 +00:00
Peter Steinberger
b9c4db1a77 test: fix stale boundary guardrails 2026-03-18 23:09:59 +00:00
Vincent Koc
a996f60f11 fix(release): isolate config docs child env 2026-03-18 16:05:40 -07:00
Vincent Koc
757c2cc2de fix(release): isolate bundled config docs loading 2026-03-18 16:01:43 -07:00
Vincent Koc
7d8d3d9d77 docs: merge duplicate OpenRouter entry, fix broken plugin anchor links 2026-03-18 16:00:46 -07:00
Vincent Koc
67da67b61a docs: fix tools nav A-Z, split plugin page, consolidate sandbox docs, add OpenShell page (#50055)
* docs: fix A-Z built-in tools nav, split plugin page, consolidate sandbox docs

* docs: add dedicated OpenShell sandbox backend page

* style: format markdown tables

* docs: trim plugin page, restructure available plugins into table + categories
2026-03-18 15:44:08 -07:00
Josh Avant
2661de384f Matrix: make onboarding status runtime-safe (#49995)
* Matrix: make onboarding status runtime-safe

* Matrix tests: mock reply dispatch in BodyForAgent coverage

* changelog

Signed-off-by: joshavant <830519+joshavant@users.noreply.github.com>

---------

Signed-off-by: joshavant <830519+joshavant@users.noreply.github.com>
2026-03-18 17:33:42 -05:00
Josh Avant
859889aae9 WhatsApp: stabilize inbound monitor and setup tests (#50007) 2026-03-18 17:08:57 -05:00
Vincent Koc
91d37ccfc3 fix(auth): lazy-load provider oauth helpers 2026-03-18 13:40:28 -07:00
Vincent Koc
6ebcd853be fix(plugin-sdk): isolate provider entry surfaces 2026-03-18 13:20:46 -07:00
Vincent Koc
b526098eb2 docs: restore original Credits heading, disambiguate H1 2026-03-18 12:38:46 -07:00
Vincent Koc
c749957c93 docs: fix duplicate Credits heading in credits.md 2026-03-18 12:34:37 -07:00
Vincent Koc
e5a1185796 docs: add extensions section to docs hubs 2026-03-18 12:29:02 -07:00
Vincent Koc
be3f4a7966 docs: add Building Extensions guide and nav entry 2026-03-18 12:28:19 -07:00
Vincent Koc
198de10523 docs: add missing H1 headings and fix HEARTBEAT template 2026-03-18 12:27:07 -07:00
Vincent Koc
63e09f8267 chore(changelog): remove fragment workflow drift 2026-03-18 12:26:56 -07:00
Vincent Koc
2797ae1583 docs: add missing voice-call CLI commands and contract test section to testing 2026-03-18 12:26:18 -07:00
Vincent Koc
cc5bd57bd7 docs: add missing provider pages (google, modelstudio, perplexity, volcengine) and nav entries 2026-03-18 12:26:01 -07:00
Vincent Koc
e9903c9133 Tests: align unit sharding with unit config 2026-03-18 12:16:07 -07:00
Josh Avant
e6911f0448 Tests: restore deterministic plugins CLI coverage (#49955)
* Tests: restore deterministic plugins CLI coverage

* CLI: preserve plugins exit control-flow narrowing

* Tests: fix plugins CLI mock typing for tsgo

* Tests: fix provider usage mock typing in key normalization
2026-03-18 14:05:04 -05:00
Vincent Koc
ef1346e503 Plugin SDK: route reply payload through public subpath 2026-03-18 12:01:15 -07:00
Vincent Koc
ecfa79ee4c Tests: fix provider auth plugin mock spread 2026-03-18 12:01:05 -07:00
Tak Hoffman
600f57c979 test: add architecture smell detector 2026-03-18 13:28:13 -05:00
darkamenosa
4b5487ee85 LINE: avoid runtime lookup during onboarding (#49960) 2026-03-19 01:27:21 +07:00
Onur
8f0727d75c Delete CNAME 2026-03-18 19:22:17 +01:00
Peter Steinberger
1746e130f9 test: fix imessage extension CI mocks 2026-03-18 18:20:04 +00:00
Peter Steinberger
a0d3dc94d0 perf: reduce unit test hot path overhead 2026-03-18 18:19:40 +00:00
Vincent Koc
fa52d122c4 Plugin SDK: route provider metadata through public models subpath 2026-03-18 11:18:04 -07:00
Peter Steinberger
62edfdffbd refactor: deduplicate reply payload handling 2026-03-18 18:14:57 +00:00
Vincent Koc
152d179302 Plugin SDK: add public WhatsApp runtime subpaths 2026-03-18 11:13:19 -07:00
Vincent Koc
8240fd900a Plugin SDK: route core channel runtimes through public subpaths 2026-03-18 11:00:58 -07:00
Josh Lehman
505d140aeb fix: stabilize build dependency resolution (#49928)
* build: mirror uuid for msteams

Add uuid to both the msteams bundled extension and the root package so the workspace build can resolve @microsoft/agents-hosting during tsdown while standalone extension installs also have the runtime dependency available.

Regeneration-Prompt: |
  pnpm build failed because @microsoft/agents-hosting 1.3.1 requires uuid in its published JS but does not declare it in its package manifest. The msteams extension dynamically imports that package, and the workspace build resolves it from the root dependency graph. Mirror uuid into the root package for workspace builds and keep it in extensions/msteams/package.json so standalone plugin installs also resolve it. Update the lockfile to match the manifest changes.

* build: prune stale plugin dist symlinks

Remove stale dist and dist-runtime plugin node_modules symlinks before tsdown runs. These links point back into extension installs, and tsdown's clean step can traverse them on rebuilds and hollow out the active pnpm dependency tree before plugin-sdk declaration generation runs.

Regeneration-Prompt: |
  pnpm build was intermittently failing in the plugin-sdk:dts phase after earlier build steps had already run. The symptom looked like missing root packages such as zod, ajv, commander, and undici even though a fresh install briefly fixed the problem. Investigate the build pipeline step by step rather than patching TypeScript errors. Confirm whether rebuilds mutate node_modules, identify the first step that does it, and preserve existing runtime-postbuild behavior.
  The key constraint is that dist and dist-runtime plugin node_modules links are intentional for runtime packaging, so do not remove that feature globally. Instead, make rebuilds safe by deleting only stale symlinks left in generated output before invoking tsdown, so tsdown cleanup cannot recurse back into the live pnpm install tree. Verify with repeated pnpm build runs.
2026-03-18 10:55:25 -07:00
Vincent Koc
ea74123ab2 Slack: fix directory test runtime stub 2026-03-18 10:54:00 -07:00
Vincent Koc
7d08070dd7 Plugins: generate bundled auth env metadata 2026-03-18 10:53:48 -07:00
Peter Steinberger
8d73bc77fa refactor: deduplicate reply payload helpers 2026-03-18 17:30:25 +00:00
scoootscooob
656679e6e0 Slack: remove duplicate directory imports (#49935) 2026-03-18 10:28:59 -07:00
scoootscooob
b49946a67e Slack: import directory helpers (#49930)
import the config-backed Slack directory helpers into the Slack channel plugin so directory.listPeers and directory.listGroups no longer throw at runtime, and add a regression test covering configured DM peer listing
2026-03-18 10:24:17 -07:00
Vincent Koc
ff326e90c3 Build: use hoisted pnpm linker 2026-03-18 10:14:53 -07:00
Vincent Koc
467ec4d5f3 Types: fix optional cluster check follow-ups 2026-03-18 10:02:40 -07:00
Peter Steinberger
05b1cdec3c test: make runner scheduling timing-driven 2026-03-18 16:57:38 +00:00
Vincent Koc
891e2a3da8 Build: isolate optional bundled plugin-sdk clusters 2026-03-18 09:54:22 -07:00
Vincent Koc
b4f16bad32 Plugin SDK: export windows spawn and temp path 2026-03-18 09:46:24 -07:00
Vincent Koc
a02bfd30c5 Plugin SDK: use public utility subpaths 2026-03-18 09:43:46 -07:00
Vincent Koc
f187e8bac4 Plugin SDK: use public slack subpath 2026-03-18 09:40:57 -07:00
Vincent Koc
e64cc1983f Plugin SDK: use public discord subpath 2026-03-18 09:39:12 -07:00
Vincent Koc
b3ca855283 Plugin SDK: use public whatsapp subpath 2026-03-18 09:37:54 -07:00
Peter Steinberger
27f655ed11 refactor: deduplicate channel runtime helpers 2026-03-18 16:37:27 +00:00
Vincent Koc
3e02635df3 Plugin SDK: use public telegram subpath 2026-03-18 09:33:21 -07:00
Vincent Koc
382640e674 Channels: trim optional bundled plugin defaults 2026-03-18 09:30:54 -07:00
Vincent Koc
d8008a9a67 Tools: classify optional bundled clusters 2026-03-18 09:26:39 -07:00
Peter Steinberger
3d8afb96bd fix: use transpiled jiti for source plugin shims 2026-03-18 16:24:45 +00:00
liyuan97
b64f4e313d MiniMax: add M2.7 models and update default to M2.7 (#49691)
* MiniMax: add M2.7 models and update default to M2.7

- Add MiniMax-M2.7 and MiniMax-M2.7-highspeed to provider catalog and model definitions
- Update default model from MiniMax-M2.5 to MiniMax-M2.7 across onboard, portal, and provider configs
- Update isModernMiniMaxModel to recognize M2.7 prefix
- Update all test fixtures to reflect M2.7 as default

Made-with: Cursor

* MiniMax: add extension test for model definitions

* update 2.7

* feat: add MiniMax M2.7 models and update default (#49691) (thanks @liyuan97)

---------

Co-authored-by: George Zhang <georgezhangtj97@gmail.com>
2026-03-18 09:24:37 -07:00
Chris Kimpton
823a09acbe docs: clarify that CI test-fix-only PRs are handled by maintainers (#49679)
Co-authored-by: Shadow <shadow@openclaw.ai>
2026-03-18 11:21:46 -05:00
Peter Steinberger
10dc4d65d1 test: refresh plugin extension boundary baseline 2026-03-18 16:16:31 +00:00
Peter Steinberger
5fd482d6b0 test: align acp session mode list 2026-03-18 16:14:14 +00:00
Vincent Koc
73539ac787 Core: move web media seam out of plugin sdk 2026-03-18 09:12:23 -07:00
Vincent Koc
947dac48f2 Tests: cap shards for explicit file lanes 2026-03-18 08:59:37 -07:00
Vincent Koc
cfdc0fdbe1 Plugins: include fal in image-generation contract registry 2026-03-18 08:59:00 -07:00
Vincent Koc
22fc5a5442 Contracts: narrow codex catalog hint return type 2026-03-18 08:54:01 -07:00
Peter Steinberger
49b248a333 fix: skip plugin sdk dts in docker builds 2026-03-18 15:48:15 +00:00
Vincent Koc
ebb10c0852 Contracts: fix codex catalog hint assertion 2026-03-18 08:46:58 -07:00
Vincent Koc
6a381e80bc Contracts: stabilize provider plugin test imports 2026-03-18 08:44:47 -07:00
Peter Steinberger
a0e7a2fcc1 fix: repair rebased contract gate 2026-03-18 15:43:24 +00:00
Peter Steinberger
f6928617b7 test: stabilize gate regressions 2026-03-18 15:36:32 +00:00
Peter Steinberger
7943e83c6c fix: restore rebased full gate 2026-03-18 15:36:18 +00:00
Peter Steinberger
c0c3c4824d fix: checkpoint gate fixes before rebase 2026-03-18 15:36:18 +00:00
Peter Steinberger
e9b19ca1d1 fix: restore full gate after web-search rebase 2026-03-18 15:35:27 +00:00
Peter Steinberger
861fcb1575 fix: restore rebased full gate 2026-03-18 15:34:27 +00:00
Peter Steinberger
b5d2123156 fix: stabilize rebased full gate 2026-03-18 15:34:27 +00:00
Peter Steinberger
0cddb5fb7c fix: restore full gate 2026-03-18 15:34:27 +00:00
Tak Hoffman
ea476de1e4 Add plugin-sdk seam audit script 2026-03-18 10:16:21 -05:00
Tak Hoffman
5d41fd4497 test: extend plugin contract setup timeouts 2026-03-18 09:42:52 -05:00
Tak Hoffman
ca13256913 Deps: restore known-good tlon api install source 2026-03-18 08:50:02 -05:00
Tak Hoffman
4a44ca8f79 fix llm-task invalid thinking timeout 2026-03-18 08:33:40 -05:00
Tak Hoffman
c2402e48c9 Build: narrow tsdown unresolved import guard 2026-03-18 08:32:41 -05:00
Tak Hoffman
13f396b395 Plugins: sync contract registry image providers 2026-03-18 08:27:48 -05:00
Tak Hoffman
86e9dcfc1b Build: fail on unresolved tsdown imports 2026-03-18 07:57:33 -05:00
Tak Hoffman
79c6158ac6 Deps: align pi-agent-core for declaration builds 2026-03-18 07:54:46 -05:00
Tak Hoffman
4157bcd024 Build: fail on plugin SDK declaration errors 2026-03-18 07:49:03 -05:00
Onur Solmaz
d41c9ad4cb Release: add plugin npm publish workflow (#47678)
* Release: add plugin npm publish workflow

* Release: make plugin publish scope explicit
2026-03-18 13:44:23 +01:00
Andrew Demczuk
089a43f5e8 fix(security): block build-tool and glibc env injection vectors in host exec sandbox (#49702)
Add GLIBC_TUNABLES, MAVEN_OPTS, SBT_OPTS, GRADLE_OPTS, ANT_OPTS,
DOTNET_ADDITIONAL_DEPS to blockedKeys and GRADLE_USER_HOME to
blockedOverrideKeys in the host exec security policy.

Closes #22681
2026-03-18 13:11:01 +01:00
Tak Hoffman
f58e0f5592 test simplify zero-state boundary guards 2026-03-18 07:04:50 -05:00
Tak Hoffman
06832112ee ci enforce boundary guardrails 2026-03-18 06:52:42 -05:00
Ayaan Zaidi
0e9b899aee test: enable vmForks for targeted channel test runs
Channel tests were always using process forks, missing the shared
transform cache that vmForks provides. This caused ~138s import
overhead per file. Now uses vmForks when available, matching the
pattern already used by unit-fast and extensions suites.
2026-03-18 15:54:02 +05:30
Ayaan Zaidi
f2655e1e92 test(telegram): fix incomplete sticker-cache mocks in tests 2026-03-18 15:37:24 +05:30
Vincent Koc
b9e08a6839 Config: align model compat thinking format types 2026-03-18 02:45:15 -07:00
Vincent Koc
238c036b0d Tlon: pin api-beta to current known-good commit 2026-03-18 02:43:43 -07:00
Vincent Koc
f96ee99bbc Plugin SDK: harden provider auth seams 2026-03-18 02:29:25 -07:00
Ayaan Zaidi
93a31b69de fix(config): add missing qwen-chat-template to thinking format schema 2026-03-18 14:54:38 +05:30
Vincent Koc
afad0697aa Plugin SDK: register provider auth login entrypoint 2026-03-18 02:06:06 -07:00
Vincent Koc
d8a1ad0f0d Plugin SDK: split provider auth login seam 2026-03-18 02:04:10 -07:00
Vignesh Natarajan
1890089f49 fix: serialize duplicate channel starts (#49583) (thanks @sudie-codes) 2026-03-18 01:57:12 -07:00
Vincent Koc
1040ae56b5 Telegram: fix reply-runtime test typings 2026-03-18 01:53:29 -07:00
Vincent Koc
2f3bc89f4f Config: align model compat thinking format schema 2026-03-18 01:53:29 -07:00
Vincent Koc
61a19107e1 Tlon: install api from tarball artifact 2026-03-18 01:49:47 -07:00
Vincent Koc
4ac9024de9 Contracts: harden plugin registry loading 2026-03-18 01:46:50 -07:00
Vincent Koc
7ac23ae7c2 Plugins: fix bundled web search compat registry 2026-03-18 01:42:02 -07:00
Vincent Koc
5625cf4724 fix(agents): correct broken docs/testing.md path in AGENTS.md 2026-03-18 01:33:04 -07:00
Vincent Koc
3cecbcf8b6 docs: fix curly quotes, non-breaking hyphens, and remaining apostrophes in headings 2026-03-18 01:31:38 -07:00
Vincent Koc
d1ef7d64e9 Contracts: harden provider registry loading 2026-03-18 01:30:05 -07:00
Vincent Koc
25011bdb1e Plugins: prefer source bundles in git checkouts 2026-03-18 01:08:40 -07:00
Ayaan Zaidi
0567f111ac test(telegram): stabilize inbound media harness 2026-03-18 13:35:56 +05:30
Ayaan Zaidi
d9e776eb47 test(telegram): align create-bot assertions 2026-03-18 13:35:56 +05:30
Vincent Koc
9b6859e5db Feishu: break plugin-sdk setup cycle 2026-03-18 01:02:16 -07:00
Vincent Koc
2afa556746 Format: sync seam fixes with oxfmt 2026-03-18 01:02:16 -07:00
Vincent Koc
da2289869d docs: remove experiments/ and design/ directories
Delete all experiment plans, proposals, research docs, and the
kilo-gateway-integration design doc. These are internal planning
docs that do not belong on the public docs site.

- 12 English experiment files
- 5 zh-CN experiment translations
- 1 design doc (kilo-gateway-integration)
- Remove nav groups from docs.json (English + zh-CN)
- Remove 3 redirects pointing to deleted experiment pages
- Remove dead experiment links from hubs.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-18 00:55:55 -07:00
Vincent Koc
0ae3e70a5c Plugin SDK: fix contract seam regressions 2026-03-18 00:50:19 -07:00
Vincent Koc
bde4c7995f docs: remove docs/refactor/ directory
Delete all 7 refactor design docs and the zh-CN translations.
Remove the zh-CN nav group from docs.json.

These were orphaned from English nav and accessible only by
direct URL. Internal design docs do not belong on the public
docs site.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-18 00:45:39 -07:00
Vincent Koc
fbd88e2c8f Main recovery: restore formatter and contract checks (#49570)
* Extensions: fix oxfmt drift on main

* Plugins: restore runtime barrel exports on main

* Config: restore web search compatibility types

* Telegram: align test harness with reply runtime

* Plugin SDK: fix channel config accessor generics

* CLI: remove redundant search provider casts

* Tests: restore main typecheck coverage

* Lobster: fix test import formatting

* Extensions: route bundled seams through plugin-sdk

* Tests: use extension env helper for xai

* Image generation: fix main oxfmt drift

* Config: restore latest main compatibility checks

* Plugin SDK: align guardrail tests with lint

* Telegram: type native command skill mock
2026-03-18 00:30:01 -07:00
Vincent Koc
e6c6aaa11b Perf: skip MCP/LSP runtime spawning when no servers are configured 2026-03-18 00:25:53 -07:00
Vincent Koc
80e681a60c Plugins: integrate LSP tool runtime into Pi embedded runner 2026-03-18 00:23:22 -07:00
Vincent Koc
8193af6d4e Plugins: add LSP server runtime with stdio JSON-RPC client and agent tool bridge 2026-03-18 00:23:22 -07:00
Vincent Koc
466510b6d8 refactor: replace "seam" terminology across codebase
Replace "seam" with clearer terms throughout:
- "surface" for public API/extension boundaries
- "boundary" for plugin/module interfaces
- "interface" for runtime connection points
- "hook" for test injection points
- "palette" for the lobster palette reference

Also delete experiments/acp-pluginification-architecture-plan.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-18 00:20:15 -07:00
Ayaan Zaidi
6802a768cf fix(zalo): break account helper cycles 2026-03-18 12:46:09 +05:30
Ayaan Zaidi
4e265fe7d6 test(telegram): fix native command runtime mocks 2026-03-18 12:46:09 +05:30
Vincent Koc
3a28bc7d8f docs(plugins): rewrite compatibility signals for clarity
Replace robotic prose with a scannable table and plain-language
summary. Same information, less stiff.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-18 00:14:20 -07:00
Vincent Koc
198ed08a38 docs: fix redirect chains and disambiguate duplicate titles
Redirects:
- /cron now goes directly to /automation/cron-jobs (was chaining via /cron-jobs)
- /model and /model/ now go directly to /concepts/models (was chaining via /models)

Duplicate titles disambiguated (6 of 7 - Logging is orphaned):
- Health Checks (macOS), Skills (macOS), Voice Wake (macOS), WebChat (macOS)
- General Troubleshooting (help/ vs gateway/)
- Provider Directory (providers/index vs concepts/model-providers)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-18 00:13:25 -07:00
Vincent Koc
6538c87673 Tests: update Claude bundle integration test for agents, output styles, and LSP 2026-03-18 00:12:24 -07:00
Vincent Koc
4ebd3d11aa Plugins: add LSP server loader and surface in inspect reports 2026-03-18 00:12:24 -07:00
Vincent Koc
50a81c8731 Plugins: merge agent and output-style dirs into Claude bundle skills 2026-03-18 00:12:24 -07:00
Vincent Koc
c99c4b1e27 Plugin SDK: restore read-only directory inspection seam 2026-03-18 00:10:35 -07:00
Vincent Koc
e17d10f7cd Plugin SDK: restore lobster and voice-call exports 2026-03-18 00:09:22 -07:00
Vincent Koc
21c2ba480a Image generation: native provider migration and explicit capabilities (#49551)
* Docs: retire nano-banana skill wrapper

* Doctor: migrate nano-banana to native image generation

* Image generation: align fal aspect ratio behavior

* Image generation: make provider capabilities explicit
2026-03-18 00:04:03 -07:00
Vincent Koc
79f2173cd2 docs: add missing frontmatter and title fields
- Add full frontmatter (title, summary, read_when) to 4 files that
  had none: auth-credential-semantics.md, kilo-gateway-integration.md,
  CONTRIBUTING-THREAT-MODEL.md, THREAT-MODEL-ATLAS.md
- Add missing title field to 3 provider docs: kilocode.md, litellm.md,
  together.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-18 00:02:43 -07:00
Vincent Koc
1cbfd53ed1 docs: remove apostrophes from headings (breaks Mintlify anchors)
Replace contractions and possessives in doc headings with expanded
forms so Mintlify generates stable anchor links. Updates matching
TOC entries and internal cross-references in faq.md.

Affected: faq.md (18 headings + 16 TOC links + 2 body refs),
twitch.md, ansible.md, render.mdx, macos-vm.md, digitalocean.md,
oracle.md, raspberry-pi.md, lore.md, AGENTS.dev.md, SOUL.dev.md,
BOOTSTRAP.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-18 00:02:42 -07:00
Vincent Koc
0dda3e66b5 Plugin SDK: align docs and fix runtime imports 2026-03-17 23:57:38 -07:00
Vincent Koc
3d31ba7830 Plugin SDK: guard package subpaths and fix Twitch setup export
* fix(plugins): add missing secret-input-schema build entry and Matrix runtime export

buildSecretInputSchema was not included in plugin-sdk-entrypoints.json,
so it was never emitted to dist/plugin-sdk/secret-input-schema.js. This
caused a ReferenceError during onboard when configuring channels that use
secret input schemas (matrix, feishu, mattermost, bluebubbles, nextcloud-talk, zalo).

Additionally, the Matrix extension's hand-written runtime-api barrel was
missing the re-export, unlike other extensions that use `export *` from
their plugin-sdk subpath.

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

* Plugin SDK: guard package subpaths and fix Twitch setup export

* Plugin SDK: fix import guardrail drift

---------

Co-authored-by: hxy91819 <masonxhuang@icloud.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 23:56:49 -07:00
Vincent Koc
8ac4b09fa4 docs: fix em-dash headings and broken links across docs
- Replace em-dashes in headings with hyphens/parens (breaks Mintlify anchors)
- Fix broken /testing link in pi-dev.md to /help/testing
- Convert absolute docs URLs to root-relative in pi-dev.md

Files: migrating.md, images.md, audio.md, media-understanding.md,
venice.md, minimax.md, AGENTS.default.md, security/index.md, pi-dev.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 23:55:46 -07:00
Vincent Koc
bd444435c9 Plugin SDK: clarify ACPX public seam 2026-03-17 23:53:32 -07:00
Vincent Koc
5eea523f39 UI: remove dead control UI modules 2026-03-17 23:52:43 -07:00
Vincent Koc
0385553918 Plugin SDK: trim lobster and qwen helper exports 2026-03-17 23:48:19 -07:00
Vincent Koc
98fbbebf6a Tests: add Claude bundle plugin inspect integration test 2026-03-17 23:34:56 -07:00
Vincent Koc
a5fa75cdb3 Plugins: accept Claude bundle hooks as wired capability in loader 2026-03-17 23:34:56 -07:00
Vincent Koc
d341d68180 Plugin SDK: trim legacy helper exports 2026-03-17 23:32:16 -07:00
Val Alexander
d1fe30b35f Plugins: add Twitch runtime barrel 2026-03-18 01:29:33 -05:00
Vincent Koc
fe84354a33 fix(plugins): add missing secret-input-schema build entry and Matrix runtime export
buildSecretInputSchema was not included in plugin-sdk-entrypoints.json,
so it was never emitted to dist/plugin-sdk/secret-input-schema.js. This
caused a ReferenceError during onboard when configuring channels that use
secret input schemas (matrix, feishu, mattermost, bluebubbles, nextcloud-talk, zalo).

Additionally, the Matrix extension's hand-written runtime-api barrel was
missing the re-export, unlike other extensions that use `export *` from
their plugin-sdk subpath.

Co-authored-by: hxy91819 <masonxhuang@icloud.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 23:28:23 -07:00
Vincent Koc
c36a493e80 Docs: clarify plugin compatibility signals 2026-03-17 23:27:23 -07:00
Vincent Koc
ad185dd4a8 CLI: make config compatibility advice opt-in 2026-03-17 23:27:23 -07:00
Bob
732e075e92 ACP: reproduce binding restart session reset (#49435)
* ACP: reproduce restart binding regression

* ACP: resume configured bindings after restart

* ACP: scope restart resume to persistent sessions

---------

Co-authored-by: Onur <2453968+osolmaz@users.noreply.github.com>
2026-03-18 07:24:38 +01:00
Vincent Koc
b333eb137b Tests: align plugin test imports with local barrels 2026-03-17 23:23:58 -07:00
Vincent Koc
100d7b0227 Doctor: add bundle plugin capability summary to workspace status 2026-03-17 23:14:40 -07:00
Vincent Koc
b48413e252 Plugins: surface MCP servers and bundle capabilities in inspect reports 2026-03-17 23:14:40 -07:00
Vincent Koc
b9b891b614 Plugins: wire Claude bundle hook resolution (parity with Codex) 2026-03-17 23:14:40 -07:00
Vincent Koc
d1d10007a9 Plugins: guard whatsapp local barrel 2026-03-17 23:11:32 -07:00
Vincent Koc
77dfa73736 Plugins: internalize whatsapp SDK imports 2026-03-17 23:10:51 -07:00
Vincent Koc
8af4628a6d Plugins: guard signal and telegram barrels 2026-03-17 23:09:26 -07:00
Vincent Koc
c81b4a5389 Plugins: guard remaining local barrels 2026-03-17 23:09:26 -07:00
Vincent Koc
6e723dfd69 Plugins: internalize medium extension SDK imports 2026-03-17 23:09:26 -07:00
Vincent Koc
df79113593 Plugins: internalize telegram SDK imports 2026-03-17 23:09:26 -07:00
Vincent Koc
0bdd17aef2 Plugins: finish signal SDK internalization 2026-03-17 23:09:26 -07:00
Vincent Koc
9282d5d09e Plugins: soften hook-only compatibility copy 2026-03-17 23:08:38 -07:00
scoootscooob
08a0219b1a Google Chat: thin runtime api seam (#49504)
Merged via squash.

Prepared head SHA: 3369cf2c35
Co-authored-by: scoootscooob <167050519+scoootscooob@users.noreply.github.com>
Co-authored-by: scoootscooob <167050519+scoootscooob@users.noreply.github.com>
Reviewed-by: @scoootscooob
2026-03-17 23:02:30 -07:00
Vincent Koc
75f98fe19a Plugins: guard small extension barrels 2026-03-17 23:01:28 -07:00
Vincent Koc
d949a513c5 Plugins: internalize small extension SDK imports 2026-03-17 23:01:28 -07:00
Ayaan Zaidi
c245c8b39d refactor(plugin-sdk): split interactive runtime helpers 2026-03-18 11:30:34 +05:30
Ayaan Zaidi
8c436a470e perf(test): decouple plugin runtime bootstrap 2026-03-18 11:30:34 +05:30
Vincent Koc
1aab71cf5b Plugins: guard local extension barrels 2026-03-17 22:59:24 -07:00
Vincent Koc
4d551e6f33 Plugins: internalize acpx SDK imports 2026-03-17 22:58:43 -07:00
Vincent Koc
02826eaa0c Plugins: internalize lobster SDK imports 2026-03-17 22:58:03 -07:00
Vincent Koc
ed479f96a1 Plugins: internalize qwen portal auth SDK imports 2026-03-17 22:57:58 -07:00
Vincent Koc
0a065bc6c2 Plugins: guard channel api barrels 2026-03-17 22:56:28 -07:00
Vincent Koc
5642fb2682 Plugins: internalize twitch SDK imports 2026-03-17 22:56:28 -07:00
Vincent Koc
645c5bda2c Plugins: internalize zalo SDK imports 2026-03-17 22:56:28 -07:00
Vincent Koc
2ef28a7a3e Plugins: internalize zalouser SDK imports 2026-03-17 22:56:28 -07:00
Vincent Koc
7b27f8a9ae docs(refactor): replace seam terminology with capability/surface
Align refactor docs with the public capability model vocabulary.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 22:55:32 -07:00
Josh Lehman
7f0f8dd268 feat: expose context-engine compaction delegate helper (#49061)
* ContextEngine: add runtime compaction delegate helper

* plugin-sdk: expose compaction delegate through compat

* docs: clarify delegated plugin compaction

* docs: use scoped compaction delegate import
2026-03-17 22:54:18 -07:00
Josh Lehman
937f118d8e Gateway: add docs hint for plugin override trust error (#49513) 2026-03-17 22:53:34 -07:00
Muhammed Mukhthar CM
ff849613a4 Extensions: route Signal and xai through plugin-sdk 2026-03-18 05:42:54 +00:00
Muhammed Mukhthar CM
dc20a7cd89 Build: fix bundled plugin runtime symlinks 2026-03-18 05:42:51 +00:00
Tak Hoffman
cd2752346c refactor move web search sdk helpers into plugin-sdk 2026-03-18 00:27:02 -05:00
Val Alexander
5f89897df1 plugins: dist node_modules symlink + config raw-toggle UI fix (#49490)
* plugins: symlink node_modules into dist plugin dir for bare-specifier resolution

* UI: fix config raw-toggle button sizing and semantic markup

* Update scripts/stage-bundled-plugin-runtime.mjs

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* Update ui/src/styles/config.css

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* fix: hoist dist node_modules cleanup before existsSync guard; drop !important from config toggle

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-03-18 00:20:14 -05:00
Alix-007
2c579b6ac1 fix(models): preserve @YYYYMMDD version suffixes (#48896) thanks @Alix-007
Co-authored-by: Alix-007 <267018309+Alix-007@users.noreply.github.com>
Co-authored-by: frankekn <frank.ekn@gmail.com>
2026-03-18 13:20:06 +08:00
Josh Lehman
4ca87fa4b0 fix: restore main build (#49478)
* Build: restore main build

* Config: align model compat schema
2026-03-17 22:14:56 -07:00
scoootscooob
4c160d2c3a Signal: fix account config type import (#49470)
Merged via squash.

Prepared head SHA: fab2ef4c1f
Co-authored-by: scoootscooob <167050519+scoootscooob@users.noreply.github.com>
Co-authored-by: scoootscooob <167050519+scoootscooob@users.noreply.github.com>
Reviewed-by: @scoootscooob
2026-03-17 22:12:37 -07:00
scoootscooob
bfecc58a62 xAI: add web search credential metadata (#49472)
Merged via squash.

Prepared head SHA: faefa4089d
Co-authored-by: scoootscooob <167050519+scoootscooob@users.noreply.github.com>
Co-authored-by: scoootscooob <167050519+scoootscooob@users.noreply.github.com>
Reviewed-by: @scoootscooob
2026-03-17 22:08:19 -07:00
Val Alexander
5464ad113e UI: expand-to-canvas, session navigation, plugin SDK fixes (#49483)
* Plugins: fix signal SDK circular re-exports and reserved commands TDZ

* UI: add expand-to-canvas button and in-app session navigation

* changelog: UI expand/navigate and plugin TDZ/import fixes
2026-03-18 00:07:53 -05:00
Tak Hoffman
0354d49a82 docs update web search config guidance 2026-03-18 00:00:17 -05:00
Peter Steinberger
67ce726bba fix(slack): repair gateway watch runtime export 2026-03-18 04:52:20 +00:00
Peter Steinberger
05603e4e6c refactor: deduplicate channel config adapters 2026-03-18 04:51:29 +00:00
Tak Hoffman
2c5fd8e0c1 chore finalize web search provider boundaries 2026-03-17 23:50:18 -05:00
Peter Steinberger
e1cae60294 test: harden prompt composition coverage 2026-03-17 21:42:46 -07:00
Ayaan Zaidi
1ef7e544e9 test(telegram): pass explicit deps in command tests 2026-03-18 10:12:15 +05:30
Ayaan Zaidi
b9dfb6cc23 test(telegram): inject bot deps in harnesses 2026-03-18 10:12:15 +05:30
Ayaan Zaidi
b85d97f22c refactor(telegram): inject shared bot deps 2026-03-18 10:12:15 +05:30
Ayaan Zaidi
243dabc186 test(telegram): align media harness with runtime seam 2026-03-18 10:12:15 +05:30
Ayaan Zaidi
23f618d62d test(telegram): rewire bot harnesses to runtime seams 2026-03-18 10:12:15 +05:30
Ayaan Zaidi
edcf3e9d32 test(telegram): add dispatch and handler seams 2026-03-18 10:12:15 +05:30
Ayaan Zaidi
6aaf0d0f24 test(telegram): add bot runtime seam 2026-03-18 10:12:15 +05:30
Tak Hoffman
77fb2589b1 test add extension plugin sdk boundary guards 2026-03-17 23:39:51 -05:00
Tak Hoffman
112d1d3a7c refactor web search config ownership into extensions 2026-03-17 23:39:51 -05:00
Peter Steinberger
2fbf2c0a47 fix: repair plugin runtime api imports 2026-03-18 04:38:06 +00:00
Gustavo Madeira Santana
9932d2984c Docs: clarify plugin target resolution and directories 2026-03-18 04:36:27 +00:00
Vincent Koc
873ac8bc79 Plugins: internalize slack SDK imports 2026-03-17 21:35:32 -07:00
Vincent Koc
aa3739167c Plugins: internalize imessage SDK imports 2026-03-17 21:35:32 -07:00
Vincent Koc
6710a2be61 Image generation: add fal provider (#49454) 2026-03-17 21:35:13 -07:00
Vincent Koc
04eb17bfab Tests: clean up trusted proxy pairing seed 2026-03-17 21:33:25 -07:00
joshavant
e5363b0268 Changelog: update secrets exec refs attribution 2026-03-17 23:32:37 -05:00
Peter Steinberger
a8907d80dd feat: finish xai provider integration 2026-03-17 21:31:20 -07:00
Gustavo Madeira Santana
2b5fa0931d Plugins: move config-backed directories behind channel plugins 2026-03-18 04:29:50 +00:00
Peter Steinberger
b86bc9de95 refactor: split remaining monitor runtime helpers 2026-03-17 21:27:21 -07:00
Val Alexander
4e94f3aa02 UI: mute colored focus ring on agent chat textarea 2026-03-17 23:25:54 -05:00
Gustavo Madeira Santana
e93412b5ce Outbound: move target resolution heuristics behind plugins 2026-03-18 04:24:54 +00:00
Josh Avant
0ffcc308f2 Secrets: gate exec dry-run and preflight resolution behind --allow-exec (#49417)
* Secrets: gate exec dry-run resolution behind --allow-exec

* Secrets: fix dry-run completeness and skipped exec audit semantics

* Secrets: require --allow-exec for exec-containing apply writes

* Docs: align secrets exec consent behavior

* Changelog: note secrets exec consent gating
2026-03-17 23:24:34 -05:00
Vincent Koc
bf470b711b docs(plugins): dedup in-process trust refs and add manifest cross-references
- Replace redundant in-process trust statements with cross-references
  to the Execution model section (lines 573, 2436)
- Add CLI reference link from plugin.md CLI section
- Add configuration reference link from manifest.md validation section
- Add provider runtime hooks link from manifest.md providerAuthChoices

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 21:23:56 -07:00
Gustavo Madeira Santana
f842de046c doctor: clarify orphan transcript archive prompt 2026-03-18 04:15:17 +00:00
Val Alexander
e5eda19db2 UI: fix redundant applyBorderRadius call and restore session-scope cap test (#49443) 2026-03-17 23:14:43 -05:00
Val Alexander
1e1bc24f80 Enhance settings persistence: add error handling for storage operations to ensure in-memory updates are applied even when storage quota is exceeded or restricted. 2026-03-17 23:14:22 -05:00
Vincent Koc
2d87bc703f Plugins: align googlechat runtime imports 2026-03-17 21:11:18 -07:00
Vincent Koc
2b67a3f76e Plugins: internalize googlechat SDK imports 2026-03-17 21:11:17 -07:00
Vincent Koc
4285eb3539 Plugins: internalize signal SDK imports 2026-03-17 21:11:15 -07:00
Vincent Koc
0636c6eafa Plugins: internalize irc SDK imports 2026-03-17 21:11:14 -07:00
Gustavo Madeira Santana
2a02337be2 Feishu: move outbound session routing behind plugin boundary 2026-03-18 04:09:49 +00:00
Gustavo Madeira Santana
c03b0877d0 Tlon: move outbound session routing behind plugin boundary 2026-03-18 04:09:49 +00:00
Gustavo Madeira Santana
de0285d8ea Nostr: move outbound session routing behind plugin boundary 2026-03-18 04:09:49 +00:00
Gustavo Madeira Santana
b8dd6548aa Zalo User: move outbound session routing behind plugin boundary 2026-03-18 04:09:49 +00:00
Gustavo Madeira Santana
33bcf11c3f Zalo: move outbound session routing behind plugin boundary 2026-03-18 04:09:48 +00:00
Gustavo Madeira Santana
6816c76738 Nextcloud Talk: move outbound session routing behind plugin boundary 2026-03-18 04:09:48 +00:00
Gustavo Madeira Santana
0f7cd59824 BlueBubbles: move outbound session routing behind plugin boundary 2026-03-18 04:09:48 +00:00
Gustavo Madeira Santana
d6c13d9dc0 Mattermost: move outbound session routing behind plugin boundary 2026-03-18 04:09:48 +00:00
Gustavo Madeira Santana
028f3c4d15 MSTeams: move outbound session routing behind plugin boundary 2026-03-18 04:09:48 +00:00
Gustavo Madeira Santana
d1d36da700 Matrix: move outbound session routing behind plugin boundary 2026-03-18 04:09:48 +00:00
Gustavo Madeira Santana
fa896704d2 WhatsApp: move outbound session routing behind plugin boundary 2026-03-18 04:09:47 +00:00
Gustavo Madeira Santana
6ba15aadcc Discord: export runtime config helpers 2026-03-18 04:09:47 +00:00
Gustavo Madeira Santana
4079de21ce Outbound: route sessions through channel plugins 2026-03-18 04:09:47 +00:00
Gustavo Madeira Santana
826c592deb Plugin SDK: add outbound session route helpers 2026-03-18 04:09:47 +00:00
Tak Hoffman
92a40d324a test refresh boundary inventories for web search migration 2026-03-17 23:07:19 -05:00
Tak Hoffman
3de973ffff refactor web search provider execution out of core 2026-03-17 23:07:19 -05:00
Val Alexander
df72ca1ece UI: add corner radius slider and appearance polish (#49436)
* Refactor CSS styles: replace hardcoded colors with CSS variables for accent colors and optimize spacing rules in layout files.

* Update CSS styles: streamline selectors, enhance hover effects, and adjust focus states for chat components and layout elements.

* Enhance focus styles for chat components: update border colors and box-shadow effects for improved accessibility and visual consistency.

* Implement theme management in UI: add dynamic theme switching based on user settings, update CSS variables for new themes, and enhance security by preventing prototype pollution in form utilities.

* Implement border radius customization in UI: add settings for corner roundness, update CSS styles for sliders, and integrate border radius adjustments across components.

* Remove border radius property from UI settings and related functions to simplify configuration and enhance consistency across components.

* Enhance responsive design in UI: add media queries for mobile layouts, adjust padding and grid structures, and implement bottom navigation for improved usability on smaller screens.

* UI: add corner radius slider to Appearance settings
2026-03-17 23:06:01 -05:00
Peter Steinberger
1a9114a169 refactor: deduplicate setup wizard helpers 2026-03-18 03:58:22 +00:00
Vincent Koc
1c81b82f48 Config: warn on plugin compatibility debt 2026-03-17 20:56:16 -07:00
Tak Hoffman
24dc91c6ef ci add time-gated boundary inventory jobs 2026-03-17 22:53:12 -05:00
Tak Hoffman
e691345774 fix preserve plugin-sdk web search compatibility 2026-03-17 22:53:12 -05:00
Peter Steinberger
326c660775 fix: restore discord runtime api exports after rebase 2026-03-17 20:52:42 -07:00
Peter Steinberger
a2518a16ac refactor: split monitor runtime helpers 2026-03-17 20:52:42 -07:00
Peter Steinberger
fb5ab95e03 build: update deps except carbon 2026-03-17 20:51:54 -07:00
Ayaan Zaidi
a89cb3e10e refactor(telegram): unify action normalization 2026-03-18 09:15:41 +05:30
Vincent Koc
4c9028439c Tests: make seam guardrails path-safe 2026-03-17 20:44:37 -07:00
Vincent Koc
2c35faf437 docs: fix "a OpenClaw" → "an OpenClaw" grammar across docs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 20:43:18 -07:00
Vincent Koc
d2ef865073 docs(plugins): deduplicate and cross-reference plugin capability docs
- Merge hook order + which-hook-to-use into single reference table
- Deduplicate npm spec restrictions (link to CLI reference)
- Deduplicate plugin shapes in cli/plugins.md (link to main definition)
- Add capability-cookbook to docs.json navigation
- Add cross-references: Architecture→Load pipeline, Config→configuration
  reference, Plugin slots→manifest kind, Adding capability→cookbook
- Add missing cursor bundle subtype in 3 locations
- Fix verbose/info→verbose/inspect references
- Remove duplicate "info is alias for inspect" note
- Add missing install command to CLI command summary
- Replace premature "shape" jargon with "pattern" before definition

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 20:43:18 -07:00
Gustavo Madeira Santana
1aae93b1fa LINE: remove shared group mentions helper 2026-03-18 03:43:07 +00:00
Vincent Koc
f253f14b0b Plugins: internalize discord SDK imports 2026-03-17 20:42:28 -07:00
Gustavo Madeira Santana
a8f433d611 BlueBubbles: move group policy behind plugin boundary 2026-03-18 03:40:42 +00:00
Gustavo Madeira Santana
bf8702973f Google Chat: move group policy behind plugin boundary 2026-03-18 03:39:25 +00:00
Gustavo Madeira Santana
4e706da898 iMessage: fix group policy config import 2026-03-18 03:39:21 +00:00
Gustavo Madeira Santana
1f5f3fc2ef iMessage: move group policy behind plugin boundary 2026-03-18 03:38:01 +00:00
Gustavo Madeira Santana
c29458d407 WhatsApp: move group policy behind plugin boundary 2026-03-18 03:38:01 +00:00
Gustavo Madeira Santana
a4b98f95c2 Changelog: attribute message discovery break 2026-03-18 03:38:01 +00:00
Peter Steinberger
9c12b41c52 fix: restore plugin sdk exports after rebase 2026-03-17 20:36:03 -07:00
Peter Steinberger
005b25e9d4 refactor: split remaining monitor runtime helpers 2026-03-17 20:36:03 -07:00
Vincent Koc
6556a40330 Tests: drop unstable plugins cli coverage 2026-03-17 20:34:51 -07:00
Vincent Koc
5c4903d3fd Plugins: centralize compatibility formatting 2026-03-17 20:33:12 -07:00
Gustavo Madeira Santana
7ba8dd112f Telegram: move group policy behind plugin boundary 2026-03-18 03:32:51 +00:00
Vincent Koc
a34944c918 Tests: pin Telegram fallback host (#49364)
* Tests: pin Telegram fallback host

* Changelog: note Telegram fallback guardrail
2026-03-17 20:32:38 -07:00
Vincent Koc
f8f9e06b58 Guardrails: pin runtime-api export seams (#49371)
* Guardrails: pin runtime-api export seams

* Guardrails: tighten runtime-api keyed lookup

* Changelog: note runtime-api guardrails

* Tests: harden runtime-api guardrail parsing

* Tests: align runtime-api guardrails with current seams
2026-03-17 20:30:14 -07:00
Gustavo Madeira Santana
0bfaa36126 Discord: move group policy behind plugin boundary 2026-03-18 03:30:02 +00:00
Peter Steinberger
9350cb19dd refactor: deduplicate plugin setup and channel config helpers 2026-03-18 03:28:05 +00:00
Gustavo Madeira Santana
9e556f75f5 Slack: move group policy behind plugin boundary 2026-03-18 03:26:21 +00:00
Gustavo Madeira Santana
889011c08c Build: remove legacy WhatsApp login shim 2026-03-18 03:23:27 +00:00
Gustavo Madeira Santana
abaa9107c5 Build: remove legacy channel action shim entries 2026-03-18 03:22:04 +00:00
Vincent Koc
2f21eeb3cb Plugins: internalize bluebubbles SDK imports 2026-03-17 20:21:00 -07:00
Gustavo Madeira Santana
1777b99ccc Signal: move message actions behind plugin boundary 2026-03-18 03:19:35 +00:00
Val Alexander
56066dccb0 docs(ui): harden legacy query token guidance (#49053) 2026-03-17 22:18:42 -05:00
Vincent Koc
0a90b07f8d Agents: honor workspace Anthropic provider capabilities 2026-03-17 20:17:39 -07:00
Gustavo Madeira Santana
28b888cbcd Slack: move message actions behind plugin boundary 2026-03-18 03:14:32 +00:00
Peter Steinberger
cd5c2f4cb2 refactor: dedupe channel plugin shared assembly 2026-03-17 20:13:52 -07:00
Vincent Koc
3cc83cb81e Plugins: internalize msteams SDK imports 2026-03-17 20:11:24 -07:00
Vincent Koc
a41840f717 Matrix: split internal runtime barrel from public SDK 2026-03-17 20:11:22 -07:00
Val Alexander
53dcafbec3 Config UI: click-to-reveal redacted env vars and use lightweight re-render (#49399)
* Refactor CSS styles: replace hardcoded colors with CSS variables for accent colors and optimize spacing rules in layout files.

* Update CSS styles: streamline selectors, enhance hover effects, and adjust focus states for chat components and layout elements.

* Enhance focus styles for chat components: update border colors and box-shadow effects for improved accessibility and visual consistency.

* Config UI: click-to-reveal redacted env vars and use lightweight re-render
2026-03-17 22:10:31 -05:00
Gustavo Madeira Santana
206d1be082 Changelog: note plugin message discovery break 2026-03-18 03:05:23 +00:00
Vincent Koc
27d4fdf3bb Plugins: surface compatibility notices 2026-03-17 20:03:40 -07:00
Gustavo Madeira Santana
6b9b32a160 Docs: require unified message discovery 2026-03-18 03:02:17 +00:00
Gustavo Madeira Santana
682f4d1ca3 Plugin SDK: require unified message discovery 2026-03-18 03:02:16 +00:00
Vincent Koc
870f260772 Gateway: cover trusted-proxy scope regression (#49372)
* Gateway: cover trusted-proxy scope regression

* Changelog: note trusted-proxy regression coverage

* Gateway: format trusted-proxy regression test
2026-03-17 19:59:01 -07:00
Val Alexander
25e6cd38b6 UI: mute sidebar and chat input accent colors (#49390)
* Refactor CSS styles: replace hardcoded colors with CSS variables for accent colors and optimize spacing rules in layout files.

* Update CSS styles: streamline selectors, enhance hover effects, and adjust focus states for chat components and layout elements.

* Enhance focus styles for chat components: update border colors and box-shadow effects for improved accessibility and visual consistency.
2026-03-17 21:56:50 -05:00
Peter Steinberger
fa34cb887d fix: resolve rebase export collisions 2026-03-17 19:53:32 -07:00
Peter Steinberger
5b2c5ee2bc refactor: remove remaining extension src imports 2026-03-17 19:53:32 -07:00
Peter Steinberger
055632460d docs: reorder changelog sections by interest 2026-03-17 19:51:57 -07:00
Vincent Koc
889bb8a78a Plugins: internalize matrix and feishu SDK imports 2026-03-17 19:47:25 -07:00
Peter Steinberger
1313767825 refactor: enforce plugin boundary seams 2026-03-17 19:45:36 -07:00
Gustavo Madeira Santana
b942dacf48 Sessions: move session target shaping to plugins 2026-03-18 02:44:49 +00:00
Peter Steinberger
44521d6b20 test: stabilize plugin contract mocks 2026-03-18 02:44:30 +00:00
Vincent Koc
6f060d7e6c Deps: bump fast-xml-parser audit override (#49367)
* Deps: bump fast-xml-parser audit override

* Changelog: note fast-xml-parser audit fix [skip ci]
2026-03-17 19:43:15 -07:00
Peter Steinberger
841b1a59d7 docs: unify unreleased changelog sections 2026-03-17 19:41:17 -07:00
Peter Steinberger
01ae160108 chore: checkpoint ci triage 2026-03-18 02:41:06 +00:00
Gustavo Madeira Santana
d8b95d2315 Polls: scope Telegram poll extras to plugin schema 2026-03-18 02:34:33 +00:00
Gustavo Madeira Santana
fa73f5aeb5 Polls: defer shared parsing until plugin fallback 2026-03-18 02:34:25 +00:00
Gustavo Madeira Santana
9e8b9aba1f WhatsApp: isolate lazy action runtime boundary 2026-03-18 02:20:57 +00:00
Gustavo Madeira Santana
bb803a42ac Mattermost: normalize plugin imports 2026-03-18 02:18:06 +00:00
Gustavo Madeira Santana
09de192b77 Tlon: import channel account snapshot type 2026-03-18 02:18:02 +00:00
Gustavo Madeira Santana
8e98019b6a Nostr: remove plugin API import cycle 2026-03-18 02:17:56 +00:00
Gustavo Madeira Santana
fb0d04c834 Tests: migrate channel action discovery to describeMessageTool 2026-03-18 02:17:47 +00:00
Gustavo Madeira Santana
1c6676cd57 Plugins: remove first-party legacy message discovery shims 2026-03-18 02:17:40 +00:00
Gustavo Madeira Santana
ed7269518f Tlon: fix plugin-sdk import boundaries 2026-03-18 02:12:53 +00:00
Gustavo Madeira Santana
b5c38b1095 Docs: point message runtime docs and tests at plugin-owned code 2026-03-18 02:08:08 +00:00
Gustavo Madeira Santana
8165db758b WhatsApp: move action runtime into extension 2026-03-18 02:08:08 +00:00
Gustavo Madeira Santana
b3ae50c71c Slack: move action runtime into extension 2026-03-18 02:08:08 +00:00
Gustavo Madeira Santana
c3386d34d2 Telegram: move action runtime into extension 2026-03-18 02:08:07 +00:00
Gustavo Madeira Santana
9df3e9b617 Discord: move action runtime into extension 2026-03-18 02:08:07 +00:00
Gustavo Madeira Santana
4c36436fb4 Plugin SDK: add legacy message discovery helper 2026-03-18 02:08:07 +00:00
Vincent Koc
d3fc6c0cc7 Plugins: internalize mattermost and tlon SDK imports 2026-03-17 19:05:51 -07:00
Gustavo Madeira Santana
d073ec42cd Tests: reuse embedded runner harness imports 2026-03-18 01:21:15 +00:00
Josh Avant
2d3bcbfe08 CLI: skip exec SecretRef dry-run resolution unless explicitly allowed (#49322)
* CLI: gate exec SecretRef dry-run resolution behind opt-in

* Docs: clarify config dry-run exec opt-in behavior

* CLI: preserve static exec dry-run validation
2026-03-17 20:20:11 -05:00
Gustavo Madeira Santana
9a455a8c08 Tests: remove compaction hook polling 2026-03-18 01:15:51 +00:00
Gustavo Madeira Santana
50cac39657 Agents: stabilize compaction hook test harness 2026-03-18 01:06:48 +00:00
Gustavo Madeira Santana
53df7ff86d Agents: stabilize overflow runner test harness 2026-03-18 01:06:43 +00:00
Gustavo Madeira Santana
f2de673130 Docs: clarify plugin-owned message discovery 2026-03-18 00:49:02 +00:00
Gustavo Madeira Santana
ab62f3b9f4 Agents: route embedded discovery and compaction ids 2026-03-18 00:49:01 +00:00
Brian Ernesto
ab1da26f4d fix(macos): show sessions after controls in tray menu (#38079)
* fix(macos): show sessions after controls in tray menu

When many sessions are active, the injected session rows push the
toggles, action buttons, and settings items off-screen, requiring
a scroll to reach them.

Change findInsertIndex and findNodesInsertIndex to anchor just before
the separator above 'Settings…' instead of before 'Send Heartbeats'.
This ensures the controls section is always immediately visible on
menu open, with sessions appearing below.

* refactor: extract findAnchoredInsertIndex to eliminate duplication

findInsertIndex and findNodesInsertIndex shared identical logic.
Extract into a single private helper so any future anchor change
(e.g. Settings item title) only needs one edit.

* macOS: use structural tray menu anchor

---------

Co-authored-by: Brian Ernesto <bernesto@users.noreply.github.com>
Co-authored-by: ImLukeF <92253590+ImLukeF@users.noreply.github.com>
2026-03-18 11:29:11 +11:00
Gustavo Madeira Santana
7dabcf287d Agents: align compact message discovery scope 2026-03-18 00:16:02 +00:00
Gustavo Madeira Santana
951f3f992b Plugins: split message discovery and dispatch 2026-03-18 00:15:58 +00:00
Gustavo Madeira Santana
da948a8073 Teams: consolidate message tool discovery 2026-03-18 00:07:06 +00:00
Gustavo Madeira Santana
cac1c62208 Feishu: consolidate message tool discovery 2026-03-18 00:07:03 +00:00
Gustavo Madeira Santana
28ab5061bf Mattermost: consolidate message tool discovery 2026-03-18 00:07:01 +00:00
Gustavo Madeira Santana
60104de428 Telegram: consolidate message tool discovery 2026-03-18 00:06:58 +00:00
Gustavo Madeira Santana
0a0ca804aa Discord: consolidate message tool discovery 2026-03-18 00:06:55 +00:00
Gustavo Madeira Santana
c9ba985839 Slack: consolidate message tool discovery 2026-03-18 00:06:50 +00:00
Gustavo Madeira Santana
bb365dba73 Plugin SDK: unify message tool discovery 2026-03-18 00:06:45 +00:00
Gustavo Madeira Santana
144b95ffce Agents: scope cross-channel message discovery 2026-03-17 23:58:52 +00:00
Gustavo Madeira Santana
b1c03715fb Agents: remove unused bootstrap imports 2026-03-17 23:55:13 +00:00
Gustavo Madeira Santana
1c08455848 Discord: dedupe message action discovery state 2026-03-17 23:55:08 +00:00
Gustavo Madeira Santana
5ce3eb3ff3 Telegram: dedupe message action discovery state 2026-03-17 23:55:05 +00:00
Gustavo Madeira Santana
a32c7e16d2 Plugin SDK: normalize and harden message action discovery 2026-03-17 23:55:00 +00:00
Gustavo Madeira Santana
df284fec27 Teams: own message tool card schema 2026-03-17 23:48:44 +00:00
Gustavo Madeira Santana
60d4c5a30b Feishu: own message tool card schema 2026-03-17 23:48:44 +00:00
Gustavo Madeira Santana
d95dc50e0a Mattermost: own message tool button schema 2026-03-17 23:48:44 +00:00
Gustavo Madeira Santana
dbc367e50a Telegram: own message tool schema and runtime seam 2026-03-17 23:48:43 +00:00
Gustavo Madeira Santana
05634eed16 Discord: own message tool components schema 2026-03-17 23:48:43 +00:00
Gustavo Madeira Santana
4b5e801d1b BlueBubbles: scope group actions in message discovery 2026-03-17 23:48:43 +00:00
Gustavo Madeira Santana
11720510f5 Slack: own message tool blocks schema 2026-03-17 23:48:43 +00:00
Gustavo Madeira Santana
a14ad01d66 Plugin SDK: centralize message tool discovery and context 2026-03-17 23:48:43 +00:00
scoootscooob
4e912bffd8 Agents: improve prompt cache hit rate and add prompt composition regression tests (#49237)
Merged via squash.

Prepared head SHA: 978b0cd6c7
Co-authored-by: scoootscooob <167050519+scoootscooob@users.noreply.github.com>
Co-authored-by: scoootscooob <167050519+scoootscooob@users.noreply.github.com>
Reviewed-by: @scoootscooob
2026-03-17 16:40:20 -07:00
joshavant
79f7dbfd6e Changelog: add config set expansion entry 2026-03-17 18:32:55 -05:00
joshavant
ab5aec137c CLI: fix config set dry-run coverage gaps 2026-03-17 18:31:03 -05:00
Gustavo Madeira Santana
ffe24955c8 Plugins: fix pnpm check regressions 2026-03-17 23:25:40 +00:00
Gustavo Madeira Santana
f118191182 Plugin SDK: break line and nostr export cycles 2026-03-17 23:22:22 +00:00
Vincent Koc
0e4c072f37 Models: add native GPT-5.4 mini and nano support (#49289)
* Models: add GPT-5.4 mini and nano support

* Tests: cover OpenAI GPT-5.4 mini and nano extension support
2026-03-17 16:21:39 -07:00
Josh Avant
e99963100d CLI: expand config set with SecretRef/provider builders and dry-run (#49296)
* CLI: expand config set ref/provider builder and dry-run

* Docs: revert README Discord token example
2026-03-17 18:15:49 -05:00
Vincent Koc
bd21442f7e Perf: add extension memory profiling command 2026-03-17 15:59:08 -07:00
Vincent Koc
af63b72901 Plugins: internalize nextcloud talk SDK imports 2026-03-17 15:58:00 -07:00
Vincent Koc
e7422716bb docs(plugins): rename plugins info to plugins inspect across all docs
Update all references from `plugins info` to `plugins inspect` in bundles,
plugin system, and CLI index docs to match the renamed command.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 15:33:42 -07:00
Josh Lehman
2f65ae1b80 fix: break Synology Chat plugin-sdk reexport cycle (#49281)
Build failed because src/plugin-sdk/synology-chat.ts reexported setup symbols through extensions/synology-chat/api.ts, and that API shim reexports openclaw/plugin-sdk/synology-chat back into the same entry. Export the setup symbols directly from the concrete setup surface so tsdown can bundle the SDK subpath without a self-referential export graph.
2026-03-17 15:27:58 -07:00
Vincent Koc
90a0d50ae9 Plugins: internalize line SDK imports 2026-03-17 15:10:20 -07:00
Vincent Koc
dcdfed995a Plugins: internalize nostr SDK imports 2026-03-17 15:08:06 -07:00
Vincent Koc
f23a069d37 Plugins: internalize synology chat SDK imports 2026-03-17 15:06:22 -07:00
Vincent Koc
681d16a892 docs(manifest): cross-reference public capability model
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 15:00:33 -07:00
Vincent Koc
77f145f1db docs(types): add JSDoc to plugin API capability registration methods
Label each registerX method with its capability type and add module-level
doc comment to channel runtime types.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 15:00:33 -07:00
Vincent Koc
6981922254 docs(plugins): replace seam terminology with capability language
Align with the decided convention: use capabilities, entry points,
and extension surfaces instead of seams.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 15:00:33 -07:00
Vincent Koc
45bfe3f44b Plugins: cover channel shape in compatibility matrix 2026-03-17 15:00:15 -07:00
Vincent Koc
7d5a90e589 Plugins: add shape compatibility matrix 2026-03-17 14:58:22 -07:00
Vincent Koc
ba09092a44 Plugins: guard internalized extension SDK imports 2026-03-17 14:54:12 -07:00
darkamenosa
b31b681088 fix(zalouser): fix setup-only onboarding flow (#49219)
* zalouser: extract shared plugin base to reduce duplication

* fix(zalouser): bump zca-js to 2.1.2 and fix state dir resolution

* fix(zalouser): allow empty allowlist during onboarding and add quickstart DM policy prompt

* fix minor review

* fix(zalouser): restore forceAllowFrom setup flow

* fix(zalouser): default group access to allowlist
2026-03-18 03:33:22 +07:00
Tak Hoffman
5a2a4abc12 CI: add built plugin singleton smoke (#48710) 2026-03-17 15:17:41 -05:00
Gustavo Madeira Santana
3d3f292f66 update contributing focus areas 2026-03-17 19:05:30 +00:00
Vincent Koc
dd7b5dc46f docs(providers): clarify provider capabilities vs public capability model
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 10:59:49 -07:00
Vincent Koc
de564689da docs(refactor): align plugin SDK plan with public capability model
Add capability plan alignment section with key decisions and required test
matrix. Rename seams to capabilities for consistency.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 10:59:49 -07:00
Vincent Koc
025bdc7e8f docs(cli): add plugins inspect command reference
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 10:59:49 -07:00
Vincent Koc
464f3da53f docs(plugins): document public capability model, plugin shapes, and inspection
Add the public capability model section documenting the six capability types,
plugin shape classification, capability labels, legacy hook guidance, export
boundary rules, and the new plugins inspect command.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 10:59:49 -07:00
Vincent Koc
8124253cdf Plugins: internalize diagnostics OTel imports 2026-03-17 10:46:08 -07:00
Vincent Koc
ff19ae1768 Plugins: internalize diffs SDK imports 2026-03-17 10:44:31 -07:00
Vincent Koc
0f56b16d47 Plugins: internalize more extension SDK imports 2026-03-17 10:42:52 -07:00
Vincent Koc
4b2aec622b Plugins: add local extension API barrels 2026-03-17 10:36:48 -07:00
Vincent Koc
0d80897476 Plugins: add inspect matrix and trim export 2026-03-17 10:33:35 -07:00
Vincent Koc
3983928958 Plugins: add inspect command and capability report 2026-03-17 10:16:06 -07:00
Ayaan Zaidi
e4825a0f93 fix(telegram): unify transport fallback chain (#49148)
* fix(telegram): unify transport fallback chain

* fix: address telegram fallback review comments

* fix: validate pinned SSRF overrides

* fix: unify telegram fallback retries (#49148)
2026-03-17 22:44:15 +05:30
Harold Hunt
272d6ed24b Plugins: add binding resolution callbacks (#48678)
Merged via squash.

Prepared head SHA: 6d7b32b184
Co-authored-by: huntharo <5617868+huntharo@users.noreply.github.com>
Co-authored-by: huntharo <5617868+huntharo@users.noreply.github.com>
Reviewed-by: @huntharo
2026-03-17 13:11:08 -04:00
Peter Steinberger
ccf16cd889 fix(gateway): clear trusted-proxy control ui scopes 2026-03-17 10:07:53 -07:00
Peter Steinberger
6d9bf6de93 refactor: narrow extension public seams 2026-03-17 09:58:33 -07:00
Peter Steinberger
bdf2c265a7 test: stabilize memory async search close 2026-03-17 16:55:19 +00:00
Peter Steinberger
6636ca87f4 docs(hooks): clarify trust model and audit guidance 2026-03-17 09:54:30 -07:00
Jonathan Jing
2145eb5908 feat(mattermost): add retry logic and timeout handling for DM channel creation (#42398)
Merged via squash.

Prepared head SHA: 3db47be907
Co-authored-by: JonathanJing <17068507+JonathanJing@users.noreply.github.com>
Co-authored-by: mukhtharcm <56378562+mukhtharcm@users.noreply.github.com>
Reviewed-by: @mukhtharcm
2026-03-17 22:16:56 +05:30
Menglin Li
7b61b025ff fix(compaction): break safeguard cancel loop for sessions with no summarizable messages (#41981) (#42215)
Merged via squash.

Prepared head SHA: 7ce6bd834e
Co-authored-by: lml2468 <39320777+lml2468@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-17 09:44:31 -07:00
Peter Steinberger
829ea70519 fix: remove duplicate setup helper imports 2026-03-17 09:38:21 -07:00
Peter Steinberger
4b125762f6 refactor: clean extension api boundaries 2026-03-17 09:38:21 -07:00
Peter Steinberger
4d8106eece docs(security): clarify wildcard Control UI origins 2026-03-17 09:36:51 -07:00
Peter Steinberger
a724bbce1a feat: add bundled Chutes extension (#49136)
* refactor: generalize bundled provider discovery seams

* feat: land chutes extension via plugin-owned auth (#41416) (thanks @Veightor)
2026-03-17 09:35:21 -07:00
Bob
ea15819ecf ACP: harden startup and move configured routing behind plugin seams (#48197)
* ACPX: keep plugin-local runtime installs out of dist

* Gateway: harden ACP startup and service PATH

* ACP: reinitialize error-state configured bindings

* ACP: classify pre-turn runtime failures as session init failures

* Plugins: move configured ACP routing behind channel seams

* Telegram tests: align startup probe assertions after rebase

* Discord: harden ACP configured binding recovery

* ACP: recover Discord bindings after stale runtime exits

* ACPX: replace dead sessions during ensure

* Discord: harden ACP binding recovery

* Discord: fix review follow-ups

* ACP bindings: load channel snapshots across workspaces

* ACP bindings: cache snapshot channel plugin resolution

* Experiments: add ACP pluginification holy grail plan

* Experiments: rename ACP pluginification plan doc

* Experiments: drop old ACP pluginification doc path

* ACP: move configured bindings behind plugin services

* Experiments: update bindings capability architecture plan

* Bindings: isolate configured binding routing and targets

* Discord tests: fix runtime env helper path

* Tests: fix channel binding CI regressions

* Tests: normalize ACP workspace assertion on Windows

* Bindings: isolate configured binding registry

* Bindings: finish configured binding cleanup

* Bindings: finish generic cleanup

* Bindings: align runtime approval callbacks

* ACP: delete residual bindings barrel

* Bindings: restore legacy compatibility

* Revert "Bindings: restore legacy compatibility"

This reverts commit ac2ed68fa2426ecc874d68278c71c71ad363fcfe.

* Tests: drop ACP route legacy helper names

* Discord/ACP: fix binding regressions

---------

Co-authored-by: Onur <2453968+osolmaz@users.noreply.github.com>
2026-03-17 17:27:52 +01:00
Kwest OG
8139f83175 fix(telegram): persist sticky IPv4 fallback across polling restarts (fixes #48177) (#48282)
* fix(telegram): persist sticky IPv4 fallback across polling restarts (fixes #48177)

Hoist resolveTelegramTransport() out of createTelegramBot() so the
transport (and its sticky IPv4 fallback state) persists across polling
restarts. Previously, each polling restart created a new transport with
stickyIpv4FallbackEnabled=false, causing repeated IPv6 timeouts on
hosts with unstable IPv6 connectivity.

Changes:
- bot.ts: accept optional telegramTransport in TelegramBotOptions
- monitor.ts: resolve transport once before polling loop
- polling-session.ts: pass transport through to bot creation

AI-assisted (Claude Sonnet 4). Tested: tsc --noEmit clean.

* Update extensions/telegram/src/polling-session.ts

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* style: fix oxfmt formatting in bot.ts

* test: cover telegram transport reuse across restarts

* fix: preserve telegram sticky IPv4 fallback across polling restarts (#48282) (thanks @yassinebkr)

---------

Co-authored-by: Yassine <yassinebkr@users.noreply.github.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-17 21:56:12 +05:30
Peter Steinberger
39a8dab0da refactor: dedupe plugin lazy runtime helpers 2026-03-17 09:24:22 -07:00
Peter Steinberger
c94beb03b2 docs(image-generation): document implicit tool enablement 2026-03-17 09:23:35 -07:00
Peter Steinberger
0aff1c7630 feat(agents): infer image generation defaults 2026-03-17 09:23:35 -07:00
Peter Steinberger
9f8cf7f71a test: stabilize full gate 2026-03-17 16:21:59 +00:00
Peter Steinberger
647fb9cc3e test: merge update cli channel cases 2026-03-17 16:21:59 +00:00
Peter Steinberger
58313fcd05 test: merge update cli restart behavior cases 2026-03-17 16:21:59 +00:00
Peter Steinberger
e3d021163c test: merge action media root cases 2026-03-17 16:21:59 +00:00
Peter Steinberger
31d739fda2 test: merge update cli validation cases 2026-03-17 16:21:59 +00:00
Peter Steinberger
c672635413 test: merge update cli outcome cases 2026-03-17 16:21:59 +00:00
Peter Steinberger
9e29511316 test: merge update cli dry run cases 2026-03-17 16:21:59 +00:00
Peter Steinberger
4a95e6529f test: merge slack validation cases 2026-03-17 16:21:59 +00:00
Peter Steinberger
6646ca61cc test: merge audit channel command hygiene cases 2026-03-17 16:21:59 +00:00
Peter Steinberger
63997aec23 test: merge audit trust exposure cases 2026-03-17 16:21:59 +00:00
Peter Steinberger
141d73ddf4 test: merge audit dangerous flag cases 2026-03-17 16:21:59 +00:00
Peter Steinberger
58c26ad706 test: merge audit code safety cases 2026-03-17 16:21:59 +00:00
Peter Steinberger
ef53926542 test: merge audit install metadata cases 2026-03-17 16:21:59 +00:00
Peter Steinberger
7866655176 test: merge audit allowCommands cases 2026-03-17 16:21:59 +00:00
Peter Steinberger
9e087f66be test: merge audit browser sandbox cases 2026-03-17 16:21:59 +00:00
Peter Steinberger
9b7aafa141 test: merge audit sandbox docker config cases 2026-03-17 16:21:59 +00:00
Peter Steinberger
23a3211c29 test: merge audit discord allowlist cases 2026-03-17 16:21:59 +00:00
Peter Steinberger
c1733d700d test: merge audit sandbox docker danger cases 2026-03-17 16:21:59 +00:00
Peter Steinberger
610d836151 test: merge audit gateway auth guardrail cases 2026-03-17 16:21:59 +00:00
Peter Steinberger
8cfcce0849 test: merge audit resolved inspection cases 2026-03-17 16:21:59 +00:00
Peter Steinberger
789730d1a3 test: merge telegram reaction id cases 2026-03-17 16:21:59 +00:00
Peter Steinberger
50c8569786 test: merge discord reaction id resolution cases 2026-03-17 16:21:59 +00:00
Peter Steinberger
c4b866855a test: merge signal reaction mapping cases 2026-03-17 16:21:59 +00:00
Peter Steinberger
253ec7452f test: merge discord action listing cases 2026-03-17 16:21:59 +00:00
Peter Steinberger
64c1fc098a test: merge command owner show gating cases 2026-03-17 16:21:59 +00:00
Peter Steinberger
37df574da0 test: merge update cli service refresh behavior 2026-03-17 16:21:59 +00:00
Peter Steinberger
59eaeaccfe test: merge command allowlist add cases 2026-03-17 16:21:59 +00:00
Peter Steinberger
7c24aab954 test: merge command config write denial cases 2026-03-17 16:21:59 +00:00
Peter Steinberger
060654e947 test: merge command hook cases 2026-03-17 16:21:59 +00:00
Peter Steinberger
48a9aa152c test: merge command approval scope cases 2026-03-17 16:21:59 +00:00
Peter Steinberger
580e00d91b test: merge command gateway config permission cases 2026-03-17 16:21:59 +00:00
Peter Steinberger
3be44b1044 test: merge update status output cases 2026-03-17 16:21:59 +00:00
Peter Steinberger
5a5a66d63d test: merge command owner gating cases 2026-03-17 16:21:59 +00:00
Peter Steinberger
f9408e57d2 test: merge slack action mapping cases 2026-03-17 16:21:59 +00:00
Peter Steinberger
c4323db30f test: merge update cli service refresh cases 2026-03-17 16:21:59 +00:00
Peter Steinberger
b7dc23b403 test: merge loader cache miss cases 2026-03-17 16:21:59 +00:00
Peter Steinberger
fb4b6eef03 test: merge audit code safety failure cases 2026-03-17 16:21:59 +00:00
Peter Steinberger
a24325f40c test: merge audit deny command cases 2026-03-17 16:21:59 +00:00
Peter Steinberger
8ab2d886eb test: merge audit windows acl cases 2026-03-17 16:21:59 +00:00
Peter Steinberger
2cfccf59c7 test: merge audit browser container cases 2026-03-17 16:21:59 +00:00
Peter Steinberger
355051f401 test: merge audit gateway auth presence cases 2026-03-17 16:21:59 +00:00
Peter Steinberger
5311d48c66 test: merge loader scoped load cases 2026-03-17 16:21:59 +00:00
Peter Steinberger
477cea7709 test: merge loader memory slot cases 2026-03-17 16:21:59 +00:00
Peter Steinberger
d49c1688f7 test: merge loader bundled telegram cases 2026-03-17 16:21:58 +00:00
Peter Steinberger
6372062be4 test: merge loader provenance warning cases 2026-03-17 16:21:58 +00:00
Peter Steinberger
97c481120f test: merge audit extension allowlist severity cases 2026-03-17 16:21:58 +00:00
Peter Steinberger
23d700b090 test: merge audit hooks ingress cases 2026-03-17 16:21:58 +00:00
Peter Steinberger
909ec6b416 test: merge loader workspace warning cases 2026-03-17 16:21:58 +00:00
Peter Steinberger
17143ed878 test: merge audit exposure heuristic cases 2026-03-17 16:21:58 +00:00
Peter Steinberger
c21654e1b9 test: merge loader precedence cases 2026-03-17 16:21:58 +00:00
Peter Steinberger
1a3bde81d8 test: merge loader single-plugin registration cases 2026-03-17 16:21:58 +00:00
Peter Steinberger
588c8be6ff test: merge audit extension and workspace cases 2026-03-17 16:21:58 +00:00
Peter Steinberger
2c073e7bcb test: merge loader http route cases 2026-03-17 16:21:58 +00:00
Peter Steinberger
d988e39fc7 test: merge loader duplicate registration cases 2026-03-17 16:21:58 +00:00
Peter Steinberger
7efa79121a test: merge install metadata audit cases 2026-03-17 16:21:58 +00:00
Peter Steinberger
bf22e9461e test: merge loader alias resolution cases 2026-03-17 16:21:58 +00:00
Peter Steinberger
444e3eb9e3 test: merge loader escape path cases 2026-03-17 16:21:58 +00:00
Peter Steinberger
790747478e test: merge loader provenance path cases 2026-03-17 16:21:58 +00:00
Peter Steinberger
85c5ec8065 test: share audit exposure severity helper 2026-03-17 16:21:58 +00:00
Peter Steinberger
167a6ebed9 test: merge gateway http audit cases 2026-03-17 16:21:58 +00:00
Peter Steinberger
4fd17021f2 test: merge hooks audit risk cases 2026-03-17 16:21:58 +00:00
Peter Steinberger
3aa76a8ce7 test: merge feishu audit doc cases 2026-03-17 16:21:58 +00:00
Peter Steinberger
7e1bc4677f test: merge control ui audit cases 2026-03-17 16:21:58 +00:00
Peter Steinberger
5f0f69b2c7 test: merge browser control audit cases 2026-03-17 16:21:58 +00:00
Peter Steinberger
2ef7b13962 test: merge channel command audit cases 2026-03-17 16:21:58 +00:00
Peter Steinberger
03b405659b test: merge audit auth precedence cases 2026-03-17 16:21:58 +00:00
Peter Steinberger
0c070ccd53 test: merge zalouser audit group cases 2026-03-17 16:21:58 +00:00
Peter Steinberger
1038990bdd test: merge discord audit allowlist cases 2026-03-17 16:21:58 +00:00
Peter Steinberger
9c086f26a0 test: merge loader setup entry matrix 2026-03-17 16:21:58 +00:00
Peter Steinberger
34460f24b8 test: merge loader cache partition cases 2026-03-17 16:21:58 +00:00
Peter Steinberger
7c3efaeccf test: merge bundle loader fixture cases 2026-03-17 16:21:58 +00:00
Peter Steinberger
61a7d856e7 test: harden commands test module seams 2026-03-17 16:21:58 +00:00
Peter Steinberger
d1df3f37a6 test: trim signal and slack action cases 2026-03-17 16:21:58 +00:00
Peter Steinberger
eef0f5bfbc test: merge tts config gating cases 2026-03-17 16:21:58 +00:00
Peter Steinberger
74cc748ff7 test: merge pid alive linux stat cases 2026-03-17 16:21:58 +00:00
Peter Steinberger
604c2636b9 test: merge message action media sandbox cases 2026-03-17 16:21:58 +00:00
Peter Steinberger
5f0c466146 test: preload inbound contract fixtures 2026-03-17 16:21:58 +00:00
Peter Steinberger
2f9e2f500f test: merge embeddings provider selection cases 2026-03-17 16:21:58 +00:00
Peter Steinberger
47a78a03a3 test: merge telegram action matrix cases 2026-03-17 16:21:58 +00:00
Peter Steinberger
dc3cb9349a test: trim lightweight status and capability suites 2026-03-17 16:21:58 +00:00
Peter Steinberger
b8861b4815 test: merge context lookup warmup cases 2026-03-17 16:21:58 +00:00
Peter Steinberger
a53de5ad51 test: cache provider discovery fixtures 2026-03-17 16:21:58 +00:00
Peter Steinberger
91f055c10e test: preload plugin sdk subpath imports 2026-03-17 16:21:58 +00:00
Peter Steinberger
40f1aad019 test: merge duplicate update cli scenarios 2026-03-17 16:21:58 +00:00
Peter Steinberger
8a9dee9ac8 test: trim redundant context engine assertions 2026-03-17 16:21:58 +00:00
Peter Steinberger
a3f09d519d test: reuse git commit module exports 2026-03-17 16:21:58 +00:00
Peter Steinberger
2b980bfcee test: reuse run-node module imports 2026-03-17 16:21:58 +00:00
Peter Steinberger
00b7308396 test: stabilize pdf tool runtime mocks 2026-03-17 16:21:58 +00:00
Peter Steinberger
63c5932e84 test: flatten twitch send mocks 2026-03-17 16:21:58 +00:00
Peter Steinberger
94a48912de test: reuse subagent orphan recovery imports 2026-03-17 16:21:58 +00:00
Peter Steinberger
9b22bd41d8 test: inline bluebubbles action mocks 2026-03-17 16:21:58 +00:00
Peter Steinberger
f2107a53cb test: remove repeated update module imports 2026-03-17 16:21:58 +00:00
Peter Steinberger
df76e0f44b test: harden CI-sensitive test suites 2026-03-17 16:21:57 +00:00
F_ool
094a0cc412 fix(context-engine): preserve legacy plugin sessionKey interop (#44779)
Merged via squash.

Prepared head SHA: e04c6fb47d
Co-authored-by: hhhhao28 <112874572+hhhhao28@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-17 09:14:14 -07:00
Peter Steinberger
ebee4e2210 fix(tlon): defer DM cite expansion until after auth 2026-03-17 09:08:20 -07:00
Peter Steinberger
e1b0e74e78 refactor: align telegram test support with plugin runtime seam 2026-03-17 09:07:05 -07:00
Peter Steinberger
795f1f438b refactor: expose lazy runtime helper to plugins 2026-03-17 08:37:11 -07:00
Jari Mustonen
4f6955fb11 fix(hooks): pass sessionFile and sessionKey in after_compaction hook (#40781)
Merged via squash.

Prepared head SHA: 11e85f8651
Co-authored-by: jarimustonen <1272053+jarimustonen@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-17 08:30:37 -07:00
Harold Hunt
f036ed27f4 CI: guard gateway watch against duplicate runtime regressions (#49048) 2026-03-17 10:55:55 -04:00
Tak Hoffman
7cd0acf8af CI: rename startup memory smoke (#49041) 2026-03-17 09:53:51 -05:00
Andrew Demczuk
f84a41dcb8 fix(security): block JVM, Python, and .NET env injection vectors in host exec sandbox (#49025)
Add JAVA_TOOL_OPTIONS, _JAVA_OPTIONS, JDK_JAVA_OPTIONS, PYTHONBREAKPOINT, and
DOTNET_STARTUP_HOOKS to blockedKeys in the host exec security policy.

Closes #22681
2026-03-17 15:37:55 +01:00
Josh Lehman
1399ca5fcb fix(plugins): forward plugin subagent overrides (#48277)
Merged via squash.

Prepared head SHA: ffa45893e0
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-17 07:20:27 -07:00
Harold Hunt
1561c6a71c tests(contracts): fix provider catalog runtime wiring (#49040) 2026-03-17 10:05:41 -04:00
huntharo
8448f48cc5 tests(feishu): inject client runtime seam 2026-03-17 09:46:58 -04:00
huntharo
3e8bf845cb tests(feishu): mock conversation runtime seam 2026-03-17 09:46:58 -04:00
huntharo
a413da9cca tests(google): inject oauth credential fs stubs 2026-03-17 09:46:58 -04:00
huntharo
4234d9b42c tests: fix googlechat outbound partial mock 2026-03-17 09:46:58 -04:00
Sally O'Malley
59cd98068f fix ssh sandbox key cp (#48924)
Signed-off-by: sallyom <somalley@redhat.com>
2026-03-17 07:22:33 -04:00
Chris Kimpton
f404ff32d5 tests: add missing useNoBundledPlugins() to bundle MCP loader test
The "treats bundle MCP as a supported bundle surface" test was missing
the useNoBundledPlugins() call present in all surrounding bundle plugin
tests. Without it, loadOpenClawPlugins() scanned and loaded the full
real bundled plugins directory on every call (with cache:false), causing
excessive memory pressure and an OOM crash on Linux CI, which manifested
as the test timing out at 120s.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 15:49:08 +05:30
Stable Genius
6b6942552d fix(macos): stop relaunching the app after quit when launch-at-login is enabled (#40213)
Merged via squash.

Prepared head SHA: c702d98bd6
Co-authored-by: stablegenius49 <259448942+stablegenius49@users.noreply.github.com>
Co-authored-by: ImLukeF <92253590+ImLukeF@users.noreply.github.com>
Reviewed-by: @ImLukeF
2026-03-17 20:59:56 +11:00
Br1an
7303253427 fix: update macOS node service to use current CLI command shape (closes #43171) (#46843)
Merged via squash.

Prepared head SHA: dbf2edd6f4
Co-authored-by: Br1an67 <29810238+Br1an67@users.noreply.github.com>
Co-authored-by: ImLukeF <92253590+ImLukeF@users.noreply.github.com>
Reviewed-by: @ImLukeF
2026-03-17 20:46:54 +11:00
stim64045-spec
6101c023bb fix(ui): restore control-ui query token compatibility (#43979)
* fix(ui): restore control-ui query token imports

* chore(changelog): add entry for openclaw#43979 thanks @stim64045-spec

---------

Co-authored-by: 大禹 <dayu@dayudeMac-mini.local>
Co-authored-by: Val Alexander <bunsthedev@gmail.com>
Co-authored-by: Val Alexander <68980965+BunsDev@users.noreply.github.com>
2026-03-17 04:03:35 -05:00
Frank Yang
6bec21bf00 chore: sync pnpm lockfile importers 2026-03-17 16:48:46 +08:00
Peter Steinberger
6bf07b5075 fix(ci): restore local check suite 2026-03-17 08:14:03 +00:00
Peter Steinberger
990d0d7261 docs(image-generation): remove nano banana stock docs 2026-03-17 01:09:58 -07:00
Peter Steinberger
0ff82497e9 test(image-generation): add live variant coverage 2026-03-17 01:09:58 -07:00
Peter Steinberger
3a456678ee feat(image-generation): add image_generate tool 2026-03-17 01:09:58 -07:00
Peter Steinberger
916db21fe5 fix(ci): harden zizmor workflow diffing 2026-03-17 08:08:33 +00:00
Vincent Koc
99c7750c2d Changelog: add Telegram DM topic session-key fix 2026-03-17 01:07:47 -07:00
Peter Steinberger
ce486292a1 test: fix discord provider helper import 2026-03-17 01:05:09 -07:00
Peter Steinberger
f9588da3e0 refactor: split plugin testing seam from bundled extension helpers 2026-03-17 01:05:09 -07:00
Peter Steinberger
527a1919ea fix(ci): quote changed extension matrix input 2026-03-17 08:04:47 +00:00
Peter Steinberger
85e610e4e7 refactor(extension-tests): share safeguard runtime assertions 2026-03-17 08:02:44 +00:00
Peter Steinberger
774b351982 refactor(failover-tests): share observation base 2026-03-17 08:02:44 +00:00
Peter Steinberger
4db3fed299 refactor(history-tests): share pruned image assertions 2026-03-17 08:02:44 +00:00
Peter Steinberger
2971c52343 refactor(payload-tests): table-drive sessions send suppressions 2026-03-17 08:02:44 +00:00
Peter Steinberger
bc36ed8e1e refactor(payload-tests): table-drive recoverable tool suppressions 2026-03-17 08:02:44 +00:00
Peter Steinberger
d46f3bd739 refactor(payload-tests): share single payload summary assertion 2026-03-17 08:02:44 +00:00
Peter Steinberger
e510132f3c refactor(skills-tests): share bundled diffs setup 2026-03-17 08:02:44 +00:00
Peter Steinberger
8c8b0ab224 refactor(runs-tests): share run handle factory 2026-03-17 08:02:44 +00:00
Peter Steinberger
b531af82d5 refactor(history-tests): share array content assertion 2026-03-17 08:02:44 +00:00
Peter Steinberger
2847ad1f8f refactor(image-tests): share ref count assertions 2026-03-17 08:02:44 +00:00
Peter Steinberger
1373821470 refactor(image-tests): share single-ref detection helper 2026-03-17 08:02:44 +00:00
Peter Steinberger
93d829b7f6 refactor(image-tests): share empty ref assertions 2026-03-17 08:02:44 +00:00
Peter Steinberger
535475e4cb refactor(payload-tests): reuse empty payload helper 2026-03-17 08:02:44 +00:00
Peter Steinberger
ec1b80809d refactor: remove remaining extension core imports 2026-03-17 00:59:46 -07:00
Peter Steinberger
9648e7fecb refactor: consolidate lazy runtime surfaces 2026-03-17 00:59:20 -07:00
Peter Steinberger
449127b474 fix: restore full gate 2026-03-17 07:47:28 +00:00
Peter Steinberger
c0e4721712 refactor(image-tests): share empty prompt image assertions 2026-03-17 07:42:45 +00:00
Peter Steinberger
7d90dff8fa refactor(model-tests): share template model mock helper 2026-03-17 07:42:45 +00:00
Peter Steinberger
9c1e9c5263 refactor(payload-tests): share empty payload helper 2026-03-17 07:42:45 +00:00
Peter Steinberger
be6716c7aa refactor(kilocode-tests): share eligibility assertions 2026-03-17 07:42:45 +00:00
Peter Steinberger
0956de7316 refactor(thinking-tests): share assistant drop helper 2026-03-17 07:42:45 +00:00
Peter Steinberger
68f3e537d3 refactor(openrouter-tests): share state dir helper 2026-03-17 07:42:45 +00:00
Peter Steinberger
bb13dd0c01 refactor(extension-tests): share safeguard factory setup 2026-03-17 07:42:45 +00:00
Peter Steinberger
58f6362921 refactor(google-tests): share schema tool fixture 2026-03-17 07:42:45 +00:00
Peter Steinberger
ef0812beff refactor(lanes-tests): share table-driven assertions 2026-03-17 07:42:45 +00:00
Peter Steinberger
38616c7c95 refactor(system-prompt-tests): share session setup helper 2026-03-17 07:42:45 +00:00
Peter Steinberger
528edce5b9 refactor(truncation-tests): share first tool result text helper 2026-03-17 07:42:45 +00:00
Peter Steinberger
e4287e0938 refactor(compaction-tests): share snapshot assertions 2026-03-17 07:42:45 +00:00
Peter Steinberger
168fa9d433 refactor(compaction-tests): share aggregate timeout params 2026-03-17 07:42:45 +00:00
Vincent Koc
1eb810a5e3 Telegram: fix named-account DM topic session keys (#48773) 2026-03-17 00:41:44 -07:00
Peter Steinberger
9053f551cb refactor(payload-tests): share empty payload assertion 2026-03-17 07:25:12 +00:00
Peter Steinberger
1843248c69 refactor(attempt-tests): share wrapped stream helper 2026-03-17 07:23:44 +00:00
Peter Steinberger
9c047c5423 refactor(kilocode-tests): share cache retention wrapper 2026-03-17 07:23:44 +00:00
Peter Steinberger
7bb36efd7b refactor(kilocode-tests): share extra-params harness 2026-03-17 07:23:44 +00:00
Peter Steinberger
1b9704df4d refactor(kilocode-tests): share reasoning payload capture 2026-03-17 07:23:44 +00:00
Peter Steinberger
5699b3dd27 refactor(heartbeat-tests): share seeded heartbeat run 2026-03-17 07:23:44 +00:00
Peter Steinberger
d698d8c5a5 refactor(media-tests): share telegram redaction assertion 2026-03-17 07:23:44 +00:00
Peter Steinberger
f8f6ae4673 refactor(apns-tests): share relay push params 2026-03-17 07:23:44 +00:00
Peter Steinberger
5747700b3c refactor(provider-tests): share codex catalog assertions 2026-03-17 07:23:44 +00:00
Peter Steinberger
201964ce6c refactor(bundle-tests): share bundle mcp fixtures 2026-03-17 07:23:44 +00:00
Peter Steinberger
e5c03ebea7 refactor(usage-tests): share provider usage loader harness 2026-03-17 07:23:44 +00:00
Peter Steinberger
282e336243 refactor(plugin-tests): share binding approval resolution 2026-03-17 07:23:44 +00:00
Peter Steinberger
c08d556ae4 refactor(plugin-tests): share interactive dispatch assertions 2026-03-17 07:23:44 +00:00
Peter Steinberger
88139c4271 refactor(contracts): share session binding assertions 2026-03-17 07:23:44 +00:00
Peter Steinberger
d08d43fb1a refactor(command-tests): share workspace harness 2026-03-17 07:23:44 +00:00
Peter Steinberger
276803095d refactor(provider-tests): share discovery catalog helpers 2026-03-17 07:23:44 +00:00
Peter Steinberger
e56e4923bd refactor(hook-tests): share subagent hook helpers 2026-03-17 07:23:44 +00:00
Peter Steinberger
52ad686ab5 refactor(runtime-tests): share typing lease assertions 2026-03-17 07:23:44 +00:00
Peter Steinberger
214c7a481c refactor(feishu-tests): share card action event builders 2026-03-17 07:23:44 +00:00
Peter Steinberger
769332c1a7 refactor(nextcloud-tests): share inbound authz setup 2026-03-17 07:23:44 +00:00
Peter Steinberger
e1ca5d9cc4 refactor(telegram-tests): share webhook settlement helper 2026-03-17 07:23:43 +00:00
Peter Steinberger
1ff10690e7 fix(telegram-tests): load plugin mocks before commands 2026-03-17 07:23:43 +00:00
Peter Steinberger
e184cd97cc refactor(telegram-tests): share native command helpers 2026-03-17 07:23:43 +00:00
Peter Steinberger
d28cb8d821 refactor(tests): share setup wizard prompter 2026-03-17 07:23:43 +00:00
Peter Steinberger
cc35627c8f fix: harden telegram and loader contracts 2026-03-17 07:17:33 +00:00
Josh Lehman
ff0481ad65 docs: fix context engine review notes 2026-03-17 00:14:51 -07:00
Josh Lehman
9887311de3 docs: address review feedback on context-engine page
- Rename 'Method' column to 'Member' with explicit Kind column since
  info is a property, not a callable method
- Document AssembleResult fields (estimatedTokens, systemPromptAddition)
  with types and optionality
- Add lifecycle timing notes for bootstrap, ingestBatch, and dispose
  so plugin authors know when each is invoked
2026-03-17 00:14:51 -07:00
Josh Lehman
315cee96b9 docs: add plugin installation steps to context engine page
Show the full workflow: install via openclaw plugins install,
enable in plugins.entries, then select in plugins.slots.contextEngine.
Uses lossless-claw as the concrete example.
2026-03-17 00:14:51 -07:00
Josh Lehman
228448e6b3 docs: add context engine documentation
Add dedicated docs page for the pluggable context engine system:
- Full lifecycle explanation (ingest, assemble, compact, afterTurn)
- Legacy engine behavior documentation
- Plugin engine authoring guide with code examples
- ContextEngine interface reference table
- ownsCompaction semantics
- Subagent lifecycle hooks (prepareSubagentSpawn, onSubagentEnded)
- systemPromptAddition mechanism
- Relationship to compaction, memory plugins, and session pruning
- Configuration reference and tips

Also:
- Add context-engine to docs nav (Agents > Fundamentals, after Context)
- Add /context-engine redirect
- Cross-link from context.md and compaction.md
2026-03-17 00:14:51 -07:00
Peter Steinberger
6f795fd60e refactor: dedupe bundled plugin entrypoints 2026-03-17 00:14:12 -07:00
Peter Steinberger
be4fdb9222 build(test): ignore vitest scratch root 2026-03-17 00:12:41 -07:00
Peter Steinberger
f8d03022cf test: cover invalid main job store load 2026-03-17 07:06:25 +00:00
Peter Steinberger
5fb7a1363f fix: stabilize full gate 2026-03-17 07:06:25 +00:00
Peter Steinberger
026d8ea534 fix: unblock full gate 2026-03-17 07:06:24 +00:00
Peter Steinberger
13505c7392 docs(changelog): restore 2026.2.27 heading 2026-03-17 00:05:42 -07:00
Peter Steinberger
e5919bc524 docs(gateway): clarify URL allowlist semantics 2026-03-17 00:03:27 -07:00
Peter Steinberger
73ca53ee02 fix: remove discord setup rebase marker 2026-03-17 00:01:17 -07:00
Peter Steinberger
3dec814fda refactor: bundle lazy runtime surfaces 2026-03-17 00:00:45 -07:00
Peter Steinberger
0d776c87c3 fix(macos): block canvas symlink escapes 2026-03-16 23:56:35 -07:00
Peter Steinberger
42c8c3c983 fix: resolve rebase type fallout in channel setup seams 2026-03-16 23:54:37 -07:00
Peter Steinberger
c1e5697889 style: fix rebase formatting drift 2026-03-16 23:52:41 -07:00
Peter Steinberger
f6868b7e42 refactor: dedupe channel entrypoints and test bridges 2026-03-16 23:52:23 -07:00
scoootscooob
80a2af1d65 Agents: move bootstrap warnings out of system prompt (#48753)
Merged via squash.

Prepared head SHA: dc1d4d075a
Co-authored-by: scoootscooob <167050519+scoootscooob@users.noreply.github.com>
Reviewed-by: @scoootscooob
2026-03-16 23:25:04 -07:00
Peter Steinberger
57204b4fa9 fix(gateway): surface env override keys in exec approvals 2026-03-16 23:24:32 -07:00
Peter Steinberger
38a6415a70 build: tighten lazy runtime boundaries 2026-03-16 23:24:17 -07:00
Peter Steinberger
e32976f8cf fix(plugin-sdk): restore core export boundary 2026-03-17 06:24:01 +00:00
Peter Steinberger
2ed5ad36ae refactor(config): share schema lookup helpers 2026-03-17 06:24:01 +00:00
Peter Steinberger
43838b1b14 refactor(device): share missing-scope helper 2026-03-17 06:24:01 +00:00
Peter Steinberger
520d753b27 refactor(usage): share legacy pi auth token lookup 2026-03-17 06:24:01 +00:00
Peter Steinberger
143530407d refactor(status): share scan helper state 2026-03-17 06:24:01 +00:00
Peter Steinberger
03c6946125 refactor(plugins): share install target flow 2026-03-17 06:24:01 +00:00
Peter Steinberger
4f5e3e1799 refactor(plugins): share claiming hook loop 2026-03-17 06:24:01 +00:00
Peter Steinberger
01c89a7985 refactor(tts): share provider readiness checks 2026-03-17 06:24:01 +00:00
Peter Steinberger
54419a826b refactor(slack): reuse shared action adapter 2026-03-17 06:24:01 +00:00
Peter Steinberger
45510084cd refactor(plugins): share bundle path list helpers 2026-03-17 06:24:01 +00:00
Peter Steinberger
c974adf10d refactor(providers): reuse simple api-key catalog helper 2026-03-17 06:24:01 +00:00
Peter Steinberger
e793e3873f refactor(whatsapp): reuse login tool implementation 2026-03-17 06:24:01 +00:00
Peter Steinberger
da9e0b658d refactor(outbound): share base session helpers 2026-03-17 06:24:01 +00:00
Peter Steinberger
4b001c7934 refactor(discord): use shared plugin base 2026-03-17 06:24:01 +00:00
Peter Steinberger
79078f6a70 refactor(setup): share env-aware patched adapters 2026-03-17 06:24:01 +00:00
Peter Steinberger
3486bff7d5 refactor(slack): share token credential setup 2026-03-17 06:24:01 +00:00
Peter Steinberger
55c52b9094 refactor(imessage): share setup status base 2026-03-17 06:24:01 +00:00
Peter Steinberger
60ee5f661f refactor(setup): reuse patched adapters across channels 2026-03-17 06:24:01 +00:00
Peter Steinberger
c9de17fc20 refactor(imessage): reuse shared setup security 2026-03-17 06:24:01 +00:00
Peter Steinberger
6a57ede661 refactor(signal): reuse shared setup security 2026-03-17 06:24:01 +00:00
Peter Steinberger
f1df31eeef refactor(discord): share setup wizard base 2026-03-17 06:24:01 +00:00
Peter Steinberger
a6bee25247 refactor(slack): share setup wizard base 2026-03-17 06:24:00 +00:00
Nimrod Gutman
2280fa0022 fix(plugins): normalize speech plugin package ids (#48777) 2026-03-17 08:21:43 +02:00
Peter Steinberger
c601dda389 docs(image-generation): document google provider 2026-03-16 23:21:16 -07:00
Peter Steinberger
618d35f933 feat(google): add image generation provider 2026-03-16 23:21:16 -07:00
Peter Steinberger
c1ef5748eb refactor: enforce scoped plugin sdk imports 2026-03-16 23:15:24 -07:00
Peter Steinberger
14d6b762fb build: remove ineffective dynamic import shims 2026-03-16 23:11:59 -07:00
Vincent Koc
efaa4dc5b3 Tests: stabilize bundled native command regressions 2026-03-16 23:01:57 -07:00
Peter Steinberger
be2e6ca0f6 fix(macos): harden exec approval socket auth 2026-03-16 23:00:22 -07:00
Peter Steinberger
2d100157bd refactor(channels): route media helpers through runtime 2026-03-16 22:58:55 -07:00
Peter Steinberger
aa2d5aaa0c feat(plugins): add image generation capability 2026-03-16 22:58:55 -07:00
Peter Steinberger
c79ade10e6 docs(plugins): add capability cookbook 2026-03-16 22:58:55 -07:00
Vincent Koc
cc88b4a72d Commands: add /plugins chat command (#48765)
* Tests: stabilize MCP config merge follow-ups

* Commands: add /plugins chat command

* Docs: add /plugins slash command guide
2026-03-16 22:57:44 -07:00
Peter Steinberger
1116ae9766 test: fix auth choice contract import 2026-03-16 22:54:00 -07:00
Peter Steinberger
00b57145ff refactor: move agent runtime into agents layer 2026-03-16 22:53:16 -07:00
Peter Steinberger
78a4d12e9a refactor: fix rebase fallout in plugin auth seams 2026-03-16 22:51:46 -07:00
Peter Steinberger
5dd2245094 refactor: restore public sdk seams after rebase 2026-03-16 22:51:46 -07:00
Peter Steinberger
f2bd76cd1a refactor: finalize plugin sdk legacy boundary cleanup 2026-03-16 22:51:46 -07:00
Vincent Koc
357ce71988 Tests: share provider registration helpers (#48767) 2026-03-16 22:50:40 -07:00
Vincent Koc
64c69c3fc9 Tests: dedupe contract helper plumbing (#48760)
* Plugins: share contract test helpers

* Channels: collapse inbound contract testkit
2026-03-16 22:45:44 -07:00
Josh Lehman
61ccc5bede chore: fix formatting drift in extension sources (#48758) 2026-03-16 22:43:21 -07:00
Vincent Koc
ac4aead8a7 Tests: order Telegram native command mocks before import 2026-03-16 22:41:39 -07:00
Peter Steinberger
0bc9c065f2 refactor: move provider auth-choice helpers into plugins 2026-03-16 22:40:33 -07:00
Vincent Koc
049bb37c62 iMessage: lazy-load channel runtime paths 2026-03-16 22:36:03 -07:00
Vincent Koc
dd9fce1686 Tests: restore Telegram native command harness mocks 2026-03-16 22:32:37 -07:00
Vincent Koc
6c866b8543 Tests: centralize contract coverage follow-ups (#48751)
* Plugins: harden global contract coverage

* Channels: tighten global contract coverage

* Channels: centralize inbound contract coverage

* Channels: move inbound contract helpers into core

* Tests: rename local inbound context checks

* Tests: stabilize contract runner profile

* Tests: split scoped contract lanes

* Channels: move inbound dispatch testkit into contracts

* Plugins: share provider contract registry helpers

* Plugins: reuse provider contract registry helpers
2026-03-16 22:26:55 -07:00
Vincent Koc
0bf11c1d69 Tests: guard channel setup import seams 2026-03-16 22:26:20 -07:00
Peter Steinberger
223ae42c79 fix(feishu): harden webhook signature compare 2026-03-16 22:22:30 -07:00
Peter Steinberger
2bbf33a9ec docs(plugins): add multi-capability ownership example 2026-03-16 22:21:18 -07:00
Peter Steinberger
dbe77d0425 fix(agents): restore embedded pi and websocket typings 2026-03-16 22:21:18 -07:00
Peter Steinberger
d2445b5fcd feat(plugins): share capability capture helpers 2026-03-16 22:21:18 -07:00
Peter Steinberger
6cbff9e7d3 refactor(imessage): share setup wizard helpers 2026-03-17 05:19:18 +00:00
Peter Steinberger
ec89357547 refactor(signal): share setup wizard helpers 2026-03-17 05:19:02 +00:00
Peter Steinberger
a1a8b74e9a refactor(nextcloud-talk): share dm policy prompt 2026-03-17 05:18:41 +00:00
Peter Steinberger
626e301502 refactor(channels): remove dead shared plugin duplicates 2026-03-17 05:18:41 +00:00
Peter Steinberger
e36f16e750 refactor(imessage): share plugin base config 2026-03-17 05:18:28 +00:00
Peter Steinberger
423f1e994e refactor(signal): share plugin base config 2026-03-17 05:18:28 +00:00
Peter Steinberger
f3da292097 refactor(slack): share plugin base config 2026-03-17 05:18:28 +00:00
Peter Steinberger
21bc5a90ec fix(slack): restore setup wizard base export 2026-03-17 05:18:28 +00:00
Peter Steinberger
e820c255bc refactor(telegram): share plugin base config 2026-03-17 05:18:16 +00:00
Peter Steinberger
7e9c46d7dd refactor(whatsapp): share plugin base config 2026-03-17 05:18:16 +00:00
Peter Steinberger
503932919f refactor(sandbox): share fs bridge path helpers 2026-03-17 05:17:52 +00:00
Peter Steinberger
1dc3104dbf fix(channels): restore shared module imports 2026-03-17 05:17:52 +00:00
Peter Steinberger
23deb3da98 refactor(discord): share native command plugin test setup 2026-03-17 05:17:52 +00:00
Peter Steinberger
7ab074631b refactor(setup): share allowlist wizard proxies 2026-03-17 05:17:52 +00:00
Peter Steinberger
5ce2ed3bd2 refactor(telegram): share native command test fixtures 2026-03-17 05:17:52 +00:00
Peter Steinberger
63d82a6299 refactor(telegram): reuse menu helpers in skill allowlist test 2026-03-17 05:17:52 +00:00
Peter Steinberger
06ae5e9d21 refactor(telegram): share native command test menu helpers 2026-03-17 05:17:51 +00:00
Peter Steinberger
b0dd757ec8 refactor(discord): share monitor provider test harness 2026-03-17 05:17:51 +00:00
Peter Steinberger
10660fe47d refactor(channels): share legacy dm allowlist paths 2026-03-17 05:17:51 +00:00
Peter Steinberger
966b8656d2 refactor(tlon): share outbound target resolution 2026-03-17 05:17:51 +00:00
Peter Steinberger
ed06d21013 refactor(providers): share template model cloning 2026-03-17 05:17:51 +00:00
Peter Steinberger
dd85ff4da7 refactor(tlon): share setup wizard base 2026-03-17 05:17:51 +00:00
Peter Steinberger
d20363bcc9 refactor(channels): remove dead shared plugin duplicates 2026-03-17 05:17:51 +00:00
Vincent Koc
7f042758b0 Sandbox: decouple built-in channel ids 2026-03-16 22:13:53 -07:00
Peter Steinberger
880bc969f9 refactor: move plugin sdk setup helpers out of commands 2026-03-16 22:11:56 -07:00
Josh Avant
da34f81ce2 fix(secrets): scope message SecretRef resolution and harden doctor/status paths (#48728)
* fix(secrets): scope message runtime resolution and harden doctor/status

* docs: align message/doctor/status SecretRef behavior notes

* test(cli): accept scoped targetIds wiring in secret-resolution coverage

* fix(secrets): keep scoped allowedPaths isolation and tighten coverage gate

* fix(secrets): avoid default-account coercion in scoped target selection

* test(doctor): cover inactive telegram secretref inspect path

* docs

Signed-off-by: joshavant <830519+joshavant@users.noreply.github.com>

* changelog

Signed-off-by: joshavant <830519+joshavant@users.noreply.github.com>

---------

Signed-off-by: joshavant <830519+joshavant@users.noreply.github.com>
2026-03-17 00:01:34 -05:00
Peter Steinberger
50c3321d2e feat(media): route image tool through media providers 2026-03-16 22:00:39 -07:00
Peter Steinberger
7fa3825e80 feat(plugins): derive bundled web search providers from plugins 2026-03-16 21:59:50 -07:00
Vincent Koc
21f5675f03 Setup: trim channel setup import cycles 2026-03-16 21:50:36 -07:00
Vincent Koc
68d2bd27c9 Plugins: reject conflicting native command aliases 2026-03-16 21:49:26 -07:00
Peter Steinberger
dde89d2a83 refactor: isolate provider sdk auth and model helpers 2026-03-16 21:47:28 -07:00
Vincent Koc
ad7924b0ac Agents: add OpenAI attribution headers (#48737) 2026-03-16 21:47:16 -07:00
Vincent Koc
06459ca0df Agents: run bundle MCP tools in embedded Pi (#48611)
* Agents: run bundle MCP tools in embedded Pi

* Plugins: fix bundle MCP path resolution

* Plugins: warn on unsupported bundle MCP transports

* Commands: add embedded Pi MCP management

* Config: move MCP management to top-level config
2026-03-16 21:46:05 -07:00
Vincent Koc
38bc364aed Runtime: narrow WhatsApp login tool surface 2026-03-16 21:39:21 -07:00
Vincent Koc
5572e6965a Agents: add provider attribution registry (#48735)
* Agents: add provider attribution registry

* Agents: record provider attribution matrix

* Agents: align OpenRouter attribution headers
2026-03-16 21:36:39 -07:00
Peter Steinberger
87b9a063ce refactor: add shared provider model definitions 2026-03-16 21:34:10 -07:00
Peter Steinberger
0cfc80b81c refactor: finish public plugin sdk boundary seams 2026-03-16 21:33:59 -07:00
Peter Steinberger
73703d977c refactor: remove onboard auth compat barrels 2026-03-16 21:33:41 -07:00
Peter Steinberger
631f6f47cf fix(extensions): restore setup and catalog tests 2026-03-16 21:31:00 -07:00
Peter Steinberger
4bba2888e7 feat(plugins): add web search runtime capability 2026-03-16 21:31:00 -07:00
Peter Steinberger
6d6825ea18 refactor: add shared provider auth modules 2026-03-16 21:21:17 -07:00
Peter Steinberger
9183081bf1 refactor: move provider auth helpers into plugin layer 2026-03-16 21:21:17 -07:00
Vincent Koc
529272d338 WhatsApp: lazy-load channel auth helpers 2026-03-16 21:19:38 -07:00
Peter Steinberger
70da383a61 test: fix rebase fallout 2026-03-16 21:18:16 -07:00
Peter Steinberger
9ebe38b6e3 refactor: untangle remaining plugin sdk boundaries 2026-03-16 21:16:32 -07:00
Peter Steinberger
afc0172cb1 docs(plugins): add capability checklist template 2026-03-16 21:13:52 -07:00
Peter Steinberger
f4fa84aea7 feat(plugins): tighten media runtime integration 2026-03-16 21:13:51 -07:00
Peter Steinberger
45cb02b1dd refactor(plugins): share MCP server map extraction 2026-03-17 04:10:36 +00:00
Peter Steinberger
08d120e706 refactor(slack): share action adapter 2026-03-17 04:10:36 +00:00
Peter Steinberger
39183746ba refactor(providers): share paired api-key catalogs 2026-03-17 04:10:36 +00:00
Peter Steinberger
0a6140acfa refactor(providers): share catalog template matcher 2026-03-17 04:10:36 +00:00
Peter Steinberger
a20b64cd92 refactor(providers): share api-key catalog helper 2026-03-17 04:10:36 +00:00
Peter Steinberger
8357372cc7 refactor(slack): share setup token credential config 2026-03-17 04:10:04 +00:00
Peter Steinberger
6a27db0cd7 refactor(outbound): share thread id normalization 2026-03-17 04:10:04 +00:00
Peter Steinberger
233ef31190 refactor(setup): reuse scoped config prelude in patched adapters 2026-03-17 04:10:03 +00:00
Peter Steinberger
4ae71485e9 refactor(setup): share scoped config prelude 2026-03-17 04:10:03 +00:00
Peter Steinberger
c51842660f refactor(setup): support account-scoped default patches 2026-03-17 04:09:49 +00:00
Peter Steinberger
78869f1517 refactor(mattermost): reuse patched setup adapter 2026-03-17 04:09:49 +00:00
Peter Steinberger
5ddbba1c70 refactor(imessage): reuse patched setup adapter 2026-03-17 04:09:49 +00:00
Peter Steinberger
387d9fa7c4 refactor(setup): reuse patched adapters in discord and signal 2026-03-17 04:09:49 +00:00
Peter Steinberger
4fd75e5fc8 refactor(setup): reuse patched adapters in slack and telegram 2026-03-17 04:09:48 +00:00
Peter Steinberger
81ef52a81e refactor(zalouser): reuse patched setup adapter 2026-03-17 04:09:48 +00:00
Peter Steinberger
7fc134d74e refactor(setup): share patched account adapters 2026-03-17 04:09:41 +00:00
Peter Steinberger
9c48321176 refactor(imessage): share setup wizard base 2026-03-17 04:09:18 +00:00
Peter Steinberger
a0e7e3c3cd refactor(discord): share plugin base config 2026-03-17 04:09:18 +00:00
Peter Steinberger
b058077b16 refactor(telegram): share setup wizard base 2026-03-17 04:09:15 +00:00
Peter Steinberger
a3474dda33 refactor(discord): share setup wizard base 2026-03-17 04:09:15 +00:00
Peter Steinberger
4f7ee60a8f refactor(setup): import docs helpers directly 2026-03-17 04:09:15 +00:00
Peter Steinberger
6d6e08b147 refactor(signal): share setup wizard base 2026-03-17 04:09:15 +00:00
Peter Steinberger
7758873d7e refactor(slack): share setup wizard base 2026-03-17 04:09:15 +00:00
Peter Steinberger
c3571d982d refactor(nextcloud-talk): share setup allowlist prompt 2026-03-17 04:09:15 +00:00
Peter Steinberger
31a8225951 refactor(imessage): share plugin base config 2026-03-17 04:09:15 +00:00
Peter Steinberger
a8853d23ef refactor(signal): share plugin base config 2026-03-17 04:09:11 +00:00
Peter Steinberger
3cc1c7ba83 refactor(telegram): share plugin base config 2026-03-17 04:09:05 +00:00
Peter Steinberger
ba79d90313 refactor(whatsapp): share plugin base config 2026-03-17 04:08:58 +00:00
Peter Steinberger
75b8117f83 refactor(slack): share plugin base config 2026-03-17 04:08:49 +00:00
Vincent Koc
f90d432de3 Plugins: honor native command aliases at dispatch 2026-03-16 21:02:08 -07:00
Peter Steinberger
095a9f6e1d fix: handle Parallels poweroff snapshot restores 2026-03-17 04:01:19 +00:00
Peter Steinberger
71a79bdf5c docs(plugins): document media understanding runtime 2026-03-16 20:58:34 -07:00
Peter Steinberger
c081dc52b7 feat(plugins): move media understanding into vendor plugins 2026-03-16 20:58:34 -07:00
Vincent Koc
e064c1198e Zalo: lazy-load channel runtime paths 2026-03-16 20:58:10 -07:00
Peter Steinberger
c64f6adc83 refactor: finish provider auth extraction and canonicalize kimi 2026-03-16 20:49:38 -07:00
Peter Steinberger
3566e88c08 docs(plugins): document media capability ownership 2026-03-16 20:42:08 -07:00
Peter Steinberger
3e010e280a feat(plugins): add media understanding provider registration 2026-03-16 20:42:00 -07:00
Peter Steinberger
14907d3de0 docs(plugins): note richer voice metadata 2026-03-16 20:27:34 -07:00
Peter Steinberger
57f1ab1fca feat(tts): enrich speech voice metadata 2026-03-16 20:27:34 -07:00
Ayaan Zaidi
5f5b409fe9 fix: remove duplicate whatsapp dm policy import 2026-03-17 08:56:11 +05:30
Peter Steinberger
5602973b5d docs(plugins): add capability contract example 2026-03-16 20:24:13 -07:00
Peter Steinberger
622f13253b feat(tts): add microsoft voice listing 2026-03-16 20:24:13 -07:00
Peter Steinberger
a71c61122d refactor: add plugin sdk setup entrypoint 2026-03-16 20:17:45 -07:00
Peter Steinberger
2497b8147e refactor: add shared setup sdk subpath 2026-03-16 20:17:13 -07:00
Peter Steinberger
77d6274624 docs: rename kimi coding package description 2026-03-16 20:17:01 -07:00
Peter Steinberger
03f50365d7 refactor: rename kimi coding surface to kimi 2026-03-16 20:17:01 -07:00
Peter Steinberger
763eff8b32 refactor: move plugin-specific config into extensions 2026-03-16 20:17:01 -07:00
Peter Steinberger
2182137bde refactor: move gateway onboarding into extensions 2026-03-16 20:17:00 -07:00
Peter Steinberger
f6d3aaa442 refactor: move remaining provider onboarding into extensions 2026-03-16 20:17:00 -07:00
Peter Steinberger
7df0ced8ac refactor: move provider onboarding into extensions 2026-03-16 20:17:00 -07:00
Peter Steinberger
5a763ac57b fix: restore check after upstream type drift 2026-03-16 20:17:00 -07:00
Peter Steinberger
683be73d54 refactor: point onboarding provider config to extensions 2026-03-16 20:17:00 -07:00
Peter Steinberger
fe4368cbca fix: align thinking defaults and plugin sdk exports 2026-03-17 03:16:39 +00:00
Peter Steinberger
1ffe8fde84 fix: stabilize docker test suite 2026-03-17 03:02:03 +00:00
Peter Steinberger
ed248c76c7 docs(plugins): document speech runtime ownership 2026-03-16 20:01:24 -07:00
Peter Steinberger
85781353ec feat(plugins): expand speech runtime ownership 2026-03-16 20:01:24 -07:00
Peter Steinberger
7c2c20a62f refactor: untangle bundled channel sdk bridges 2026-03-16 19:58:23 -07:00
Keshav Rao
3aa4199ef0 agent: preemptive context overflow detection during tool loops (#29371)
Merged via squash.

Prepared head SHA: 19661b8fb1
Co-authored-by: keshav55 <3821985+keshav55@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-16 19:04:00 -07:00
lishuaigit
76500c7a78 fix: detect Ollama "prompt too long" as context overflow error (#34019)
Merged via squash.

Prepared head SHA: 825a402f0f
Co-authored-by: lishuaigit <7495165+lishuaigit@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-16 18:57:33 -07:00
Peter Steinberger
6da9ba3267 docs(plugins): document capability ownership model 2026-03-16 18:50:09 -07:00
Peter Steinberger
662031a88e feat(plugins): add speech provider registration 2026-03-16 18:50:09 -07:00
Vincent Koc
ad05cd9ab2 Tests: document Discord plugin auth gating 2026-03-16 18:45:31 -07:00
Vincent Koc
029f5d6427 Tlon: lazy-load channel runtime paths 2026-03-16 18:44:35 -07:00
Peter Steinberger
b230e524a5 refactor(whatsapp): reuse shared normalize helpers 2026-03-17 01:43:56 +00:00
Peter Steinberger
1c0db5b8e4 refactor(slack): share setup helpers 2026-03-17 01:43:56 +00:00
Vincent Koc
e88c6d8486 Tests: cover Telegram plugin auth on real registry 2026-03-16 18:43:05 -07:00
Vincent Koc
9c80d717bc Tests: pin loader command activation semantics 2026-03-16 18:40:50 -07:00
Vincent Koc
7959be4336 Tests: cover Discord provider plugin registry 2026-03-16 18:38:11 -07:00
Vincent Koc
6805a80da2 Tests: lock plugin slash commands to one runtime graph 2026-03-16 18:38:11 -07:00
Peter Steinberger
8a10903cf7 test: fix check contract type drift 2026-03-16 18:37:58 -07:00
Peter Steinberger
e554eee541 refactor: route bundled channel setup helpers through private sdk bridges 2026-03-16 18:35:20 -07:00
Peter Steinberger
6c1433a3c0 refactor: move provider catalogs into extensions 2026-03-16 18:33:07 -07:00
Vincent Koc
0a93e22b37 Plugins: fix catalog contract mocks 2026-03-16 18:02:46 -07:00
Vincent Koc
4194bba575 Plugins: speed up auth-choice contracts 2026-03-16 17:59:39 -07:00
Vincent Koc
8b2f0cbb6c CI: run global contract lane 2026-03-16 17:59:39 -07:00
Vincent Koc
02df22a495 Tests: improve extension runner discovery 2026-03-16 17:59:39 -07:00
Vincent Koc
0f013575f8 Channels: add global threading and directory contracts 2026-03-16 17:59:39 -07:00
Vincent Koc
750ce393bc Plugins: stabilize global catalog contracts 2026-03-16 17:59:39 -07:00
Harold Hunt
94c27f34a1 fix(plugins): keep built plugin loading on one module graph (#48595) 2026-03-16 20:58:58 -04:00
Tak Hoffman
4863b651c6 docs: rename onboarding user-facing wizard copy
Co-authored-by: Tak <contact-redacted@example.com>
2026-03-16 19:50:31 -05:00
Clayton Shaw
6ba4d0ddc3 fix: remove orphaned tool_result blocks during compaction (#15691) (#16095)
Merged via squash.

Prepared head SHA: b772432c1f
Co-authored-by: claw-sylphx <260243939+claw-sylphx@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-16 15:57:45 -07:00
Tak Hoffman
313e5bb58b Fix launcher startup regressions (#48501)
* Fix launcher startup regressions

* Fix CI follow-up regressions

* Fix review follow-ups

* Fix workflow audit shell inputs

* Handle require resolve gaxios misses
2026-03-16 17:21:18 -05:00
Sayr Wolfridge
a53030a7f2 fix(compaction): stabilize toolResult trim/prune flow in safeguard (#44133)
Merged via squash.

Prepared head SHA: ec789c66ec
Co-authored-by: SayrWolfridge <267323413+SayrWolfridge@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-16 15:02:49 -07:00
sparkyrider
10ef58dd69 fix(whatsapp): restore implicit reply mentions for LID identities (#48494)
Threads selfLid from the Baileys socket through the inbound WhatsApp
pipeline and adds LID-format matching to the implicit mention check
in group gating, so reply-to-bot detection works when WhatsApp sends
the quoted sender in @lid format.

Also fixes the device-suffix stripping regex (was a silent no-op).

Closes #23029

Co-authored-by: sparkyrider <sparkyrider@users.noreply.github.com>
Reviewed-by: @ademczuk
2026-03-16 22:44:35 +01:00
Val Alexander
2ab25babce fix(ui): align chatStream lifecycle type with nullable state 2026-03-16 16:35:11 -05:00
Jaewon Hwang
04985dab23 fix: enable auto-scroll during assistant response streaming
Fix auto-scroll behavior when AI assistant streams responses in the web UI.
Previously, the viewport would remain at the sent message position and users
had to manually click a badge to see streaming responses.

Fixes #14959

Changes:
- Reset chat scroll state before sending message to ensure viewport readiness
- Force scroll to bottom after message send to position viewport correctly
- Detect streaming start (chatStream: null -> string) and trigger auto-scroll
- Ensure smooth scroll-following during entire streaming response

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 16:35:11 -05:00
Josh Lehman
eeb140b4f0 fix(plugins): late-binding subagent runtime for non-gateway load paths (#46648)
Merged via squash.

Prepared head SHA: 44742652c9
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-16 14:27:54 -07:00
git-jxj
abce640772 fix(ui): language dropdown selection not persisting after refresh (#48019)
Merged via squash.

Prepared head SHA: 06c82586d9
Co-authored-by: git-jxj <65210887+git-jxj@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-17 00:03:48 +03:00
Tak Hoffman
2de28379dd Plugins: remove public extension-api surface (#48462)
* Plugins: remove public extension-api surface

* Plugins: fix loader setup routing follow-ups

* CI: ignore non-extension helper dirs in extension-fast

* Docs: note extension-api removal as breaking
2026-03-16 15:51:08 -05:00
Altay
412811ec19 fix(changelog): add entry for Control UI logger import fix (#48469)
* fix(changelog): note Control UI logger import fix

* fix(changelog): attribute Control UI logger fix entry

* fix(changelog): credit original Control UI fix author
2026-03-16 23:17:12 +03:00
Altay
df3a19051d fix(logging): make logger import browser-safe 2026-03-16 23:08:21 +03:00
Gustavo Madeira Santana
546e4d940a Build: share root dist chunks across tsdown entries 2026-03-16 16:43:47 +00:00
Gustavo Madeira Santana
09df232f39 Plugins: stage local bundled runtime tree 2026-03-16 16:43:47 +00:00
Ayaan Zaidi
7e2658908d perf: lazy-load status route startup helpers 2026-03-16 22:07:59 +05:30
Ayaan Zaidi
97a7dcf48e perf: reduce status json startup memory 2026-03-16 21:51:24 +05:30
Gustavo Madeira Santana
2c3c48fd8d Channels: ignore enabled-only disabled plugin config 2026-03-16 15:55:06 +00:00
Vincent Koc
4649f82b77 Docs: normalize unreleased changelog refs 2026-03-16 08:39:05 -07:00
Vincent Koc
c28a52263b Docs: repair unreleased changelog attribution 2026-03-16 08:36:27 -07:00
Gustavo Madeira Santana
8a226fffb4 Infra: ignore ciao probing cancellations 2026-03-16 15:26:47 +00:00
Gustavo Madeira Santana
13894ec5aa Gateway tests: share ordered client teardown helper 2026-03-16 14:36:04 +00:00
Gustavo Madeira Santana
d352be8e99 Gateway tests: centralize mock responses provider setup 2026-03-16 14:36:04 +00:00
Ayaan Zaidi
ce1d95454f test: fix stale web search and boot-md contracts 2026-03-16 20:04:30 +05:30
Gustavo Madeira Santana
771fbeae79 Gateway: simplify startup and stabilize mock responses tests 2026-03-16 14:32:55 +00:00
Hung-Che Lo
f8bcfb9d73 feat(skills): preserve all skills in prompt via compact fallback before dropping (#47553)
* feat(skills): add compact format fallback for skill catalog truncation

When the full-format skill catalog exceeds the character budget,
applySkillsPromptLimits now tries a compact format (name + location
only, no description) before binary-searching for the largest fitting
prefix. This preserves full model awareness of registered skills in
the common overflow case.

Three-tier strategy:
1. Full format fits → use as-is
2. Compact format fits → switch to compact, keep all skills
3. Compact still too large → binary search largest compact prefix

Other changes:
- escapeXml() utility for safe XML attribute values
- formatSkillsCompact() emits same XML structure minus <description>
- Compact char-budget check reserves 150 chars for the warning line
  the caller prepends, preventing prompt overflow at the boundary
- 13 tests covering all tiers, edge cases, and budget reservation
- docs/.generated/config-baseline.json: fix pre-existing oxfmt issue

* docs: document compact skill prompt fallback

---------

Co-authored-by: Frank Yang <frank.ekn@gmail.com>
2026-03-16 22:12:15 +08:00
Gustavo Madeira Santana
1f1a93a1dc Docs: document deferred channel startup opt-in 2026-03-16 14:03:25 +00:00
Gustavo Madeira Santana
96ed010a37 Gateway: gate deferred channel startup behind opt-in 2026-03-16 13:55:53 +00:00
Gustavo Madeira Santana
1b234b910b Gateway: defer full channel plugins until after listen 2026-03-16 13:31:20 +00:00
Gustavo Madeira Santana
541e697554 Plugins: share channel plugin id resolution 2026-03-16 13:31:20 +00:00
Ayaan Zaidi
4337b1eba5 docs(config): refresh generated baseline 2026-03-16 18:58:32 +05:30
5791 changed files with 442710 additions and 161643 deletions

View File

@@ -0,0 +1,87 @@
---
name: openclaw-ghsa-maintainer
description: Maintainer workflow for OpenClaw GitHub Security Advisories (GHSA). Use when Codex needs to inspect, patch, validate, or publish a repo advisory, verify private-fork state, prepare advisory Markdown or JSON payloads safely, handle GHSA API-specific publish constraints, or confirm advisory publish success.
---
# OpenClaw GHSA Maintainer
Use this skill for repo security advisory workflow only. Keep general release work in `openclaw-release-maintainer`.
## Respect advisory guardrails
- Before reviewing or publishing a repo advisory, read `SECURITY.md`.
- Ask permission before any publish action.
- Treat this skill as GHSA-only. Do not use it for stable or beta release work.
## Fetch and inspect advisory state
Fetch the current advisory and the latest published npm version:
```bash
gh api /repos/openclaw/openclaw/security-advisories/<GHSA>
npm view openclaw version --userconfig "$(mktemp)"
```
Use the fetch output to confirm the advisory state, linked private fork, and vulnerability payload shape before patching.
## Verify private fork PRs are closed
Before publishing, verify that the advisory's private fork has no open PRs:
```bash
fork=$(gh api /repos/openclaw/openclaw/security-advisories/<GHSA> | jq -r .private_fork.full_name)
gh pr list -R "$fork" --state open
```
The PR list must be empty before publish.
## Prepare advisory Markdown and JSON safely
- Write advisory Markdown via heredoc to a temp file. Do not use escaped `\n` strings.
- Build PATCH payload JSON with `jq`, not hand-escaped shell JSON.
Example pattern:
```bash
cat > /tmp/ghsa.desc.md <<'EOF'
<markdown description>
EOF
jq -n --rawfile desc /tmp/ghsa.desc.md \
'{summary,severity,description:$desc,vulnerabilities:[...]}' \
> /tmp/ghsa.patch.json
```
## Apply PATCH calls in the correct sequence
- Do not set `severity` and `cvss_vector_string` in the same PATCH call.
- Use separate calls when the advisory requires both fields.
- Publish by PATCHing the advisory and setting `"state":"published"`. There is no separate `/publish` endpoint.
Example shape:
```bash
gh api -X PATCH /repos/openclaw/openclaw/security-advisories/<GHSA> \
--input /tmp/ghsa.patch.json
```
## Publish and verify success
After publish, re-fetch the advisory and confirm:
- `state=published`
- `published_at` is set
- the description does not contain literal escaped `\\n`
Verification pattern:
```bash
gh api /repos/openclaw/openclaw/security-advisories/<GHSA>
jq -r .description < /tmp/ghsa.refetch.json | rg '\\\\n'
```
## Common GHSA footguns
- Publishing fails with HTTP 422 if required fields are missing or the private fork still has open PRs.
- A payload that looks correct in shell can still be wrong if Markdown was assembled with escaped newline strings.
- Advisory PATCH sequencing matters; separate field updates when GHSA API constraints require it.

View File

@@ -0,0 +1,87 @@
---
name: openclaw-parallels-smoke
description: End-to-end Parallels smoke, upgrade, and rerun workflow for OpenClaw across macOS, Windows, and Linux guests. Use when Codex needs to run, rerun, debug, or interpret VM-based install, onboarding, gateway smoke tests, latest-release-to-main upgrade checks, fresh snapshot retests, or optional Discord roundtrip verification under Parallels.
---
# OpenClaw Parallels Smoke
Use this skill for Parallels guest workflows and smoke interpretation. Do not load it for normal repo work.
## Global rules
- Use the snapshot most closely matching the requested fresh baseline.
- Gateway verification in smoke runs should use `openclaw gateway status --deep --require-rpc` unless the stable version being checked does not support it yet.
- Stable `2026.3.12` pre-upgrade diagnostics may require a plain `gateway status --deep` fallback.
- Treat `precheck=latest-ref-fail` on that stable pre-upgrade lane as baseline, not automatically a regression.
- Pass `--json` for machine-readable summaries.
- Per-phase logs land under `/tmp/openclaw-parallels-*`.
- Do not run local and gateway agent turns in parallel on the same fresh workspace or session.
- For `prlctl exec`, pass the VM name before `--current-user` (`prlctl exec "$VM" --current-user ...`), not the other way around.
- If the workflow installs OpenClaw from a repo checkout instead of the site installer/npm release, finish by installing a real guest CLI shim and verifying it in a fresh guest shell. `pnpm openclaw ...` inside the repo is not enough for handoff parity.
- On macOS guests, prefer a user-global install plus a stable PATH-visible shim:
- install with `NPM_CONFIG_PREFIX="$HOME/.npm-global" npm install -g .`
- make sure `~/.local/bin/openclaw` exists or `~/.npm-global/bin` is on PATH
- verify from a brand-new guest shell with `which openclaw` and `openclaw --version`
## npm install then update
- Preferred entrypoint: `pnpm test:parallels:npm-update`
- Flow: fresh snapshot -> install npm package baseline -> smoke -> install current main tgz on the same guest -> smoke again.
- Same-guest update verification should set the default model explicitly to `openai/gpt-5.4` before the agent turn and use a fresh explicit `--session-id` so old session model state does not leak into the check.
- The aggregate npm-update wrapper must resolve the Linux VM with the same Ubuntu fallback policy as `parallels-linux-smoke.sh` before both fresh and update lanes. On Peter's current host, missing `Ubuntu 24.04.3 ARM64` should fall back to `Ubuntu 25.10`.
- On Windows same-guest update checks, restart the gateway after the npm upgrade before `gateway status` / `agent`; in-place global npm updates can otherwise leave stale hashed `dist/*` module imports alive in the running service.
- For Windows same-guest update checks, prefer the done-file/log-drain PowerShell runner pattern over one long-lived `prlctl exec ... powershell -EncodedCommand ...` transport. The guest can finish successfully while the outer `prlctl exec` still hangs.
- Linux same-guest update verification should also export `HOME=/root`, pass `OPENAI_API_KEY` via `prlctl exec ... /usr/bin/env`, and use `openclaw agent --local`; the fresh Linux baseline does not rely on persisted gateway credentials.
## CLI invocation footgun
- The Parallels smoke shell scripts should tolerate a literal bare `--` arg so `pnpm test:parallels:* -- --json` and similar forwarded invocations work without needing to call `bash scripts/e2e/...` directly.
## macOS flow
- Preferred entrypoint: `pnpm test:parallels:macos`
- Default to the snapshot closest to `macOS 26.3.1 latest`.
- On Peter's Tahoe VM, `fresh-latest-march-2026` can hang in `prlctl snapshot-switch`; if restore times out there, rerun with `--snapshot-hint 'macOS 26.3.1 latest'` before blaming auth or the harness.
- The macOS smoke should include a dashboard load phase after gateway health: resolve the tokenized URL with `openclaw dashboard --no-open`, verify the served HTML contains the Control UI title/root shell, then open Safari and require an established localhost TCP connection from Safari to the gateway port.
- `prlctl exec` is fine for deterministic repo commands, but use the guest Terminal or `prlctl enter` when installer parity or shell-sensitive behavior matters.
- Multi-word `openclaw agent --message ...` checks should go through a guest shell wrapper (`guest_current_user_sh` / `guest_current_user_cli` or `/bin/sh -lc ...`), not raw `prlctl exec ... node openclaw.mjs ...`, or the message can be split into extra argv tokens and Commander reports `too many arguments for 'agent'`.
- On the fresh Tahoe snapshot, `brew` exists but `node` may be missing from PATH in noninteractive exec. Use `/opt/homebrew/bin/node` when needed.
- Fresh host-served tgz installs should install as guest root with `HOME=/var/root`, then run onboarding as the desktop user via `prlctl exec --current-user`.
- Root-installed tgz smoke can log plugin blocks for world-writable `extensions/*`; do not treat that as an onboarding or gateway failure unless plugin loading is the task.
## Windows flow
- Preferred entrypoint: `pnpm test:parallels:windows`
- Use the snapshot closest to `pre-openclaw-native-e2e-2026-03-12`.
- Always use `prlctl exec --current-user`; plain `prlctl exec` lands in `NT AUTHORITY\\SYSTEM`.
- Prefer explicit `npm.cmd` and `openclaw.cmd`.
- Use PowerShell only as the transport with `-ExecutionPolicy Bypass`, then call the `.cmd` shims from inside it.
- Multi-word `openclaw agent --message ...` checks should call `& $openclaw ...` inside PowerShell, not `Start-Process ... -ArgumentList` against `openclaw.cmd`, or Commander can see split argv and throw `too many arguments for 'agent'`.
- Windows installer/tgz phases now retry once after guest-ready recheck; keep new Windows smoke steps idempotent so a transport-flake retry is safe.
- Windows global `npm install -g` phases can stay quiet for a minute or more even when healthy; inspect the phase log before calling it hung, and only treat it as a regression once the retry wrapper or timeout trips.
- Keep onboarding and status output ASCII-clean in logs; fancy punctuation becomes mojibake in current capture paths.
- If you hit an older run with `rc=255` plus an empty `fresh.install-main.log` or `upgrade.install-main.log`, treat it as a likely `prlctl exec` transport drop after guest start-up, not immediate proof of an npm/package failure.
## Linux flow
- Preferred entrypoint: `pnpm test:parallels:linux`
- Use the snapshot closest to fresh `Ubuntu 24.04.3 ARM64`.
- If that exact VM is missing on the host, fall back to the closest Ubuntu guest with a fresh poweroff snapshot. On Peter's host today, that is `Ubuntu 25.10`.
- Use plain `prlctl exec`; `--current-user` is not the right transport on this snapshot.
- Fresh snapshots may be missing `curl`, and `apt-get update` can fail on clock skew. Bootstrap with `apt-get -o Acquire::Check-Date=false update` and install `curl ca-certificates`.
- Fresh `main` tgz smoke still needs the latest-release installer first because the snapshot has no Node or npm before bootstrap.
- This snapshot does not have a usable `systemd --user` session; managed daemon install is unsupported.
- `prlctl exec` reaps detached Linux child processes on this snapshot, so detached background gateway runs are not trustworthy smoke signals.
- Treat `gateway=skipped-no-detached-linux-gateway` plus `daemon=systemd-user-unavailable` as baseline on that Linux lane, not a regression.
## Discord roundtrip
- Discord roundtrip is optional and should be enabled with:
- `--discord-token-env`
- `--discord-guild-id`
- `--discord-channel-id`
- Keep the Discord token only in a host env var.
- Use installed `openclaw message send/read`, not `node openclaw.mjs message ...`.
- Set `channels.discord.guilds` as one JSON object, not dotted config paths with snowflakes.
- Avoid long `prlctl enter` or expect-driven Discord config scripts; prefer `prlctl exec --current-user /bin/sh -lc ...` with short commands.
- For a narrower macOS-only Discord proof run, the existing `parallels-discord-roundtrip` skill is the deep-dive companion.

View File

@@ -0,0 +1,75 @@
---
name: openclaw-pr-maintainer
description: Maintainer workflow for reviewing, triaging, preparing, closing, or landing OpenClaw pull requests and related issues. Use when Codex needs to validate bug-fix claims, search for related issues or PRs, apply or recommend close/reason labels, prepare GitHub comments safely, check review-thread follow-up, or perform maintainer-style PR decision making before merge or closure.
---
# OpenClaw PR Maintainer
Use this skill for maintainer-facing GitHub workflow, not for ordinary code changes.
## Apply close and triage labels correctly
- If an issue or PR matches an auto-close reason, apply the label and let `.github/workflows/auto-response.yml` handle the comment/close/lock flow.
- Do not manually close plus manually comment for these reasons.
- `r:*` labels can be used on both issues and PRs.
- Current reasons:
- `r: skill`
- `r: support`
- `r: no-ci-pr`
- `r: too-many-prs`
- `r: testflight`
- `r: third-party-extension`
- `r: moltbook`
- `r: spam`
- `invalid`
- `dirty` for PRs only
## Enforce the bug-fix evidence bar
- Never merge a bug-fix PR based only on issue text, PR text, or AI rationale.
- Before landing, require:
1. symptom evidence such as a repro, logs, or a failing test
2. a verified root cause in code with file/line
3. a fix that touches the implicated code path
4. a regression test when feasible, or explicit manual verification plus a reason no test was added
- If the claim is unsubstantiated or likely wrong, request evidence or changes instead of merging.
- If the linked issue appears outdated or incorrect, correct triage first. Do not merge a speculative fix.
## Handle GitHub text safely
- For issue comments and PR comments, use literal multiline strings or `-F - <<'EOF'` for real newlines. Never embed `\n`.
- Do not use `gh issue/pr comment -b "..."` when the body contains backticks or shell characters. Prefer a single-quoted heredoc.
- Do not wrap issue or PR refs like `#24643` in backticks when you want auto-linking.
- PR landing comments should include clickable full commit links for landed and source SHAs when present.
## Search broadly before deciding
- Prefer targeted keyword search before proposing new work or closing something as duplicate.
- Use `--repo openclaw/openclaw` with `--match title,body` first.
- Add `--match comments` when triaging follow-up discussion.
- Do not stop at the first 500 results when the task requires a full search.
Examples:
```bash
gh search prs --repo openclaw/openclaw --match title,body --limit 50 -- "auto-update"
gh search issues --repo openclaw/openclaw --match title,body --limit 50 -- "auto-update"
gh search issues --repo openclaw/openclaw --match title,body --limit 50 \
--json number,title,state,url,updatedAt -- "auto update" \
--jq '.[] | "\(.number) | \(.state) | \(.title) | \(.url)"'
```
## Follow PR review and landing hygiene
- If bot review conversations exist on your PR, address them and resolve them yourself once fixed.
- Leave a review conversation unresolved only when reviewer or maintainer judgment is still needed.
- When landing or merging any PR, follow the global `/landpr` process.
- Use `scripts/committer "<msg>" <file...>` for scoped commits instead of manual `git add` and `git commit`.
- Keep commit messages concise and action-oriented.
- Group related changes; avoid bundling unrelated refactors.
- Use `.github/pull_request_template.md` for PR submissions and `.github/ISSUE_TEMPLATE/` for issues.
## Extra safety
- If a close or reopen action would affect more than 5 PRs, ask for explicit confirmation with the exact count and target query first.
- `sync` means: if the tree is dirty, commit all changes with a sensible Conventional Commit message, then `git pull --rebase`, then `git push`. Stop if rebase conflicts cannot be resolved safely.

View File

@@ -0,0 +1,237 @@
---
name: openclaw-release-maintainer
description: Maintainer workflow for OpenClaw releases, prereleases, changelog release notes, and publish validation. Use when Codex needs to prepare or verify stable or beta release steps, align version naming, assemble release notes, check release auth requirements, or validate publish-time commands and artifacts.
---
# OpenClaw Release Maintainer
Use this skill for release and publish-time workflow. Keep ordinary development changes and GHSA-specific advisory work outside this skill.
## Respect release guardrails
- Do not change version numbers without explicit operator approval.
- Ask permission before any npm publish or release step.
- 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.
## Keep release channel naming aligned
- `stable`: tagged releases only, with npm dist-tag `latest`
- `beta`: prerelease tags like `vYYYY.M.D-beta.N`, with npm dist-tag `beta`
- Prefer `-beta.N`; do not mint new `-1` or `-2` beta suffixes
- `dev`: moving head on `main`
- When using a beta Git tag, publish npm with the matching beta version suffix so the plain version is not consumed or blocked
## Handle versions and release files consistently
- Version locations include:
- `package.json`
- `apps/android/app/build.gradle.kts`
- `apps/ios/Sources/Info.plist`
- `apps/ios/Tests/Info.plist`
- `apps/macos/Sources/OpenClaw/Resources/Info.plist`
- `docs/install/updating.md`
- Peekaboo Xcode project and plist version fields
- Before creating a release tag, make every version location above match the version encoded by that tag.
- 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.
- 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
`appcast.xml` unless a separate beta feed exists.
- For fallback correction tags like `vYYYY.M.D-N`, the repo version still stays
at `YYYY.M.D`, but the mac release must use a strictly higher numeric
`APP_BUILD` / Sparkle build than the original release so existing installs
see it as newer.
## Build changelog-backed release notes
- Changelog entries should be user-facing, not internal release-process notes.
- 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
- 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
## Run publish-time validation
Before tagging or publishing, run:
```bash
node --import tsx scripts/release-check.ts
pnpm release:check
pnpm test:install:smoke
```
For a non-root smoke path:
```bash
OPENCLAW_INSTALL_SMOKE_SKIP_NONROOT=1 pnpm test:install:smoke
```
After npm publish, run:
```bash
node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
```
- This verifies the published registry install path in a fresh temp prefix.
- For stable correction releases like `YYYY.M.D-N`, it also verifies the
upgrade path from `YYYY.M.D` to `YYYY.M.D-N` so a correction publish cannot
silently leave existing global installs on the old base stable payload.
## Check all relevant release builds
- Always validate the OpenClaw npm release path before creating the tag.
- Default release checks:
- `pnpm check`
- `pnpm build`
- `node --import tsx scripts/release-check.ts`
- `pnpm release:check`
- `OPENCLAW_INSTALL_SMOKE_SKIP_NONROOT=1 pnpm test:install:smoke`
- Check all release-related build surfaces touched by the release, not only the npm package.
- Include mac release readiness in preflight by running the public validation
workflow in `openclaw/openclaw` and the real mac preflight in
`openclaw/releases-private` for every release.
- Treat the `appcast.xml` update on `main` as part of mac release readiness, not an optional follow-up.
- The workflows remain tag-based. The agent is responsible for making sure
preflight runs complete successfully before any publish run starts.
- 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.
- 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
generate replacement `appcast.xml` files from the same stale seed.
- For stable releases, confirm the latest beta already passed the broader release workflows before cutting stable.
- If any required build, packaging step, or release workflow is red, do not say the release is ready.
## Use the right auth flow
- OpenClaw publish uses GitHub trusted publishing.
- 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
public release assets.
- The private mac workflow also accepts `smoke_test_only=true` for branch-safe
workflow smoke tests that use ad-hoc signing, skip notarization, skip shared
appcast generation, and do not prove release readiness.
- `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.
- `.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 does not build or publish macOS artifacts.
- Real mac preflight and real mac publish both use
`openclaw/releases-private/.github/workflows/openclaw-macos-publish.yml`.
- The private mac workflow runs on GitHub's xlarge macOS runner and uses a
SwiftPM cache because the Swift build/test/package path is CPU-heavy.
- Private mac preflight uploads notarized build artifacts as workflow artifacts
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, 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.
- 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.
- Mac publish uses
`openclaw/releases-private/.github/workflows/openclaw-macos-publish.yml` for
build, signing, notarization, packaged mac artifact generation, and
stable-feed `appcast.xml` artifact generation.
- Real private mac publish uploads the packaged `.zip`, `.dmg`, and
`.dSYM.zip` assets to the existing GitHub release in `openclaw/openclaw`
automatically when `OPENCLAW_PUBLIC_REPO_RELEASE_TOKEN` is present in the
private repo `mac-release` environment.
- For stable releases, the agent must also download the signed
`macos-appcast-<tag>` artifact from the successful private mac workflow and
then update `appcast.xml` on `main`.
- For beta mac releases, do not update the shared production `appcast.xml`
unless a separate beta Sparkle feed exists.
- The private repo targets a dedicated `mac-release` environment. If the GitHub
plan does not yet support required reviewers there, do not assume the
environment alone is the approval boundary; rely on private repo access and
CODEOWNERS until those settings can be enabled.
- Do not use `NPM_TOKEN` or the plugin OTP flow for OpenClaw releases.
- `@openclaw/*` plugin publishes use a separate maintainer-only flow.
- Only publish plugins that already exist on npm; bundled disk-tree-only plugins stay unpublished.
## Fallback local mac publish
- Keep the original local macOS publish workflow available as a fallback in case
CI/CD mac publishing is unavailable or broken.
- Preserve the existing maintainer workflow Peter uses: run it on a real Mac
with local signing, notary, and Sparkle credentials already configured.
- Follow the private maintainer macOS runbook for the local steps:
`scripts/package-mac-dist.sh` to build, sign, notarize, and package the app;
manual GitHub release asset upload; then `scripts/make_appcast.sh` plus the
`appcast.xml` commit to `main`.
- `scripts/package-mac-dist.sh` now fails closed for release builds if the
bundled app comes out with a debug bundle id, an empty Sparkle feed URL, or a
`CFBundleVersion` below the canonical Sparkle build floor for that short
version. For correction tags, set a higher explicit `APP_BUILD`.
- `scripts/make_appcast.sh` first uses `generate_appcast` from `PATH`, then
falls back to the SwiftPM Sparkle tool output under `apps/macos/.build`.
- For stable tags, the local fallback may update the shared production
`appcast.xml`.
- For beta tags, the local fallback still publishes the mac assets but must not
update the shared production `appcast.xml` unless a separate beta feed exists.
- Treat the local workflow as fallback only. Prefer the CI/CD publish workflow
when it is working.
- After any stable mac publish, verify all of the following before you call the
release finished:
- the GitHub release has `.zip`, `.dmg`, and `.dSYM.zip` assets
- `appcast.xml` on `main` points at the new stable zip
- the packaged app reports the expected short version and a numeric
`CFBundleVersion` at or above the canonical Sparkle build floor
## Run the release sequence
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 wait for it to pass.
10. Start `.github/workflows/macos-release.yml` in `openclaw/openclaw` and wait
for the public validation-only run to pass.
11. Start
`openclaw/releases-private/.github/workflows/openclaw-macos-publish.yml`
with `preflight_only=true` and wait for it to pass.
12. 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.
13. Start `.github/workflows/openclaw-npm-release.yml` with the same tag for
the real publish.
14. Wait for `npm-release` approval from `@openclaw/openclaw-release-managers`.
15. Start
`openclaw/releases-private/.github/workflows/openclaw-macos-publish.yml`
for the real publish and wait for success.
16. Verify the successful real private mac run uploaded the `.zip`, `.dmg`,
and `.dSYM.zip` artifacts to the existing GitHub release in
`openclaw/openclaw`.
17. For stable releases, download `macos-appcast-<tag>` from the successful
private mac run, update `appcast.xml` on `main`, and verify the feed.
18. For beta releases, publish the mac assets but expect no shared production
`appcast.xml` artifact and do not update the shared production feed unless a
separate beta feed exists.
19. After publish, verify npm and the attached release artifacts.
## GHSA advisory work
- Use `openclaw-ghsa-maintainer` for GHSA advisory inspection, patch/publish flow, private-fork validation, and GHSA API-specific publish checks.

View File

@@ -0,0 +1,71 @@
---
name: openclaw-test-heap-leaks
description: Investigate `pnpm test` memory growth, Vitest worker OOMs, and suspicious RSS increases in OpenClaw using the `scripts/test-parallel.mjs` heap snapshot tooling. Use when Codex needs to reproduce test-lane memory growth, collect repeated `.heapsnapshot` files, compare snapshots from the same worker PID, distinguish transformed-module retention from real data leaks, and fix or reduce the impact by patching cleanup logic or isolating hotspot tests.
---
# OpenClaw Test Heap Leaks
Use this skill for test-memory investigations. Do not guess from RSS alone when heap snapshots are available.
## Workflow
1. Reproduce the failing shape first.
- Match the real entrypoint if possible. For Linux CI-style unit failures, start with:
- `pnpm canvas:a2ui:bundle && OPENCLAW_TEST_MEMORY_TRACE=1 OPENCLAW_TEST_HEAPSNAPSHOT_INTERVAL_MS=60000 OPENCLAW_TEST_HEAPSNAPSHOT_DIR=.tmp/heapsnap OPENCLAW_TEST_WORKERS=2 OPENCLAW_TEST_MAX_OLD_SPACE_SIZE_MB=6144 pnpm test`
- Keep `OPENCLAW_TEST_MEMORY_TRACE=1` enabled so the wrapper prints per-file RSS summaries alongside the snapshots.
- If the report is about a specific shard or worker budget, preserve that shape.
2. Wait for repeated snapshots before concluding anything.
- Take at least two intervals from the same lane.
- Compare snapshots from the same PID inside one lane directory such as `.tmp/heapsnap/unit-fast/`.
- Use `scripts/heapsnapshot-delta.mjs` to compare either two files directly or the earliest/latest pair per PID in one lane directory.
3. Classify the growth before choosing a fix.
- If growth is dominated by Vite/Vitest transformed source strings, `Module`, `system / Context`, bytecode, descriptor arrays, or property maps, treat it as retained module graph growth in long-lived workers.
- If growth is dominated by app objects, caches, buffers, server handles, timers, mock state, sqlite state, or similar runtime objects, treat it as a likely cleanup or lifecycle leak.
4. Fix the right layer.
- For retained transformed-module growth in shared workers:
- Move hotspot files out of `unit-fast` by updating `test/fixtures/test-parallel.behavior.json`.
- Prefer `singletonIsolated` for files that are safe alone but inflate shared worker heaps.
- If the file should already have been peeled out by timings but is absent from `test/fixtures/test-timings.unit.json`, call that out explicitly. Missing timings are a scheduling blind spot.
- For real leaks:
- Patch the implicated test or runtime cleanup path.
- Look for missing `afterEach`/`afterAll`, module-reset gaps, retained global state, unreleased DB handles, or listeners/timers that survive the file.
5. Verify with the most direct proof.
- Re-run the targeted lane or file with heap snapshots enabled if the suite still finishes in reasonable time.
- If snapshot overhead pushes tests over Vitest timeouts, fall back to the same lane without snapshots and confirm the RSS trend or OOM is reduced.
- For wrapper-only changes, at minimum verify the expected lanes start and the snapshot files are written.
## Heuristics
- Do not call everything a leak. In this repo, large `unit-fast` growth can be a worker-lifetime problem rather than an application object leak.
- `scripts/test-parallel.mjs` and `scripts/test-parallel-memory.mjs` are the primary control points for wrapper diagnostics.
- The lane names printed by `[test-parallel] start ...` and `[test-parallel][mem] summary ...` tell you where to focus.
- When one or two files account for most of the delta and they are missing from timings, reducing impact by isolating them is usually the first pragmatic fix.
- When the same retained object families grow across multiple intervals in the same worker PID, trust the snapshots over intuition.
## Snapshot Comparison
- Direct comparison:
- `node .agents/skills/openclaw-test-heap-leaks/scripts/heapsnapshot-delta.mjs before.heapsnapshot after.heapsnapshot`
- Auto-select earliest/latest snapshots per PID within one lane:
- `node .agents/skills/openclaw-test-heap-leaks/scripts/heapsnapshot-delta.mjs --lane-dir .tmp/heapsnap/unit-fast`
- Useful flags:
- `--top 40`
- `--min-kb 32`
- `--pid 16133`
Read the top positive deltas first. Large positive growth in module-transform artifacts suggests lane isolation; large positive growth in runtime objects suggests a real leak.
## Output Expectations
When using this skill, report:
- The exact reproduce command.
- Which lane and PID were compared.
- The dominant retained object families from the snapshot delta.
- Whether the issue is a real leak or shared-worker retained module growth.
- The concrete fix or impact-reduction patch.
- What you verified, and what snapshot overhead prevented you from verifying.

View File

@@ -0,0 +1,4 @@
interface:
display_name: "Test Heap Leaks"
short_description: "Investigate test OOMs with heap snapshots"
default_prompt: "Use $openclaw-test-heap-leaks to investigate test memory growth with heap snapshots and reduce its impact."

View File

@@ -0,0 +1,265 @@
#!/usr/bin/env node
import fs from "node:fs";
import path from "node:path";
function printUsage() {
console.error(
"Usage: node heapsnapshot-delta.mjs <before.heapsnapshot> <after.heapsnapshot> [--top N] [--min-kb N]",
);
console.error(
" or: node heapsnapshot-delta.mjs --lane-dir <dir> [--pid PID] [--top N] [--min-kb N]",
);
}
function fail(message) {
console.error(message);
process.exit(1);
}
function parseArgs(argv) {
const options = {
top: 30,
minKb: 64,
laneDir: null,
pid: null,
files: [],
};
for (let index = 0; index < argv.length; index += 1) {
const arg = argv[index];
if (arg === "--top") {
options.top = Number.parseInt(argv[index + 1] ?? "", 10);
index += 1;
continue;
}
if (arg === "--min-kb") {
options.minKb = Number.parseInt(argv[index + 1] ?? "", 10);
index += 1;
continue;
}
if (arg === "--lane-dir") {
options.laneDir = argv[index + 1] ?? null;
index += 1;
continue;
}
if (arg === "--pid") {
options.pid = Number.parseInt(argv[index + 1] ?? "", 10);
index += 1;
continue;
}
options.files.push(arg);
}
if (!Number.isFinite(options.top) || options.top <= 0) {
fail("--top must be a positive integer");
}
if (!Number.isFinite(options.minKb) || options.minKb < 0) {
fail("--min-kb must be a non-negative integer");
}
if (options.pid !== null && (!Number.isInteger(options.pid) || options.pid <= 0)) {
fail("--pid must be a positive integer");
}
return options;
}
function parseHeapFilename(filePath) {
const base = path.basename(filePath);
const match = base.match(
/^Heap\.(?<stamp>\d{8}\.\d{6})\.(?<pid>\d+)\.0\.(?<seq>\d+)\.heapsnapshot$/u,
);
if (!match?.groups) {
return null;
}
return {
filePath,
pid: Number.parseInt(match.groups.pid, 10),
stamp: match.groups.stamp,
sequence: Number.parseInt(match.groups.seq, 10),
};
}
function resolvePair(options) {
if (options.laneDir) {
const entries = fs
.readdirSync(options.laneDir)
.map((name) => parseHeapFilename(path.join(options.laneDir, name)))
.filter((entry) => entry !== null)
.filter((entry) => options.pid === null || entry.pid === options.pid)
.toSorted((left, right) => {
if (left.pid !== right.pid) {
return left.pid - right.pid;
}
if (left.stamp !== right.stamp) {
return left.stamp.localeCompare(right.stamp);
}
return left.sequence - right.sequence;
});
if (entries.length === 0) {
fail(`No matching heap snapshots found in ${options.laneDir}`);
}
const groups = new Map();
for (const entry of entries) {
const group = groups.get(entry.pid) ?? [];
group.push(entry);
groups.set(entry.pid, group);
}
const candidates = Array.from(groups.values())
.map((group) => ({
pid: group[0].pid,
before: group[0],
after: group.at(-1),
count: group.length,
}))
.filter((entry) => entry.count >= 2);
if (candidates.length === 0) {
fail(`Need at least two snapshots for one PID in ${options.laneDir}`);
}
const chosen =
options.pid !== null
? (candidates.find((entry) => entry.pid === options.pid) ?? null)
: candidates.toSorted((left, right) => right.count - left.count || left.pid - right.pid)[0];
if (!chosen) {
fail(`No PID with at least two snapshots matched in ${options.laneDir}`);
}
return {
before: chosen.before.filePath,
after: chosen.after.filePath,
pid: chosen.pid,
snapshotCount: chosen.count,
};
}
if (options.files.length !== 2) {
printUsage();
process.exit(1);
}
return {
before: options.files[0],
after: options.files[1],
pid: null,
snapshotCount: 2,
};
}
function loadSummary(filePath) {
const data = JSON.parse(fs.readFileSync(filePath, "utf8"));
const meta = data.snapshot?.meta;
if (!meta) {
fail(`Invalid heap snapshot: ${filePath}`);
}
const nodeFieldCount = meta.node_fields.length;
const typeNames = meta.node_types[0];
const strings = data.strings;
const typeIndex = meta.node_fields.indexOf("type");
const nameIndex = meta.node_fields.indexOf("name");
const selfSizeIndex = meta.node_fields.indexOf("self_size");
const summary = new Map();
for (let offset = 0; offset < data.nodes.length; offset += nodeFieldCount) {
const type = typeNames[data.nodes[offset + typeIndex]];
const name = strings[data.nodes[offset + nameIndex]];
const selfSize = data.nodes[offset + selfSizeIndex];
const key = `${type}\t${name}`;
const current = summary.get(key) ?? {
type,
name,
selfSize: 0,
count: 0,
};
current.selfSize += selfSize;
current.count += 1;
summary.set(key, current);
}
return {
nodeCount: data.snapshot.node_count,
summary,
};
}
function formatBytes(bytes) {
if (Math.abs(bytes) >= 1024 ** 2) {
return `${(bytes / 1024 ** 2).toFixed(2)} MiB`;
}
if (Math.abs(bytes) >= 1024) {
return `${(bytes / 1024).toFixed(1)} KiB`;
}
return `${bytes} B`;
}
function formatDelta(bytes) {
return `${bytes >= 0 ? "+" : "-"}${formatBytes(Math.abs(bytes))}`;
}
function truncate(text, maxLength) {
return text.length <= maxLength ? text : `${text.slice(0, maxLength - 1)}`;
}
function main() {
const options = parseArgs(process.argv.slice(2));
const pair = resolvePair(options);
const before = loadSummary(pair.before);
const after = loadSummary(pair.after);
const minBytes = options.minKb * 1024;
const rows = [];
for (const [key, next] of after.summary) {
const previous = before.summary.get(key) ?? { selfSize: 0, count: 0 };
const sizeDelta = next.selfSize - previous.selfSize;
const countDelta = next.count - previous.count;
if (sizeDelta < minBytes) {
continue;
}
rows.push({
type: next.type,
name: next.name,
sizeDelta,
countDelta,
afterSize: next.selfSize,
afterCount: next.count,
});
}
rows.sort(
(left, right) => right.sizeDelta - left.sizeDelta || right.countDelta - left.countDelta,
);
console.log(`before: ${pair.before}`);
console.log(`after: ${pair.after}`);
if (pair.pid !== null) {
console.log(`pid: ${pair.pid} (${pair.snapshotCount} snapshots found)`);
}
console.log(
`nodes: ${before.nodeCount} -> ${after.nodeCount} (${after.nodeCount - before.nodeCount >= 0 ? "+" : ""}${after.nodeCount - before.nodeCount})`,
);
console.log(`filter: top=${options.top} min=${options.minKb} KiB`);
console.log("");
if (rows.length === 0) {
console.log("No entries exceeded the minimum delta.");
return;
}
for (const row of rows.slice(0, options.top)) {
console.log(
[
formatDelta(row.sizeDelta).padStart(11),
`count ${row.countDelta >= 0 ? "+" : ""}${row.countDelta}`.padStart(10),
row.type.padEnd(16),
truncate(row.name || "(empty)", 96),
].join(" "),
);
}
}
main();

View File

@@ -42,10 +42,13 @@ pnpm test:parallels:macos \
## Notes
- Snapshot target: closest to `macOS 26.3.1 fresh`.
- Snapshot resolver now prefers matching `*-poweroff*` clones when the base hint also matches. That lets the harness reuse disk-only recovery snapshots without passing a longer hint.
- If Windows/Linux snapshot restore logs show `PET_QUESTION_SNAPSHOT_STATE_INCOMPATIBLE_CPU`, drop the suspended state once, create a `*-poweroff*` replacement snapshot, and rerun. The smoke scripts now auto-start restored power-off snapshots.
- Harness configures Discord inside the guest; no checked-in token/config.
- Use the `openclaw` wrapper for guest `message send/read`; `node openclaw.mjs message ...` does not expose the lazy message subcommands the same way.
- Write `channels.discord.guilds` in one JSON object (`--strict-json`), not dotted `config set channels.discord.guilds.<snowflake>...` paths; numeric snowflakes get treated like array indexes.
- Avoid `prlctl enter` / expect for long Discord setup scripts; it line-wraps/corrupts long commands. Use `prlctl exec --current-user /bin/sh -lc ...` for the Discord config phase.
- Full 3-OS sweeps: the shared build lock is safe in parallel, but snapshot restore is still a Parallels bottleneck. Prefer serialized Windows/Linux restore-heavy reruns if the host is already under load.
- Harness cleanup deletes the temporary Discord smoke messages at exit.
- Per-phase logs: `/tmp/openclaw-parallels-smoke.*`
- Machine summary: pass `--json`

View File

@@ -0,0 +1,108 @@
---
name: security-triage
description: Triage GitHub security advisories for OpenClaw with high-confidence close/keep decisions, exact tag and commit verification, trust-model checks, optional hardening notes, and a final reply ready to post and copy to clipboard.
---
# Security Triage
Use when reviewing OpenClaw security advisories, drafts, or GHSA reports.
Goal: high-confidence maintainers' triage without over-closing real issues or shipping unnecessary regressions.
## Close Bar
Close only if one of these is true:
- duplicate of an existing advisory or fixed issue
- invalid against shipped behavior
- out of scope under `SECURITY.md`
- fixed before any affected release/tag
Do not close only because `main` is fixed. If latest shipped tag or npm release is affected, keep it open until released or published with the right status.
## Required Reads
Before answering:
1. Read `SECURITY.md`.
2. Read the GHSA body with `gh api /repos/openclaw/openclaw/security-advisories/<GHSA>`.
3. Inspect the exact implicated code paths.
4. Verify shipped state:
- `git tag --sort=-creatordate | head`
- `npm view openclaw version --userconfig "$(mktemp)"`
- `git tag --contains <fix-commit>`
- if needed: `git show <tag>:path/to/file`
5. Search for canonical overlap:
- existing published GHSAs
- older fixed bugs
- same trust-model class already covered in `SECURITY.md`
## Review Method
For each advisory, decide:
- `close`
- `keep open`
- `keep open but narrow`
Check in this order:
1. Trust model
- Is the prerequisite already inside trusted host/local/plugin/operator state?
- Does `SECURITY.md` explicitly call this class out as out of scope or hardening-only?
2. Shipped behavior
- Is the bug present in the latest shipped tag or npm release?
- Was it fixed before release?
3. Exploit path
- Does the report show a real boundary bypass, not just prompt injection, local same-user control, or helper-level semantics?
4. Functional tradeoff
- If a hardening change would reduce intended user functionality, call that out before proposing it.
- Prefer fixes that preserve user workflows over deny-by-default regressions unless the boundary demands it.
## Response Format
When preparing a maintainer-ready close reply:
1. Print the GHSA URL first.
2. Then draft a detailed response the maintainer can post.
3. Include:
- exact reason for close
- exact code refs
- exact shipped tag / release facts
- exact fix commit or canonical duplicate GHSA when applicable
- optional hardening note only if worthwhile and functionality-preserving
Keep tone firm, specific, non-defensive.
## Clipboard Step
After drafting the final post body, copy it:
```bash
pbcopy <<'EOF'
<final response>
EOF
```
Tell the user that the clipboard now contains the proposed response.
## Useful Commands
```bash
gh api /repos/openclaw/openclaw/security-advisories/<GHSA>
gh api /repos/openclaw/openclaw/security-advisories --paginate
git tag --sort=-creatordate | head -n 20
npm view openclaw version --userconfig "$(mktemp)"
git tag --contains <commit>
git show <tag>:<path>
gh search issues --repo openclaw/openclaw --match title,body,comments -- "<terms>"
gh search prs --repo openclaw/openclaw --match title,body,comments -- "<terms>"
```
## Decision Notes
- “fixed on main, unreleased” is usually not a close.
- “needs attacker-controlled trusted local state first” is usually out of scope.
- “same-host same-user process can already read/write local state” is usually out of scope.
- “helper function behaves differently than documented config semantics” is usually invalid.
- If only the severity is wrong but the bug is real, keep it open and narrow the impact in the reply.

View File

@@ -1,7 +1,7 @@
.git
.worktrees
# Sensitive files docker-setup.sh writes .env with OPENCLAW_GATEWAY_TOKEN
# Sensitive files scripts/docker/setup.sh writes .env with OPENCLAW_GATEWAY_TOKEN
# into the project root; keep it out of the build context.
.env
.env.*

View File

@@ -7,7 +7,10 @@ body:
- type: markdown
attributes:
value: |
Thanks for filing this report. Keep it concise, reproducible, and evidence-based.
Thanks for filing this report. Keep every answer concise, reproducible, and grounded in observed evidence.
Do not speculate or infer beyond the evidence. If a narrative section cannot be answered from the available evidence, respond with exactly `NOT_ENOUGH_INFO`.
If this is a plugin beta-release blocker, rename the issue title to `Beta blocker: <plugin-name> - <summary>` and apply the `beta-blocker` label after filing.
- type: dropdown
id: bug_type
attributes:
@@ -19,39 +22,52 @@ body:
- Behavior bug (incorrect output/state without crash)
validations:
required: true
- type: dropdown
id: beta_blocker
attributes:
label: Beta release blocker
description: >
Choose `Yes` only if this blocks plugin compatibility during the current beta release window.
Selecting `Yes` does not apply the label automatically. You must also rename the issue title
to `Beta blocker: <plugin-name> - <summary>` for the automation to apply the `beta-blocker` label.
options:
- "No"
- "Yes"
validations:
required: true
- type: textarea
id: summary
attributes:
label: Summary
description: One-sentence statement of what is broken.
placeholder: After upgrading to <version>, <channel> behavior regressed from <prior version>.
description: One-sentence statement of what is broken, based only on observed evidence. If the evidence is insufficient, respond with exactly `NOT_ENOUGH_INFO`.
placeholder: After upgrading from 2026.2.10 to 2026.2.17, Telegram thread replies stopped posting; reproduced twice and confirmed by gateway logs.
validations:
required: true
- type: textarea
id: repro
attributes:
label: Steps to reproduce
description: Provide the shortest deterministic repro path.
description: Provide the shortest deterministic repro path supported by direct observation. If the repro path cannot be grounded from the evidence, respond with exactly `NOT_ENOUGH_INFO`.
placeholder: |
1. Configure channel X.
2. Send message Y.
3. Run command Z.
1. Start OpenClaw 2026.2.17 with the attached config.
2. Send a Telegram thread reply in the affected chat.
3. Observe no reply and confirm the attached `reply target not found` log line.
validations:
required: true
- type: textarea
id: expected
attributes:
label: Expected behavior
description: What should happen if the bug does not exist.
placeholder: Agent posts a reply in the same thread.
description: State the expected result using a concrete reference such as prior observed behavior, attached docs, or a known-good version. If no grounded reference exists, respond with exactly `NOT_ENOUGH_INFO`.
placeholder: In 2026.2.10, the agent posted replies in the same Telegram thread under the same workflow.
validations:
required: true
- type: textarea
id: actual
attributes:
label: Actual behavior
description: What happened instead, including user-visible errors.
placeholder: No reply is posted; gateway logs "reply target not found".
description: Describe only the observed result, including user-visible errors and cited evidence. If the observed result cannot be grounded from the evidence, respond with exactly `NOT_ENOUGH_INFO`.
placeholder: No reply is posted in the thread; the attached gateway log shows `reply target not found` at 14:23:08 UTC.
validations:
required: true
- type: input
@@ -92,12 +108,6 @@ body:
placeholder: openclaw -> cloudflare-ai-gateway -> minimax
validations:
required: true
- type: input
id: config_location
attributes:
label: Config file / key location
description: Optional. Relevant config source or key path if this bug depends on overrides or custom provider setup. Redact secrets.
placeholder: ~/.openclaw/openclaw.json ; models.providers.cloudflare-ai-gateway.baseUrl ; ~/.openclaw/agents/<agentId>/agent/models.json
- type: textarea
id: provider_setup_details
attributes:
@@ -111,27 +121,28 @@ body:
id: logs
attributes:
label: Logs, screenshots, and evidence
description: Include redacted logs/screenshots/recordings that prove the behavior.
description: Include the redacted logs, screenshots, recordings, docs, or version comparisons that support the grounded answers above.
render: shell
- type: textarea
id: impact
attributes:
label: Impact and severity
description: |
Explain who is affected, how severe it is, how often it happens, and the practical consequence.
Explain who is affected, how severe it is, how often it happens, and the practical consequence using only observed evidence.
If any part cannot be grounded from the evidence, respond with exactly `NOT_ENOUGH_INFO`.
Include:
- Affected users/systems/channels
- Severity (annoying, blocks workflow, data risk, etc.)
- Frequency (always/intermittent/edge case)
- Consequence (missed messages, failed onboarding, extra cost, etc.)
placeholder: |
Affected: Telegram group users on <version>
Severity: High (blocks replies)
Frequency: 100% repro
Consequence: Agents cannot respond in threads
Affected: Telegram group users on 2026.2.17
Severity: High (blocks thread replies)
Frequency: 4/4 observed attempts
Consequence: Agents do not respond in the affected threads
- type: textarea
id: additional_information
attributes:
label: Additional information
description: Add any context that helps triage but does not fit above. If this is a regression, include the last known good and first known bad versions.
placeholder: Last known good version <...>, first known bad version <...>, temporary workaround is ...
description: Add any remaining grounded context that helps triage but does not fit above. If this is a regression, include the last known good and first known bad versions when observed. If there is not enough evidence, respond with exactly `NOT_ENOUGH_INFO`.
placeholder: Last known good version 2026.2.10, first known bad version 2026.2.17, temporary workaround is sending a top-level message instead of a thread reply.

View File

@@ -23,6 +23,16 @@ runs:
exit 0
fi
if ! [[ "$BASE_SHA" =~ ^[0-9a-fA-F]{7,40}$ ]]; then
echo "::error title=ensure-base-commit invalid base sha::Refusing invalid base SHA: $BASE_SHA"
exit 2
fi
if ! git check-ref-format --branch "$FETCH_REF" >/dev/null 2>&1; then
echo "::error title=ensure-base-commit invalid fetch ref::Refusing invalid fetch ref: $FETCH_REF"
exit 2
fi
if git rev-parse --verify "$BASE_SHA^{commit}" >/dev/null 2>&1; then
echo "Base commit already present: $BASE_SHA"
exit 0
@@ -30,7 +40,9 @@ runs:
for deepen_by in 25 100 300; do
echo "Base commit missing; deepening $FETCH_REF by $deepen_by."
git fetch --no-tags --deepen="$deepen_by" origin "$FETCH_REF" || true
if ! git fetch --no-tags --deepen="$deepen_by" origin -- "$FETCH_REF"; then
echo "::warning title=ensure-base-commit fetch failed::Failed to deepen $FETCH_REF by $deepen_by while looking for $BASE_SHA"
fi
if git rev-parse --verify "$BASE_SHA^{commit}" >/dev/null 2>&1; then
echo "Resolved base commit after deepening: $BASE_SHA"
exit 0
@@ -38,7 +50,9 @@ runs:
done
echo "Base commit still missing; fetching full history for $FETCH_REF."
git fetch --no-tags origin "$FETCH_REF" || true
if ! git fetch --no-tags origin -- "$FETCH_REF"; then
echo "::warning title=ensure-base-commit fetch failed::Failed to fetch full history for $FETCH_REF while looking for $BASE_SHA"
fi
if git rev-parse --verify "$BASE_SHA^{commit}" >/dev/null 2>&1; then
echo "Resolved base commit after full ref fetch: $BASE_SHA"
exit 0

View File

@@ -1,7 +1,7 @@
name: Setup Node environment
description: >
Initialize submodules with retry, install Node 24 by default, pnpm, optionally Bun,
and optionally run pnpm install. Requires actions/checkout to run first.
Install Node 24 by default, pnpm, optionally Bun, and optionally run pnpm
install. Requires actions/checkout to run first.
inputs:
node-version:
description: Node.js version to install.
@@ -34,20 +34,6 @@ inputs:
runs:
using: composite
steps:
- name: Checkout submodules (retry)
shell: bash
run: |
set -euo pipefail
git submodule sync --recursive
for attempt in 1 2 3 4 5; do
if git -c protocol.version=2 submodule update --init --force --depth=1 --recursive; then
exit 0
fi
echo "Submodule update failed (attempt $attempt/5). Retrying…"
sleep $((attempt * 10))
done
exit 1
- name: Setup Node.js
uses: actions/setup-node@v6
with:
@@ -63,7 +49,7 @@ runs:
- name: Setup Bun
if: inputs.install-bun == 'true'
uses: oven-sh/setup-bun@v2.1.3
uses: oven-sh/setup-bun@v2.2.0
with:
bun-version: "1.3.9"

23
.github/labeler.yml vendored
View File

@@ -165,7 +165,10 @@
- "Dockerfile.*"
- "docker-compose.yml"
- "docker-setup.sh"
- "setup-podman.sh"
- ".dockerignore"
- "scripts/docker/setup.sh"
- "scripts/podman/setup.sh"
- "scripts/**/*docker*"
- "scripts/**/Dockerfile*"
- "scripts/sandbox-*.sh"
@@ -218,14 +221,14 @@
- changed-files:
- any-glob-to-any-file:
- "extensions/open-prose/**"
"extensions: qwen-portal-auth":
- changed-files:
- any-glob-to-any-file:
- "extensions/qwen-portal-auth/**"
"extensions: device-pair":
- changed-files:
- any-glob-to-any-file:
- "extensions/device-pair/**"
"extensions: duckduckgo":
- changed-files:
- any-glob-to-any-file:
- "extensions/duckduckgo/**"
"extensions: acpx":
- changed-files:
- any-glob-to-any-file:
@@ -234,6 +237,10 @@
- changed-files:
- any-glob-to-any-file:
- "extensions/byteplus/**"
"extensions: deepseek":
- changed-files:
- any-glob-to-any-file:
- "extensions/deepseek/**"
"extensions: anthropic":
- changed-files:
- any-glob-to-any-file:
@@ -290,6 +297,10 @@
- changed-files:
- any-glob-to-any-file:
- "extensions/synthetic/**"
"extensions: tavily":
- changed-files:
- any-glob-to-any-file:
- "extensions/tavily/**"
"extensions: talk-voice":
- changed-files:
- any-glob-to-any-file:
@@ -314,3 +325,7 @@
- changed-files:
- any-glob-to-any-file:
- "extensions/xiaomi/**"
"extensions: fal":
- changed-files:
- any-glob-to-any-file:
- "extensions/fal/**"

View File

@@ -2,6 +2,8 @@
Describe the problem and fix in 25 bullets:
If this PR fixes a plugin beta-release blocker, title it `fix(<plugin-id>): beta blocker - <summary>` and link the matching `Beta blocker: <plugin-name> - <summary>` issue labeled `beta-blocker`. Contributors cannot label PRs, so the title is the PR-side signal for maintainers and automation.
- Problem:
- Why it matters:
- What changed:
@@ -11,7 +13,7 @@ Describe the problem and fix in 25 bullets:
- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Refactor required for the fix
- [ ] Docs
- [ ] Security hardening
- [ ] Chore/infra
@@ -31,12 +33,50 @@ Describe the problem and fix in 25 bullets:
- Closes #
- Related #
- [ ] This PR fixes a bug or regression
## Root Cause / Regression History (if applicable)
For bug fixes or regressions, explain why this happened, not just what changed. Otherwise write `N/A`. If the cause is unclear, write `Unknown`.
- Root cause:
- Missing detection / guardrail:
- Prior context (`git blame`, prior PR, issue, or refactor if known):
- Why this regressed now:
- If unknown, what was ruled out:
## Regression Test Plan (if applicable)
For bug fixes or regressions, name the smallest reliable test coverage that should have caught this. Otherwise write `N/A`.
- Coverage level that should have caught this:
- [ ] Unit test
- [ ] Seam / integration test
- [ ] End-to-end test
- [ ] Existing coverage already sufficient
- Target test or file:
- Scenario the test should lock in:
- Why this is the smallest reliable guardrail:
- Existing test that already covers this (if any):
- If no new test is added, why not:
## User-visible / Behavior Changes
List user-visible changes (including defaults/config).
If none, write `None`.
## Diagram (if applicable)
For UI changes or non-trivial logic flows, include a small ASCII diagram reviewers can scan quickly. Otherwise write `N/A`.
```text
Before:
[user action] -> [old state]
After:
[user action] -> [new state] -> [result]
```
## Security Impact (required)
- New permissions/capabilities? (`Yes/No`)
@@ -101,12 +141,6 @@ If a bot review conversation is addressed by this PR, resolve that conversation
- Migration needed? (`Yes/No`)
- If yes, exact upgrade steps:
## Failure Recovery (if this breaks)
- How to disable/revert this change quickly:
- Files/config to restore:
- Known bad symptoms reviewers should watch for:
## Risks and Mitigations
List only real risks for this PR. Add/remove entries as needed. If none, write `None`.

View File

@@ -11,6 +11,10 @@ on:
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref || github.run_id }}
cancel-in-progress: ${{ github.event_name == 'pull_request_target' }}
permissions: {}
jobs:
@@ -398,11 +402,13 @@ jobs:
const invalidLabel = "invalid";
const spamLabel = "r: spam";
const dirtyLabel = "dirty";
const badBarnacleLabel = "bad-barnacle";
const noisyPrMessage =
"Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.";
if (pullRequest) {
if (labelSet.has(dirtyLabel)) {
// `bad-barnacle` exempts PRs that Barnacle incorrectly marked dirty.
if (labelSet.has(dirtyLabel) && !labelSet.has(badBarnacleLabel)) {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,

108
.github/workflows/ci-bun.yml vendored Normal file
View File

@@ -0,0 +1,108 @@
name: CI Bun
on:
push:
branches: [main]
concurrency:
group: ci-bun-push-${{ github.run_id }}
cancel-in-progress: false
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
jobs:
preflight:
runs-on: blacksmith-16vcpu-ubuntu-2404
timeout-minutes: 20
outputs:
run_bun_checks: ${{ steps.manifest.outputs.run_bun_checks }}
bun_checks_matrix: ${{ steps.manifest.outputs.bun_checks_matrix }}
steps:
- name: Checkout
uses: actions/checkout@v6
with:
submodules: false
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
install-bun: "false"
install-deps: "false"
use-sticky-disk: "false"
- name: Build Bun CI manifest
id: manifest
env:
OPENCLAW_CI_DOCS_ONLY: "false"
OPENCLAW_CI_DOCS_CHANGED: "false"
OPENCLAW_CI_RUN_NODE: "true"
OPENCLAW_CI_RUN_MACOS: "false"
OPENCLAW_CI_RUN_ANDROID: "false"
OPENCLAW_CI_RUN_WINDOWS: "false"
OPENCLAW_CI_RUN_SKILLS_PYTHON: "false"
OPENCLAW_CI_HAS_CHANGED_EXTENSIONS: "false"
OPENCLAW_CI_CHANGED_EXTENSIONS_MATRIX: '{"include":[]}'
run: node scripts/ci-write-manifest-outputs.mjs --workflow ci-bun
build-bun-artifacts:
needs: [preflight]
if: needs.preflight.outputs.run_bun_checks == 'true'
runs-on: blacksmith-16vcpu-ubuntu-2404
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v6
with:
submodules: false
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
install-bun: "false"
use-sticky-disk: "false"
- name: Build A2UI bundle
run: pnpm canvas:a2ui:bundle
- name: Upload A2UI bundle artifact
uses: actions/upload-artifact@v4
with:
name: canvas-a2ui-bundle
path: src/canvas-host/a2ui/
bun-checks:
name: ${{ matrix.check_name }}
needs: [preflight, build-bun-artifacts]
if: needs.preflight.outputs.run_bun_checks == 'true'
runs-on: blacksmith-16vcpu-ubuntu-2404
timeout-minutes: 20
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.preflight.outputs.bun_checks_matrix) }}
steps:
- name: Checkout
uses: actions/checkout@v6
with:
submodules: false
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
install-bun: "true"
use-sticky-disk: "false"
- name: Download A2UI bundle artifact
uses: actions/download-artifact@v8
with:
name: canvas-a2ui-bundle
path: src/canvas-host/a2ui/
- name: Run Bun test shard
env:
SHARD_COUNT: ${{ matrix.shard_count }}
SHARD_INDEX: ${{ matrix.shard_index }}
shell: bash
run: |
set -euo pipefail
OPENCLAW_TEST_ISOLATE=1 bunx vitest run --config vitest.unit.config.ts --shard "$SHARD_INDEX/$SHARD_COUNT"

1000
.github/workflows/ci.yml vendored

File diff suppressed because it is too large Load Diff

View File

@@ -116,7 +116,7 @@ jobs:
- name: Build Android for CodeQL
if: matrix.language == 'java-kotlin'
working-directory: apps/android
run: ./gradlew --no-daemon :app:assembleDebug
run: ./gradlew --no-daemon :app:assemblePlayDebug
- name: Build Swift for CodeQL
if: matrix.language == 'swift'

View File

@@ -2,8 +2,6 @@ name: Docker Release
on:
push:
branches:
- main
tags:
- "v*"
paths-ignore:
@@ -15,12 +13,12 @@ on:
workflow_dispatch:
inputs:
tag:
description: Existing release tag to backfill (for example v2026.3.13)
description: Existing release tag to backfill (for example v2026.3.22)
required: true
type: string
concurrency:
group: docker-release-${{ github.workflow }}-${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref }}
group: ${{ github.event_name == 'workflow_dispatch' && format('docker-release-manual-{0}', inputs.tag) || format('docker-release-push-{0}', github.run_id) }}
cancel-in-progress: false
env:
@@ -159,6 +157,8 @@ jobs:
with:
context: .
platforms: linux/amd64
cache-from: type=gha,scope=docker-release-amd64
cache-to: type=gha,mode=max,scope=docker-release-amd64
tags: ${{ steps.tags.outputs.value }}
labels: ${{ steps.labels.outputs.value }}
provenance: false
@@ -171,6 +171,8 @@ jobs:
with:
context: .
platforms: linux/amd64
cache-from: type=gha,scope=docker-release-amd64
cache-to: type=gha,mode=max,scope=docker-release-amd64
build-args: |
OPENCLAW_VARIANT=slim
tags: ${{ steps.tags.outputs.slim }}
@@ -272,6 +274,8 @@ jobs:
with:
context: .
platforms: linux/arm64
cache-from: type=gha,scope=docker-release-arm64
cache-to: type=gha,mode=max,scope=docker-release-arm64
tags: ${{ steps.tags.outputs.value }}
labels: ${{ steps.labels.outputs.value }}
provenance: false
@@ -284,6 +288,8 @@ jobs:
with:
context: .
platforms: linux/arm64
cache-from: type=gha,scope=docker-release-arm64
cache-to: type=gha,mode=max,scope=docker-release-arm64
build-args: |
OPENCLAW_VARIANT=slim
tags: ${{ steps.tags.outputs.slim }}

View File

@@ -4,41 +4,87 @@ on:
push:
branches: [main]
pull_request:
types: [opened, reopened, synchronize, ready_for_review, converted_to_draft]
workflow_dispatch:
concurrency:
group: install-smoke-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
group: ${{ github.event_name == 'pull_request' && format('{0}-{1}', github.workflow, github.event.pull_request.number) || format('{0}-{1}', github.workflow, github.run_id) }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
jobs:
docs-scope:
preflight:
if: github.event_name != 'pull_request' || !github.event.pull_request.draft
runs-on: blacksmith-16vcpu-ubuntu-2404
outputs:
docs_only: ${{ steps.check.outputs.docs_only }}
docs_only: ${{ steps.manifest.outputs.docs_only }}
run_install_smoke: ${{ steps.manifest.outputs.run_install_smoke }}
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 1
fetch-tags: false
persist-credentials: false
submodules: false
- name: Ensure docs-scope base commit
- name: Ensure preflight base commit
uses: ./.github/actions/ensure-base-commit
with:
base-sha: ${{ github.event_name == 'push' && github.event.before || github.event.pull_request.base.sha }}
fetch-ref: ${{ github.event_name == 'push' && github.ref_name || github.event.pull_request.base.ref }}
- name: Detect docs-only changes
id: check
id: docs_scope
uses: ./.github/actions/detect-docs-changes
- name: Detect changed smoke scope
id: changed_scope
if: steps.docs_scope.outputs.docs_only != 'true'
shell: bash
run: |
set -euo pipefail
if [ "${{ github.event_name }}" = "push" ]; then
BASE="${{ github.event.before }}"
else
BASE="${{ github.event.pull_request.base.sha }}"
fi
node scripts/ci-changed-scope.mjs --base "$BASE" --head HEAD
- name: Setup Node environment
if: steps.docs_scope.outputs.docs_only != 'true'
uses: ./.github/actions/setup-node-env
with:
install-bun: "false"
install-deps: "false"
use-sticky-disk: "false"
- name: Build install-smoke CI manifest
id: manifest
env:
OPENCLAW_CI_DOCS_ONLY: ${{ steps.docs_scope.outputs.docs_only }}
OPENCLAW_CI_DOCS_CHANGED: "false"
OPENCLAW_CI_RUN_NODE: "false"
OPENCLAW_CI_RUN_MACOS: "false"
OPENCLAW_CI_RUN_ANDROID: "false"
OPENCLAW_CI_RUN_WINDOWS: "false"
OPENCLAW_CI_RUN_SKILLS_PYTHON: "false"
OPENCLAW_CI_HAS_CHANGED_EXTENSIONS: "false"
OPENCLAW_CI_CHANGED_EXTENSIONS_MATRIX: '{"include":[]}'
OPENCLAW_CI_RUN_CHANGED_SMOKE: ${{ steps.changed_scope.outputs.run_changed_smoke || 'false' }}
run: node scripts/ci-write-manifest-outputs.mjs --workflow install-smoke
install-smoke:
needs: [docs-scope]
if: needs.docs-scope.outputs.docs_only != 'true'
needs: [preflight]
if: needs.preflight.outputs.run_install_smoke == 'true'
runs-on: blacksmith-16vcpu-ubuntu-2404
env:
DOCKER_BUILD_SUMMARY: "false"
DOCKER_BUILD_RECORD_UPLOAD: "false"
steps:
- name: Checkout CLI
uses: actions/checkout@v6
@@ -53,6 +99,8 @@ jobs:
with:
context: .
file: ./Dockerfile
build-args: |
OPENCLAW_DOCKER_APT_UPGRADE=0
tags: openclaw-dockerfile-smoke:local
load: true
push: false
@@ -62,24 +110,66 @@ jobs:
run: |
docker run --rm --entrypoint sh openclaw-dockerfile-smoke:local -lc 'which openclaw && openclaw --version'
# This smoke only validates that the build-arg path preinstalls selected
# extension deps without breaking image build or basic CLI startup. It
# does not exercise runtime loading/registration of diagnostics-otel.
# This smoke validates that the build-arg path preinstalls the matrix
# runtime deps declared by the plugin and that matrix discovery stays
# healthy in the final runtime image.
- name: Build extension Dockerfile smoke image
uses: useblacksmith/build-push-action@v2
with:
context: .
file: ./Dockerfile
build-args: |
OPENCLAW_EXTENSIONS=diagnostics-otel
OPENCLAW_DOCKER_APT_UPGRADE=0
OPENCLAW_EXTENSIONS=matrix
tags: openclaw-ext-smoke:local
load: true
push: false
provenance: false
- name: Smoke test Dockerfile with extension build arg
- name: Smoke test Dockerfile with matrix extension build arg
run: |
docker run --rm --entrypoint sh openclaw-ext-smoke:local -lc 'which openclaw && openclaw --version'
docker run --rm --entrypoint sh openclaw-ext-smoke:local -lc '
which openclaw &&
openclaw --version &&
node -e "
const Module = require(\"node:module\");
const matrixPackage = require(\"/app/extensions/matrix/package.json\");
const requireFromMatrix = Module.createRequire(\"/app/extensions/matrix/package.json\");
const runtimeDeps = Object.keys(matrixPackage.dependencies ?? {});
if (runtimeDeps.length === 0) {
throw new Error(
\"matrix package has no declared runtime dependencies; smoke cannot validate install mirroring\",
);
}
for (const dep of runtimeDeps) {
requireFromMatrix.resolve(dep);
}
const { spawnSync } = require(\"node:child_process\");
const run = spawnSync(\"openclaw\", [\"plugins\", \"list\", \"--json\"], { encoding: \"utf8\" });
if (run.status !== 0) {
process.stderr.write(run.stderr || run.stdout || \"plugins list failed\\n\");
process.exit(run.status ?? 1);
}
const parsed = JSON.parse(run.stdout);
const matrix = (parsed.plugins || []).find((entry) => entry.id === \"matrix\");
if (!matrix) {
throw new Error(\"matrix plugin missing from bundled plugin list\");
}
const matrixDiag = (parsed.diagnostics || []).filter(
(diag) =>
typeof diag.source === \"string\" &&
diag.source.includes(\"/extensions/matrix\") &&
typeof diag.message === \"string\" &&
diag.message.includes(\"extension entry escapes package directory\"),
);
if (matrixDiag.length > 0) {
throw new Error(
\"unexpected matrix diagnostics: \" +
matrixDiag.map((diag) => diag.message).join(\"; \"),
);
}
"
'
- name: Build installer smoke image
uses: useblacksmith/build-push-action@v2
@@ -104,12 +194,12 @@ jobs:
- name: Run installer docker tests
env:
CLAWDBOT_INSTALL_URL: https://openclaw.ai/install.sh
CLAWDBOT_INSTALL_CLI_URL: https://openclaw.ai/install-cli.sh
CLAWDBOT_NO_ONBOARD: "1"
CLAWDBOT_INSTALL_SMOKE_SKIP_CLI: "1"
CLAWDBOT_INSTALL_SMOKE_SKIP_IMAGE_BUILD: "1"
CLAWDBOT_INSTALL_NONROOT_SKIP_IMAGE_BUILD: ${{ github.event_name == 'pull_request' && '0' || '1' }}
CLAWDBOT_INSTALL_SMOKE_SKIP_NONROOT: ${{ github.event_name == 'pull_request' && '1' || '0' }}
CLAWDBOT_INSTALL_SMOKE_SKIP_PREVIOUS: "1"
OPENCLAW_INSTALL_URL: https://openclaw.ai/install.sh
OPENCLAW_INSTALL_CLI_URL: https://openclaw.ai/install-cli.sh
OPENCLAW_NO_ONBOARD: "1"
OPENCLAW_INSTALL_SMOKE_SKIP_CLI: "1"
OPENCLAW_INSTALL_SMOKE_SKIP_IMAGE_BUILD: "1"
OPENCLAW_INSTALL_NONROOT_SKIP_IMAGE_BUILD: ${{ github.event_name == 'pull_request' && '0' || '1' }}
OPENCLAW_INSTALL_SMOKE_SKIP_NONROOT: ${{ github.event_name == 'pull_request' && '1' || '0' }}
OPENCLAW_INSTALL_SMOKE_SKIP_PREVIOUS: "1"
run: bash scripts/test-install-sh-docker.sh

View File

@@ -2,9 +2,9 @@ name: Labeler
on:
pull_request_target: # zizmor: ignore[dangerous-triggers] maintainer-owned triage workflow; no untrusted checkout or PR code execution
types: [opened, synchronize, reopened]
types: [opened, synchronize, reopened, edited]
issues:
types: [opened]
types: [opened, edited]
workflow_dispatch:
inputs:
max_prs:
@@ -19,6 +19,10 @@ on:
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref || github.run_id }}
cancel-in-progress: ${{ github.event_name == 'pull_request_target' }}
permissions: {}
jobs:
@@ -205,6 +209,59 @@ jobs:
// labels: [trustedLabel],
// });
// }
- name: Apply beta-blocker title label
uses: actions/github-script@v8
with:
github-token: ${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token }}
script: |
const pullRequest = context.payload.pull_request;
if (!pullRequest) {
return;
}
const labelName = "beta-blocker";
const matchesBetaBlocker = /\bbeta blocker\b/i.test(pullRequest.title ?? "");
try {
await github.rest.issues.getLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: labelName,
});
} catch (error) {
if (error?.status !== 404) {
throw error;
}
core.info(`Skipping ${labelName} labeling because the label does not exist in the repository.`);
return;
}
const currentLabels = await github.paginate(github.rest.issues.listLabelsOnIssue, {
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pullRequest.number,
per_page: 100,
});
const hasLabel = currentLabels.some((label) => label.name === labelName);
if (matchesBetaBlocker && !hasLabel) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pullRequest.number,
labels: [labelName],
});
return;
}
if (!matchesBetaBlocker && hasLabel) {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pullRequest.number,
name: labelName,
});
}
- name: Apply too-many-prs label
uses: actions/github-script@v8
with:
@@ -415,6 +472,7 @@ jobs:
const maxCount = processAll ? Number.POSITIVE_INFINITY : Math.max(1, maxPrs);
const sizeLabels = ["size: XS", "size: S", "size: M", "size: L", "size: XL"];
const betaBlockerLabel = "beta-blocker";
const labelColor = "b76e79";
// const trustedLabel = "trusted-contributor";
// const experiencedLabel = "experienced-contributor";
@@ -445,6 +503,22 @@ jobs:
}
}
async function hasBetaBlockerLabel() {
try {
await github.rest.issues.getLabel({
owner,
repo,
name: betaBlockerLabel,
});
return true;
} catch (error) {
if (error?.status !== 404) {
throw error;
}
return false;
}
}
async function resolveContributorLabel(login) {
if (contributorCache.has(login)) {
return contributorCache.get(login);
@@ -576,7 +650,37 @@ jobs:
labelNames.add(label);
}
async function applyBetaBlockerTitleLabel(pullRequest, labelNames) {
const matchesBetaBlocker = /\bbeta blocker\b/i.test(pullRequest.title ?? "");
if (matchesBetaBlocker) {
if (!labelNames.has(betaBlockerLabel)) {
await github.rest.issues.addLabels({
owner,
repo,
issue_number: pullRequest.number,
labels: [betaBlockerLabel],
});
labelNames.add(betaBlockerLabel);
}
return;
}
if (!labelNames.has(betaBlockerLabel)) {
return;
}
await github.rest.issues.removeLabel({
owner,
repo,
issue_number: pullRequest.number,
name: betaBlockerLabel,
});
labelNames.delete(betaBlockerLabel);
}
await ensureSizeLabels();
const betaBlockerLabelExists = await hasBetaBlockerLabel();
let page = 1;
let processed = 0;
@@ -614,6 +718,9 @@ jobs:
await applySizeLabel(pullRequest, currentLabels, labelNames);
await applyContributorLabel(pullRequest, labelNames);
if (betaBlockerLabelExists) {
await applyBetaBlockerTitleLabel(pullRequest, labelNames);
}
processed += 1;
}
@@ -715,3 +822,56 @@ jobs:
// labels: [trustedLabel],
// });
// }
- name: Apply beta-blocker title label
uses: actions/github-script@v8
with:
github-token: ${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token }}
script: |
const issue = context.payload.issue;
if (!issue || issue.pull_request) {
return;
}
const labelName = "beta-blocker";
const matchesBetaBlocker = /^beta blocker:/i.test(issue.title ?? "");
try {
await github.rest.issues.getLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: labelName,
});
} catch (error) {
if (error?.status !== 404) {
throw error;
}
core.info(`Skipping ${labelName} labeling because the label does not exist in the repository.`);
return;
}
const currentLabels = await github.paginate(github.rest.issues.listLabelsOnIssue, {
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
per_page: 100,
});
const hasLabel = currentLabels.some((label) => label.name === labelName);
if (matchesBetaBlocker && !hasLabel) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
labels: [labelName],
});
return;
}
if (!matchesBetaBlocker && hasLabel) {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
name: labelName,
});
}

86
.github/workflows/macos-release.yml vendored Normal file
View File

@@ -0,0 +1,86 @@
name: macOS Release
on:
workflow_dispatch:
inputs:
tag:
description: Existing release tag to validate for macOS release handoff (for example v2026.3.22 or v2026.3.22-beta.1)
required: true
type: string
preflight_only:
description: Retained for operator compatibility; this public workflow is validation-only
required: true
default: true
type: boolean
concurrency:
group: macos-release-${{ inputs.tag }}
cancel-in-progress: false
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
NODE_VERSION: "24.x"
PNPM_VERSION: "10.23.0"
jobs:
validate_macos_release_request:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Validate tag input format
env:
RELEASE_TAG: ${{ inputs.tag }}
run: |
set -euo pipefail
if [[ ! "${RELEASE_TAG}" =~ ^v[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*((-beta\.[1-9][0-9]*)|(-[1-9][0-9]*))?$ ]]; then
echo "Invalid release tag format: ${RELEASE_TAG}"
exit 1
fi
- name: Checkout selected tag
uses: actions/checkout@v6
with:
ref: refs/tags/${{ inputs.tag }}
fetch-depth: 0
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "false"
use-sticky-disk: "false"
- name: Ensure matching GitHub release exists
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ inputs.tag }}
run: gh release view "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" >/dev/null
- name: Validate release tag and package metadata
env:
RELEASE_TAG: ${{ inputs.tag }}
RELEASE_MAIN_REF: origin/main
run: |
set -euo pipefail
RELEASE_SHA=$(git rev-parse HEAD)
export RELEASE_SHA RELEASE_TAG RELEASE_MAIN_REF
git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main
pnpm release:openclaw:npm:check
- name: Summarize next step
env:
RELEASE_TAG: ${{ inputs.tag }}
run: |
{
echo "## Public macOS validation only"
echo
echo "This workflow no longer builds, signs, notarizes, or uploads macOS assets."
echo
echo "Next step:"
echo "- Run \`openclaw/releases-private/.github/workflows/openclaw-macos-publish.yml\` with tag \`${RELEASE_TAG}\`."
echo "- Use \`preflight_only=true\` there for the full private mac preflight."
echo "- For the real publish path, the private run uploads the packaged \`.zip\`, \`.dmg\`, and \`.dSYM.zip\` files to the existing GitHub release in \`openclaw/openclaw\` automatically."
echo "- For stable releases, also download \`macos-appcast-${RELEASE_TAG}\` from the successful private run and commit \`appcast.xml\` back to \`main\` in \`openclaw/openclaw\`."
} >> "$GITHUB_STEP_SUMMARY"

View File

@@ -1,15 +1,17 @@
name: OpenClaw NPM Release
on:
push:
tags:
- "v*"
workflow_dispatch:
inputs:
tag:
description: Release tag to publish (for example v2026.3.14, v2026.3.14-beta.1, or fallback v2026.3.14-1)
description: Release tag to publish (for example v2026.3.22, v2026.3.22-beta.1, or fallback v2026.3.22-1)
required: true
type: string
preflight_only:
description: Run validation/build only and skip the gated publish job
required: true
default: false
type: boolean
concurrency:
group: openclaw-npm-release-${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref }}
@@ -21,15 +23,25 @@ env:
PNPM_VERSION: "10.23.0"
jobs:
preview_openclaw_npm:
if: github.event_name == 'push'
preflight_openclaw_npm:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Validate tag input format
env:
RELEASE_TAG: ${{ inputs.tag }}
run: |
set -euo pipefail
if [[ ! "${RELEASE_TAG}" =~ ^v[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*((-beta\.[1-9][0-9]*)|(-[1-9][0-9]*))?$ ]]; then
echo "Invalid release tag format: ${RELEASE_TAG}"
exit 1
fi
- name: Checkout
uses: actions/checkout@v6
with:
ref: refs/tags/${{ inputs.tag }}
fetch-depth: 0
- name: Setup Node environment
@@ -40,38 +52,12 @@ jobs:
install-bun: "false"
use-sticky-disk: "false"
- name: Print release plan
env:
RELEASE_TAG: ${{ github.ref_name }}
run: |
set -euo pipefail
RELEASE_SHA=$(git rev-parse HEAD)
PACKAGE_VERSION=$(node -p "require('./package.json').version")
if [[ "${RELEASE_TAG}" =~ ^v[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*-[1-9][0-9]*$ ]]; then
TAG_KIND="fallback correction"
else
TAG_KIND="standard"
fi
echo "Release plan for ${RELEASE_TAG}:"
echo "Resolved release SHA: ${RELEASE_SHA}"
echo "Resolved package version: ${PACKAGE_VERSION}"
echo "Resolved tag kind: ${TAG_KIND}"
if [[ "${TAG_KIND}" == "fallback correction" ]]; then
echo "Correction tag note: npm version remains ${PACKAGE_VERSION}"
fi
echo "Would run: git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main"
echo "Would run with env: RELEASE_SHA=${RELEASE_SHA} RELEASE_TAG=${RELEASE_TAG} RELEASE_MAIN_REF=origin/main pnpm release:openclaw:npm:check"
echo "Would run: npm view openclaw@${PACKAGE_VERSION} version"
echo "Would run: pnpm check"
echo "Would run: pnpm build"
echo "Would run: pnpm release:check"
- name: Validate release tag and package metadata
env:
RELEASE_TAG: ${{ github.ref_name }}
RELEASE_TAG: ${{ inputs.tag }}
RELEASE_MAIN_REF: origin/main
run: |
set -euxo pipefail
set -euo pipefail
RELEASE_SHA=$(git rev-parse HEAD)
export RELEASE_SHA RELEASE_TAG RELEASE_MAIN_REF
# Fetch the full main ref so merge-base ancestry checks keep working
@@ -81,52 +67,51 @@ jobs:
- name: Ensure version is not already published
env:
RELEASE_TAG: ${{ github.ref_name }}
PREFLIGHT_ONLY: ${{ inputs.preflight_only }}
run: |
set -euxo pipefail
set -euo pipefail
PACKAGE_VERSION=$(node -p "require('./package.json').version")
IS_CORRECTION_TAG=0
if [[ "${RELEASE_TAG}" =~ ^v[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*-[1-9][0-9]*$ ]]; then
IS_CORRECTION_TAG=1
fi
if npm view "openclaw@${PACKAGE_VERSION}" version >/dev/null 2>&1; then
if [[ "${IS_CORRECTION_TAG}" == "1" ]]; then
echo "openclaw@${PACKAGE_VERSION} is already published on npm."
echo "Correction tag ${RELEASE_TAG} is allowed as a fallback release tag, so preview will continue without treating this as an error."
if [[ "${PREFLIGHT_ONLY}" == "true" ]]; then
echo "openclaw@${PACKAGE_VERSION} is already published on npm; continuing because preflight_only=true."
exit 0
fi
echo "openclaw@${PACKAGE_VERSION} is already published on npm."
exit 1
fi
if [[ "${IS_CORRECTION_TAG}" == "1" ]]; then
echo "Previewing fallback correction tag ${RELEASE_TAG} for npm version openclaw@${PACKAGE_VERSION}"
else
echo "Previewing openclaw@${PACKAGE_VERSION}"
fi
echo "Publishing openclaw@${PACKAGE_VERSION}"
- name: Check
run: |
set -euxo pipefail
pnpm check
run: pnpm check
- name: Build
run: |
set -euxo pipefail
pnpm build
run: pnpm build
- name: Verify release contents
run: |
set -euxo pipefail
pnpm release:check
run: pnpm release:check
- name: Preview publish command
run: bash scripts/openclaw-npm-publish.sh --dry-run
validate_publish_dispatch_ref:
if: ${{ !inputs.preflight_only }}
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Require main 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."
exit 1
fi
publish_openclaw_npm:
if: github.event_name == 'workflow_dispatch'
# npm trusted publishing + provenance requires a GitHub-hosted runner.
needs: [preflight_openclaw_npm, validate_publish_dispatch_ref]
if: ${{ !inputs.preflight_only }}
runs-on: ubuntu-latest
environment: npm-release
permissions:
@@ -182,14 +167,5 @@ jobs:
echo "Publishing openclaw@${PACKAGE_VERSION}"
- name: Check
run: pnpm check
- name: Build
run: pnpm build
- name: Verify release contents
run: pnpm release:check
- name: Publish
run: bash scripts/openclaw-npm-publish.sh --publish

214
.github/workflows/plugin-npm-release.yml vendored Normal file
View File

@@ -0,0 +1,214 @@
name: Plugin NPM Release
on:
push:
branches:
- main
paths:
- ".github/workflows/plugin-npm-release.yml"
- "extensions/**"
- "package.json"
- "scripts/lib/plugin-npm-release.ts"
- "scripts/plugin-npm-publish.sh"
- "scripts/plugin-npm-release-check.ts"
- "scripts/plugin-npm-release-plan.ts"
workflow_dispatch:
inputs:
publish_scope:
description: Publish the selected plugins or all publishable plugins from the ref
required: true
default: selected
type: choice
options:
- selected
- all-publishable
ref:
description: Commit SHA on main to publish from (copy from the preview run)
required: true
type: string
plugins:
description: Comma-separated plugin package names to publish when publish_scope=selected
required: false
type: string
concurrency:
group: plugin-npm-release-${{ github.event_name == 'workflow_dispatch' && inputs.ref || github.sha }}
cancel-in-progress: false
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
NODE_VERSION: "24.x"
PNPM_VERSION: "10.23.0"
jobs:
preview_plugins_npm:
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
ref_sha: ${{ steps.ref.outputs.sha }}
has_candidates: ${{ steps.plan.outputs.has_candidates }}
candidate_count: ${{ steps.plan.outputs.candidate_count }}
matrix: ${{ steps.plan.outputs.matrix }}
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || github.sha }}
fetch-depth: 0
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "false"
use-sticky-disk: "false"
- name: Resolve checked-out ref
id: ref
run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
- name: Validate ref is on main
run: |
set -euo pipefail
git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main
git merge-base --is-ancestor HEAD origin/main
- name: Validate publishable plugin metadata
env:
PUBLISH_SCOPE: ${{ github.event_name == 'workflow_dispatch' && inputs.publish_scope || '' }}
RELEASE_PLUGINS: ${{ github.event_name == 'workflow_dispatch' && inputs.plugins || '' }}
BASE_REF: ${{ github.event_name != 'workflow_dispatch' && github.event.before || '' }}
HEAD_REF: ${{ steps.ref.outputs.sha }}
run: |
set -euo pipefail
if [[ -n "${PUBLISH_SCOPE}" ]]; then
release_args=(--selection-mode "${PUBLISH_SCOPE}")
if [[ -n "${RELEASE_PLUGINS}" ]]; then
release_args+=(--plugins "${RELEASE_PLUGINS}")
fi
pnpm release:plugins:npm:check -- "${release_args[@]}"
elif [[ -n "${BASE_REF}" ]]; then
pnpm release:plugins:npm:check -- --base-ref "${BASE_REF}" --head-ref "${HEAD_REF}"
else
pnpm release:plugins:npm:check
fi
- name: Resolve plugin release plan
id: plan
env:
PUBLISH_SCOPE: ${{ github.event_name == 'workflow_dispatch' && inputs.publish_scope || '' }}
RELEASE_PLUGINS: ${{ github.event_name == 'workflow_dispatch' && inputs.plugins || '' }}
BASE_REF: ${{ github.event_name != 'workflow_dispatch' && github.event.before || '' }}
HEAD_REF: ${{ steps.ref.outputs.sha }}
run: |
set -euo pipefail
mkdir -p .local
if [[ -n "${PUBLISH_SCOPE}" ]]; then
plan_args=(--selection-mode "${PUBLISH_SCOPE}")
if [[ -n "${RELEASE_PLUGINS}" ]]; then
plan_args+=(--plugins "${RELEASE_PLUGINS}")
fi
node --import tsx scripts/plugin-npm-release-plan.ts "${plan_args[@]}" > .local/plugin-npm-release-plan.json
elif [[ -n "${BASE_REF}" ]]; then
node --import tsx scripts/plugin-npm-release-plan.ts --base-ref "${BASE_REF}" --head-ref "${HEAD_REF}" > .local/plugin-npm-release-plan.json
else
node --import tsx scripts/plugin-npm-release-plan.ts > .local/plugin-npm-release-plan.json
fi
cat .local/plugin-npm-release-plan.json
candidate_count="$(jq -r '.candidates | length' .local/plugin-npm-release-plan.json)"
has_candidates="false"
if [[ "${candidate_count}" != "0" ]]; then
has_candidates="true"
fi
matrix_json="$(jq -c '.candidates' .local/plugin-npm-release-plan.json)"
{
echo "candidate_count=${candidate_count}"
echo "has_candidates=${has_candidates}"
echo "matrix=${matrix_json}"
} >> "$GITHUB_OUTPUT"
echo "Plugin release candidates:"
jq -r '.candidates[]? | "- \(.packageName)@\(.version) [\(.publishTag)] from \(.packageDir)"' .local/plugin-npm-release-plan.json
echo "Already published / skipped:"
jq -r '.skippedPublished[]? | "- \(.packageName)@\(.version)"' .local/plugin-npm-release-plan.json
preview_plugin_pack:
needs: preview_plugins_npm
if: needs.preview_plugins_npm.outputs.has_candidates == 'true'
runs-on: ubuntu-latest
permissions:
contents: read
strategy:
fail-fast: false
matrix:
plugin: ${{ fromJson(needs.preview_plugins_npm.outputs.matrix) }}
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: ${{ needs.preview_plugins_npm.outputs.ref_sha }}
fetch-depth: 1
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "false"
use-sticky-disk: "false"
install-deps: "false"
- name: Preview publish command
run: bash scripts/plugin-npm-publish.sh --dry-run "${{ matrix.plugin.packageDir }}"
- name: Preview npm pack contents
working-directory: ${{ matrix.plugin.packageDir }}
run: npm pack --dry-run --json --ignore-scripts
publish_plugins_npm:
needs: [preview_plugins_npm, preview_plugin_pack]
if: github.event_name == 'workflow_dispatch' && needs.preview_plugins_npm.outputs.has_candidates == 'true'
runs-on: ubuntu-latest
environment: npm-release
permissions:
contents: read
id-token: write
strategy:
fail-fast: false
matrix:
plugin: ${{ fromJson(needs.preview_plugins_npm.outputs.matrix) }}
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: ${{ needs.preview_plugins_npm.outputs.ref_sha }}
fetch-depth: 1
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
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
env:
PACKAGE_NAME: ${{ matrix.plugin.packageName }}
PACKAGE_VERSION: ${{ matrix.plugin.version }}
run: |
set -euo pipefail
if npm view "${PACKAGE_NAME}@${PACKAGE_VERSION}" version >/dev/null 2>&1; then
echo "${PACKAGE_NAME}@${PACKAGE_VERSION} is already published on npm."
exit 1
fi
- name: Publish
run: bash scripts/plugin-npm-publish.sh --publish "${{ matrix.plugin.packageDir }}"

View File

@@ -8,13 +8,14 @@ on:
- Dockerfile.sandbox-common
- scripts/sandbox-common-setup.sh
pull_request:
types: [opened, reopened, synchronize, ready_for_review, converted_to_draft]
paths:
- Dockerfile.sandbox
- Dockerfile.sandbox-common
- scripts/sandbox-common-setup.sh
concurrency:
group: sandbox-common-smoke-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
env:
@@ -22,6 +23,7 @@ env:
jobs:
sandbox-common-smoke:
if: github.event_name != 'pull_request' || !github.event.pull_request.draft
runs-on: blacksmith-16vcpu-ubuntu-2404
steps:
- name: Checkout

View File

@@ -42,7 +42,7 @@ jobs:
stale-issue-label: stale
stale-pr-label: stale
exempt-issue-labels: enhancement,maintainer,pinned,security,no-stale
exempt-pr-labels: maintainer,no-stale
exempt-pr-labels: maintainer,no-stale,bad-barnacle
operations-per-run: 2000
ascending: true
exempt-all-assignees: true
@@ -98,7 +98,7 @@ jobs:
stale-issue-label: stale
stale-pr-label: stale
exempt-issue-labels: enhancement,maintainer,pinned,security,no-stale
exempt-pr-labels: maintainer,no-stale
exempt-pr-labels: maintainer,no-stale,bad-barnacle
operations-per-run: 2000
ascending: true
exempt-all-assignees: true

View File

@@ -7,7 +7,7 @@ on:
workflow_dispatch:
concurrency:
group: workflow-sanity-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
env:
@@ -72,7 +72,10 @@ jobs:
- name: Disallow direct inputs interpolation in composite run blocks
run: python3 scripts/check-composite-action-input-interpolation.py
config-docs-drift:
- name: Disallow tracked merge conflict markers
run: node scripts/check-no-conflict-markers.mjs
generated-doc-baselines:
if: github.event_name == 'workflow_dispatch'
runs-on: blacksmith-16vcpu-ubuntu-2404
steps:
@@ -87,3 +90,6 @@ jobs:
- name: Check config docs drift statefile
run: pnpm config:docs:check
- name: Check plugin SDK API baseline drift
run: pnpm plugin-sdk:api:check

8
.gitignore vendored
View File

@@ -4,10 +4,12 @@ node_modules
docker-compose.override.yml
docker-compose.extra.yml
dist
dist-runtime
pnpm-lock.yaml
bun.lock
bun.lockb
coverage
__openclaw_vitest__/
__pycache__/
*.pyc
.tsbuildinfo
@@ -29,6 +31,7 @@ apps/android/.gradle/
apps/android/app/build/
apps/android/.cxx/
apps/android/.kotlin/
apps/android/benchmark/results/
# Bun build artifacts
*.bun-build
@@ -98,8 +101,6 @@ USER.md
/local/
package-lock.json
.claude/
.agents/
.agents
.agent/
skills-lock.json
@@ -133,3 +134,6 @@ ui/src/ui/__screenshots__
ui/src/ui/views/__screenshots__
ui/.vitest-attachments
docs/superpowers
# Deprecated changelog fragment workflow
changelog/fragments/

3
.npmrc
View File

@@ -1 +1,4 @@
# pnpm build-script allowlist lives in package.json -> pnpm.onlyBuiltDependencies.
# TS 7 native-preview fails to resolve packages reliably from pnpm's isolated linker.
# Keep the workspace on a hoisted layout so pnpm check/build stay stable.
node-linker=hoisted

215
AGENTS.md
View File

@@ -2,52 +2,18 @@
- Repo: https://github.com/openclaw/openclaw
- In chat replies, file references must be repo-root relative only (example: `extensions/bluebubbles/src/channel.ts:80`); never absolute paths or `~/...`.
- GitHub issues/comments/PR comments: use literal multiline strings or `-F - <<'EOF'` (or $'...') for real newlines; never embed "\\n".
- GitHub comment footgun: never use `gh issue/pr comment -b "..."` when body contains backticks or shell chars. Always use single-quoted heredoc (`-F - <<'EOF'`) so no command substitution/escaping corruption.
- GitHub linking footgun: dont wrap issue/PR refs like `#24643` in backticks when you want auto-linking. Use plain `#24643` (optionally add full URL).
- PR landing comments: always make commit SHAs clickable with full commit links (both landed SHA + source SHA when present).
- PR review conversations: if a bot leaves review conversations on your PR, address them and resolve those conversations yourself once fixed. Leave a conversation unresolved only when reviewer or maintainer judgment is still needed; do not leave bot-conversation cleanup to maintainers.
- GitHub searching footgun: don't limit yourself to the first 500 issues or PRs when wanting to search all. Unless you're supposed to look at the most recent, keep going until you've reached the last page in the search
- Security advisory analysis: before triage/severity decisions, read `SECURITY.md` to align with OpenClaw's trust model and design boundaries.
- 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.
## Auto-close labels (issues and PRs)
- If an issue/PR matches one of the reasons below, apply the label and let `.github/workflows/auto-response.yml` handle comment/close/lock.
- Do not manually close + manually comment for these reasons.
- Why: keeps wording consistent, preserves automation behavior (`state_reason`, locking), and keeps triage/reporting searchable by label.
- `r:*` labels can be used on both issues and PRs.
- `r: skill`: close with guidance to publish skills on Clawhub.
- `r: support`: close with redirect to Discord support + stuck FAQ.
- `r: no-ci-pr`: close test-fix-only PRs for failing `main` CI and post the standard explanation.
- `r: too-many-prs`: close when author exceeds active PR limit.
- `r: testflight`: close requests asking for TestFlight access/builds. OpenClaw does not provide TestFlight distribution yet, so use the standard response (“Not available, build from source.”) instead of ad-hoc replies.
- `r: third-party-extension`: close with guidance to ship as third-party plugin.
- `r: moltbook`: close + lock as off-topic (not affiliated).
- `r: spam`: close + lock as spam (`lock_reason: spam`).
- `invalid`: close invalid items (issues are closed as `not_planned`; PRs are closed).
- `dirty`: close PRs with too many unrelated/unexpected changes (PR-only label).
## PR truthfulness and bug-fix validation
- Never merge a bug-fix PR based only on issue text, PR text, or AI rationale.
- Before `/landpr`, run `/reviewpr` and require explicit evidence for bug-fix claims.
- Minimum merge gate for bug-fix PRs:
1. symptom evidence (repro/log/failing test),
2. verified root cause in code with file/line,
3. fix touches the implicated code path,
4. regression test (fail before/pass after) when feasible; if not feasible, include manual verification proof and why no test was added.
- If claim is unsubstantiated or likely hallucinated/BS: do not merge. Request evidence/changes, or close with `invalid` when appropriate.
- If linked issue appears wrong/outdated, correct triage first; do not merge speculative fixes.
## Project Structure & Module Organization
- Source code: `src/` (CLI wiring in `src/cli`, commands in `src/commands`, web provider in `src/provider-web.ts`, infra in `src/infra`, media pipeline in `src/media`).
- Tests: colocated `*.test.ts`.
- Docs: `docs/` (images, queue, Pi config). Built output lives in `dist/`.
- Plugins/extensions: live under `extensions/*` (workspace packages). Keep plugin-only deps in the extension `package.json`; do not add them to the root `package.json` unless core uses them.
- Nomenclature: use "plugin" / "plugins" in docs, UI, changelogs, and contributor guidance. `extensions/*` remains the internal directory/package path 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`, `extensions/<id>` by default, 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 under `extensions/*` (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/`
@@ -63,7 +29,7 @@
- For docs, UI copy, and picker lists, order services/providers alphabetically unless the section is explicitly describing runtime behavior (for example auto-detection or execution order).
- Section cross-references: use anchors on root-relative paths (example: `[Hooks](/configuration#hooks)`).
- Doc headings and anchors: avoid em dashes and apostrophes in headings because they break Mintlify anchor links.
- When Peter asks for links, reply with full `https://docs.openclaw.ai/...` URLs (not root-relative).
- When the user asks for links, reply with full `https://docs.openclaw.ai/...` URLs (not root-relative).
- When you touch docs, end the reply with the `https://docs.openclaw.ai/...` URLs you referenced.
- README (GitHub): keep absolute docs URLs (`https://docs.openclaw.ai/...`) so links work on GitHub.
- Docs content must be generic: no personal device names/hostnames/paths; use placeholders like `user@gateway-host` and “gateway host”.
@@ -106,14 +72,28 @@
- Format check: `pnpm format` (oxfmt --check)
- Format fix: `pnpm format:fix` (oxfmt --write)
- Tests: `pnpm test` (vitest); coverage: `pnpm test:coverage`
- Generated baseline artifacts live together under `docs/.generated/`.
- 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, update the matching baseline artifact and keep the two drift-check flows adjacent in scripts/workflows/docs guidance rather than inventing a third pattern.
- 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.
- Preferred landing bar for pushes to `main`: `pnpm check` and `pnpm test`, with a green result when feasible.
- 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 commit or push 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.
- 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.
## Coding Style & Naming Conventions
- Language: TypeScript (ESM). Prefer strict typing; avoid `any`.
- Formatting/linting via Oxlint and Oxfmt; run `pnpm check` before commits.
- Formatting/linting via Oxlint and Oxfmt.
- Never add `@ts-nocheck` and do not disable `no-explicit-any`; fix root causes and update Oxlint/Oxfmt config only when required.
- 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.
- 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 `extensions/<id>/**`, do not use relative imports/exports that resolve outside that same `extensions/<id>` 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.
@@ -123,23 +103,26 @@
- 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").
## Release Channels (Naming)
## Release / Advisory Workflows
- stable: tagged releases only (e.g. `vYYYY.M.D`), npm dist-tag `latest`.
- beta: prerelease tags `vYYYY.M.D-beta.N`, npm dist-tag `beta` (may ship without macOS app).
- beta naming: prefer `-beta.N`; do not mint new `-1/-2` betas. Legacy `vYYYY.M.D-<patch>` and `vYYYY.M.D.beta.N` remain recognized.
- dev: moving head on `main` (no tag; git checkout main).
- 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.
## Testing Guidelines
- 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.
- 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, keep using the wrapper: `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 wrapper config/profile/pool routing.
- Do not set test workers above 16; tried already.
- If local Vitest runs cause memory pressure (common on non-Mac-Studio hosts), use `OPENCLAW_TEST_PROFILE=low OPENCLAW_TEST_SERIAL_GATEWAY=1 pnpm test` for land/gate runs.
- Live tests (real keys): `CLAWDBOT_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`.
- Full kit + whats covered: `docs/testing.md`.
- Keep Vitest on `forks` only. Do not introduce or reintroduce any non-`forks` Vitest pool or alternate execution mode in configs, wrapper scripts, or default test commands without explicit approval in this chat. This includes `threads`, `vmThreads`, `vmForks`, and any future/nonstandard pool variant.
- If local Vitest runs cause memory pressure, the wrapper now derives budgets from host capabilities (CPU, memory band, current load). For a conservative explicit override during land/gate runs, use `OPENCLAW_TEST_PROFILE=serial OPENCLAW_TEST_SERIAL_GATEWAY=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`.
- 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.
@@ -148,7 +131,9 @@
## Commit & Pull Request Guidelines
**Full maintainer PR workflow (optional):** If you want the repo's end-to-end maintainer workflow (triage order, quality bar, rebase rules, commit/changelog conventions, co-contributor policy, and the `review-pr` > `prepare-pr` > `merge-pr` pipeline), see `.agents/skills/PR_WORKFLOW.md`. Maintainers may use other workflows; when a maintainer specifies a workflow, follow that. If no workflow is specified, default to PR_WORKFLOW.
- 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`.
- `/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.
@@ -157,105 +142,30 @@
- PR submission template (canonical): `.github/pull_request_template.md`
- Issue submission templates (canonical): `.github/ISSUE_TEMPLATE/`
## Shorthand Commands
- `sync`: if working tree is dirty, commit all changes (pick a sensible Conventional Commit message), then `git pull --rebase`; if rebase conflicts and cannot resolve, stop; otherwise `git push`.
## Git Notes
- 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.
## GitHub Search (`gh`)
- Prefer targeted keyword search before proposing new work or duplicating fixes.
- Use `--repo openclaw/openclaw` + `--match title,body` first; add `--match comments` when triaging follow-up threads.
- PRs: `gh search prs --repo openclaw/openclaw --match title,body --limit 50 -- "auto-update"`
- Issues: `gh search issues --repo openclaw/openclaw --match title,body --limit 50 -- "auto-update"`
- Structured output example:
`gh search issues --repo openclaw/openclaw --match title,body --limit 50 --json number,title,state,url,updatedAt -- "auto update" --jq '.[] | "\(.number) | \(.state) | \(.title) | \(.url)"'`
## Security & Configuration Tips
- Web provider stores creds at `~/.openclaw/credentials/`; rerun `openclaw login` if logged out.
- Pi sessions live under `~/.openclaw/sessions/` by default; the base directory is not configurable.
- 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; use `docs/reference/RELEASING.md` for the public release policy.
- 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.
## GHSA (Repo Advisory) Patch/Publish
- Before reviewing security advisories, read `SECURITY.md`.
- Fetch: `gh api /repos/openclaw/openclaw/security-advisories/<GHSA>`
- Latest npm: `npm view openclaw version --userconfig "$(mktemp)"`
- Private fork PRs must be closed:
`fork=$(gh api /repos/openclaw/openclaw/security-advisories/<GHSA> | jq -r .private_fork.full_name)`
`gh pr list -R "$fork" --state open` (must be empty)
- Description newline footgun: write Markdown via heredoc to `/tmp/ghsa.desc.md` (no `"\\n"` strings)
- Build patch JSON via jq: `jq -n --rawfile desc /tmp/ghsa.desc.md '{summary,severity,description:$desc,vulnerabilities:[...]}' > /tmp/ghsa.patch.json`
- GHSA API footgun: cannot set `severity` and `cvss_vector_string` in the same PATCH; do separate calls.
- Patch + publish: `gh api -X PATCH /repos/openclaw/openclaw/security-advisories/<GHSA> --input /tmp/ghsa.patch.json` (publish = include `"state":"published"`; no `/publish` endpoint)
- If publish fails (HTTP 422): missing `severity`/`description`/`vulnerabilities[]`, or private fork has open PRs
- Verify: re-fetch; ensure `state=published`, `published_at` set; `jq -r .description | rg '\\\\n'` returns nothing
## Troubleshooting
- Rebrand/migration issues or legacy config/service warnings: run `openclaw doctor` (see `docs/gateway/doctor.md`).
## Agent-Specific Notes
## Local Runtime / Platform Notes
- Vocabulary: "makeup" = "mac app".
- Parallels macOS retests: use the snapshot most closely named like `macOS 26.3.1 fresh` when the user asks for a clean/fresh macOS rerun; avoid older Tahoe snapshots unless explicitly requested.
- Parallels beta smoke: use `--target-package-spec openclaw@<beta-version>` for the beta artifact, and pin the stable side with both `--install-version <stable-version>` and `--latest-version <stable-version>` for upgrade runs. npm dist-tags can move mid-run.
- Parallels beta smoke, Windows nuance: old stable `2026.3.12` still prints the Unicode Windows onboarding banner, so mojibake during the stable precheck log is expected there. Judge the beta package by the post-upgrade lane.
- Parallels macOS smoke playbook:
- `prlctl exec` is fine for deterministic repo commands, but it can misrepresent interactive shell behavior (`PATH`, `HOME`, `curl | bash`, shebang resolution). For installer parity or shell-sensitive repros, prefer the guest Terminal or `prlctl enter`.
- Fresh Tahoe snapshot current reality: `brew` exists, `node` may not be on `PATH` in noninteractive guest exec. Use absolute `/opt/homebrew/bin/node` for repo/CLI runs when needed.
- Preferred automation entrypoint: `pnpm test:parallels:macos`. It restores the snapshot most closely matching `macOS 26.3.1 fresh`, serves the current `main` tarball from the host, then runs fresh-install and latest-release-to-main smoke lanes.
- Discord roundtrip smoke is opt-in. Pass `--discord-token-env <VAR> --discord-guild-id <guild> --discord-channel-id <channel>`; the harness will configure Discord in-guest, post a guest message, verify host-side visibility via the Discord REST API, post a fresh host-side message back into the channel, then verify `openclaw message read` sees it in-guest.
- Keep the Discord token in a host env var only. For Peters Mac Studio bot, fetch it into a temp env var from `~/.openclaw/openclaw.json` over SSH instead of hardcoding it in repo files/shell history.
- For Discord smoke on this snapshot: use `openclaw message send/read` via the installed wrapper, not `node openclaw.mjs message ...`; lazy `message` subcommands do not resolve the same way through the direct module entrypoint.
- For Discord guild allowlists: set `channels.discord.guilds` as one JSON object. Do not use dotted `config set channels.discord.guilds.<snowflake>...` paths; numeric snowflakes get treated as array indexes.
- Avoid `prlctl enter` / expect for the Discord config phase; long lines get mangled. Use `prlctl exec --current-user /bin/sh -lc ...` with short commands or temp files.
- Gateway verification in smoke runs should use `openclaw gateway status --deep --require-rpc`, not plain `--deep`, so probe failures go non-zero.
- Latest-release pre-upgrade diagnostics still need compatibility fallback: stable `2026.3.12` does not know `--require-rpc`, so precheck status dumps should fall back to plain `gateway status --deep` until the guest is upgraded.
- Harness output: pass `--json` for machine-readable summary; per-phase logs land under `/tmp/openclaw-parallels-smoke.*`.
- All-OS parallel runs should share the host `dist` build via `/tmp/openclaw-parallels-build.lock` instead of rebuilding three times.
- Current expected outcome on latest stable pre-upgrade: `precheck=latest-ref-fail` is normal on `2026.3.12`; treat it as a baseline signal, not a regression, unless the post-upgrade `main` lane also fails.
- Fresh host-served tgz install: restore fresh snapshot, install tgz as guest root with `HOME=/var/root`, then run onboarding as the desktop user via `prlctl exec --current-user`.
- For `openclaw onboard --non-interactive --secret-input-mode ref --install-daemon`, expect env-backed auth-profile refs (for example `OPENAI_API_KEY`) to be copied into the service env at install time; this path was fixed and should stay green.
- Dont run local + gateway agent turns in parallel on the same fresh workspace/session; they can collide on the session lock. Run sequentially.
- Root-installed tarball smoke on Tahoe can still log plugin blocks for world-writable `extensions/*` under `/opt/homebrew/lib/node_modules/openclaw`; treat that as separate from onboarding/gateway health unless the task is plugin loading.
- Parallels Windows smoke playbook:
- Preferred automation entrypoint: `pnpm test:parallels:windows`. It restores the snapshot most closely matching `pre-openclaw-native-e2e-2026-03-12`, serves the current `main` tarball from the host, then runs fresh-install and latest-release-to-main smoke lanes.
- Gateway verification in smoke runs should use `openclaw gateway status --deep --require-rpc`, not plain `--deep`, so probe failures go non-zero.
- Latest-release pre-upgrade diagnostics still need compatibility fallback: stable `2026.3.12` does not know `--require-rpc`, so precheck status dumps should fall back to plain `gateway status --deep` until the guest is upgraded.
- Always use `prlctl exec --current-user` for Windows guest runs; plain `prlctl exec` lands in `NT AUTHORITY\SYSTEM` and does not match the real desktop-user install path.
- Prefer explicit `npm.cmd` / `openclaw.cmd`. Bare `npm` / `openclaw` in PowerShell can hit the `.ps1` shim and fail under restrictive execution policy.
- Use PowerShell only as the transport (`powershell.exe -NoProfile -ExecutionPolicy Bypass`) and call the `.cmd` shims explicitly from inside it.
- Harness output: pass `--json` for machine-readable summary; per-phase logs land under `/tmp/openclaw-parallels-windows.*`.
- Current expected outcome on latest stable pre-upgrade: `precheck=latest-ref-fail` is normal on `2026.3.12`; treat it as a baseline signal, not a regression, unless the post-upgrade `main` lane also fails.
- Keep Windows onboarding/status text ASCII-clean in logs. Fancy punctuation in banners shows up as mojibake through the current guest PowerShell capture path.
- Parallels Linux smoke playbook:
- Preferred automation entrypoint: `pnpm test:parallels:linux`. It restores the snapshot most closely matching `fresh` on `Ubuntu 24.04.3 ARM64`, serves the current `main` tarball from the host, then runs fresh-install and latest-release-to-main smoke lanes.
- Use plain `prlctl exec` on this snapshot. `--current-user` is not the right transport there.
- Fresh snapshot reality: `curl` is missing and `apt-get update` can fail on clock skew. Bootstrap with `apt-get -o Acquire::Check-Date=false update` and install `curl ca-certificates` before testing installer paths.
- Fresh `main` tgz smoke on Linux still needs the latest-release installer first, because this snapshot has no Node/npm before bootstrap. The harness does stable bootstrap first, then overlays current `main`.
- This snapshot does not have a usable `systemd --user` session. Treat managed daemon install as unsupported here; use `--skip-health`, then verify with direct `openclaw gateway run --bind loopback --port 18789 --force`.
- Env-backed auth refs are still fine, but any direct shell launch (`openclaw gateway run`, `openclaw agent --local`, Linux `gateway status --deep` against that direct run) must inherit the referenced env vars in the same shell.
- `prlctl exec` reaps detached Linux child processes on this snapshot, so a background `openclaw gateway run` launched from automation is not a trustworthy smoke path. The harness verifies installer + `agent --local`; do direct gateway checks only from an interactive guest shell when needed.
- When you do run Linux gateway checks manually from an interactive guest shell, use `openclaw gateway status --deep --require-rpc` so an RPC miss is a hard failure.
- Prefer direct argv guest commands for fetch/install steps (`curl`, `npm install -g`, `openclaw ...`) over nested `bash -lc` quoting; Linux guest quoting through Parallels was the flaky part.
- Harness output: pass `--json` for machine-readable summary; per-phase logs land under `/tmp/openclaw-parallels-linux.*`.
- Current expected outcome on Linux smoke: fresh + upgrade should pass installer and `agent --local`; gateway remains `skipped-no-detached-linux-gateway` on this snapshot and should not be treated as a regression by itself.
- 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`.
- 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.
- Never update the Carbon dependency.
- 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.
- 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 currently runs only as the menubar app; there is no separate LaunchAgent/helper label installed. Restart via the OpenClaw Mac app or `scripts/restart-mac.sh`; to verify/kill use `launchctl print gui/$UID | grep openclaw` rather than assuming a fixed label. **When debugging on macOS, start/stop the gateway via the app, not ad-hoc tmux sessions; kill any temporary tunnels before handoff.**
@@ -270,6 +180,20 @@
- 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`.
## 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.
- Never update the Carbon dependency.
- 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:** do **not** create/remove/modify `git worktree` checkouts (or edit `.worktrees/*`) unless explicitly requested.
@@ -280,41 +204,12 @@
- 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).
- Lobster seam: 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.
- **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 ~500 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.
- 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.
- 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.
- 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`.
- 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.
## Release Auth
- Core `openclaw` publish uses GitHub trusted publishing; do not use `NPM_TOKEN` or the plugin OTP flow for core releases.
- Separate `@openclaw/*` plugin publishes use a different maintainer-only auth flow.
- Plugin scope: only publish already-on-npm `@openclaw/*` plugins. Bundled disk-tree-only plugins stay out.
- Maintainers: private 1Password item names, tmux rules, plugin publish helpers, and local mac signing/notary setup live in the private [maintainer release docs](https://github.com/openclaw/maintainers/blob/main/release/README.md).
## Changelog Release Notes
- When cutting a mac release with beta GitHub prerelease:
- Tag `vYYYY.M.D-beta.N` from the release commit (example: `v2026.2.15-beta.1`).
- Create prerelease with title `openclaw YYYY.M.D-beta.N`.
- Use release notes from `CHANGELOG.md` version section (`Changes` + `Fixes`, no title duplicate).
- Attach at least `OpenClaw-YYYY.M.D.zip` and `OpenClaw-YYYY.M.D.dSYM.zip`; include `.dmg` if available.
- Keep top version entries in `CHANGELOG.md` sorted by impact:
- `### Changes` first.
- `### Fixes` deduped and ranked with user-facing fixes first.
- Before tagging/publishing, run:
- `node --import tsx scripts/release-check.ts`
- `pnpm release:check`
- `pnpm test:install:smoke` or `OPENCLAW_INSTALL_SMOKE_SKIP_NONROOT=1 pnpm test:install:smoke` for non-root smoke path.

File diff suppressed because it is too large Load Diff

View File

@@ -24,7 +24,7 @@ Welcome to the lobster tank! 🦞
- GitHub: [@joshp123](https://github.com/joshp123) · X: [@jjpcodes](https://x.com/jjpcodes)
- **Ayaan Zaidi** - Telegram subsystem, Android app
- GitHub: [@obviyus](https://github.com/obviyus) · X: [@0bviyus](https://x.com/0bviyus)
- GitHub: [@obviyus](https://github.com/obviyus) · X: [@obviyus](https://x.com/obviyus)
- **Tyler Yust** - Agents/subagents, cron, BlueBubbles, macOS app
- GitHub: [@tyler6204](https://github.com/tyler6204) · X: [@tyleryust](https://x.com/tyleryust)
@@ -47,7 +47,7 @@ Welcome to the lobster tank! 🦞
- **Christoph Nakazawa** - JS Infra
- GitHub: [@cpojer](https://github.com/cpojer) · X: [@cnakazawa](https://x.com/cnakazawa)
- **Gustavo Madeira Santana** - Multi-agents, CLI, web UI
- **Gustavo Madeira Santana** - Multi-agents, CLI, Performance, Plugins, Matrix
- GitHub: [@gumadeiras](https://github.com/gumadeiras) · X: [@gumadeiras](https://x.com/gumadeiras)
- **Onur Solmaz** - Agents, dev workflows, ACP integrations, MS Teams
@@ -58,6 +58,7 @@ Welcome to the lobster tank! 🦞
- **Jonathan Taylor** - ACP subsystem, Gateway features/bugs, Gog/Mog/Sog CLI's, SEDMAT
- GitHub [@visionik](https://github.com/visionik) · X: [@visionik](https://x.com/visionik)
- **Josh Lehman** - Compaction, Tlon/Urbit subsystem
- GitHub [@jalehman](https://github.com/jalehman) · X: [@jlehman\_](https://x.com/jlehman_)
@@ -76,14 +77,13 @@ Welcome to the lobster tank! 🦞
- **Tengji (George) Zhang** - Chinese model APIs, cloud, pi
- GitHub: [@odysseus0](https://github.com/odysseus0) · X: [@odysseus0z](https://x.com/odysseus0z)
- **Andrew (Bubbles) Demczuk** - Agents/Gateway/TTS/VTT
- GitHub: [@ademczuk](https://github.com/ademczuk) · X: [@ademczuk](https://x.com/ademczuk)
## How to Contribute
1. **Bugs & small fixes** → Open a PR!
2. **New features / architecture** → Start a [GitHub Discussion](https://github.com/openclaw/openclaw/discussions) or ask in Discord first
3. **Questions** → Discord [#help](https://discord.com/channels/1456350064065904867/1459642797895319552) / [#users-helping-users](https://discord.com/channels/1456350064065904867/1459007081603403828)
3. **Refactor-only PRs** → Don't open a PR. We are not accepting refactor-only changes unless a maintainer explicitly asks for them as part of a concrete fix.
4. **Test/CI-only PRs for known `main` failures** → Don't open a PR. The Maintainer team is already tracking those failures, and PRs that only tweak tests or CI to chase them will be closed unless they are required to validate a new fix.
5. **Questions** → Discord [#help](https://discord.com/channels/1456350064065904867/1459642797895319552) / [#users-helping-users](https://discord.com/channels/1456350064065904867/1459007081603403828)
## Before You PR
@@ -91,8 +91,14 @@ Welcome to the lobster tank! 🦞
- Run tests: `pnpm build && pnpm check && pnpm test`
- For extension/plugin changes, run the fast local lane first:
- `pnpm test:extension <extension-name>`
- If you changed shared plugin or channel surfaces, still run the broader relevant lanes (`pnpm test:extensions`, `pnpm test:channels`, or `pnpm test`) before asking for review
- `pnpm test:extension --list` to see valid extension ids
- If you changed shared plugin or channel surfaces, run `pnpm test:contracts`
- For targeted shared-surface work, use `pnpm test:contracts:channels` or `pnpm test:contracts:plugins`
- If you changed broader runtime behavior, still run the relevant wider lanes (`pnpm test:extensions`, `pnpm test:channels`, or `pnpm test`) before asking for review
- If you have access to Codex, run `codex review --base origin/main` locally before opening or updating your PR. Treat this as the current highest standard of AI review, even if GitHub Codex review also runs.
- Do not submit refactor-only PRs unless a maintainer explicitly requested that refactor for an active fix or deliverable.
- Do not submit test or CI-config fixes for failures already red on `main` CI. If a failure is already visible in the [main branch CI runs](https://github.com/openclaw/openclaw/actions), it's a known issue the Maintainer team is tracking, and a PR that only addresses those failures will be closed automatically. If you spot a _new_ regression not yet shown in main CI, report it as an issue first.
- Do not submit test-only PRs that just try to make known `main` CI failures pass. Test changes are acceptable when they are required to validate a new fix or cover new behavior in the same PR.
- Ensure CI checks pass
- Keep PRs focused (one thing per PR; do not mix unrelated concerns)
- Describe what & why

View File

@@ -14,6 +14,7 @@
# Slim (bookworm-slim): docker build --build-arg OPENCLAW_VARIANT=slim .
ARG OPENCLAW_EXTENSIONS=""
ARG OPENCLAW_VARIANT=default
ARG OPENCLAW_DOCKER_APT_UPGRADE=1
ARG OPENCLAW_NODE_BOOKWORM_IMAGE="node:24-bookworm@sha256:3a09aa6354567619221ef6c45a5051b671f953f0a1924d1f819ffb236e520e6b"
ARG OPENCLAW_NODE_BOOKWORM_DIGEST="sha256:3a09aa6354567619221ef6c45a5051b671f953f0a1924d1f819ffb236e520e6b"
ARG OPENCLAW_NODE_BOOKWORM_SLIM_IMAGE="node:24-bookworm-slim@sha256:e8e2e91b1378f83c5b2dd15f0247f34110e2fe895f6ca7719dbb780f929368eb"
@@ -113,6 +114,7 @@ LABEL org.opencontainers.image.base.name="docker.io/library/node:24-bookworm-sli
# ── Stage 3: Runtime ────────────────────────────────────────────
FROM base-${OPENCLAW_VARIANT}
ARG OPENCLAW_VARIANT
ARG OPENCLAW_DOCKER_APT_UPGRADE
# OCI base-image metadata for downstream image consumers.
# If you change these annotations, also update:
@@ -129,10 +131,14 @@ WORKDIR /app
# Install system utilities present in bookworm but missing in bookworm-slim.
# On the full bookworm image these are already installed (apt-get is a no-op).
# Smoke workflows can opt out of distro upgrades to cut repeated CI time while
# keeping the default runtime image behavior unchanged.
RUN --mount=type=cache,id=openclaw-bookworm-apt-cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,id=openclaw-bookworm-apt-lists,target=/var/lib/apt,sharing=locked \
apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get upgrade -y --no-install-recommends && \
if [ "${OPENCLAW_DOCKER_APT_UPGRADE}" != "0" ]; then \
DEBIAN_FRONTEND=noninteractive apt-get upgrade -y --no-install-recommends; \
fi && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
procps hostname curl git lsof openssl
@@ -146,6 +152,10 @@ COPY --from=runtime-assets --chown=node:node /app/extensions ./extensions
COPY --from=runtime-assets --chown=node:node /app/skills ./skills
COPY --from=runtime-assets --chown=node:node /app/docs ./docs
# In npm-installed Docker images, prefer the copied source extension tree for
# bundled discovery so package metadata that points at source entries stays valid.
ENV OPENCLAW_BUNDLED_PLUGINS_DIR=/app/extensions
# Keep pnpm available in the runtime image for container-local workflows.
# Use a shared Corepack home so the non-root `node` user does not need a
# first-run network fetch when invoking pnpm.

View File

@@ -19,14 +19,14 @@
</p>
**OpenClaw** is a _personal AI assistant_ you run on your own devices.
It answers you on the channels you already use (WhatsApp, Telegram, Slack, Discord, Google Chat, Signal, iMessage, BlueBubbles, IRC, Microsoft Teams, Matrix, Feishu, LINE, Mattermost, Nextcloud Talk, Nostr, Synology Chat, Tlon, Twitch, Zalo, Zalo Personal, WebChat). It can speak and listen on macOS/iOS/Android, and can render a live Canvas you control. The Gateway is just the control plane — the product is the assistant.
It answers you on the channels you already use (WhatsApp, Telegram, Slack, Discord, Google Chat, Signal, iMessage, BlueBubbles, IRC, Microsoft Teams, Matrix, Feishu, LINE, Mattermost, Nextcloud Talk, Nostr, Synology Chat, Tlon, Twitch, Zalo, Zalo Personal, WeChat, WebChat). It can speak and listen on macOS/iOS/Android, and can render a live Canvas you control. The Gateway is just the control plane — the product is the assistant.
If you want a personal, single-user assistant that feels local, fast, and always-on, this is it.
[Website](https://openclaw.ai) · [Docs](https://docs.openclaw.ai) · [Vision](VISION.md) · [DeepWiki](https://deepwiki.com/openclaw/openclaw) · [Getting Started](https://docs.openclaw.ai/start/getting-started) · [Updating](https://docs.openclaw.ai/install/updating) · [Showcase](https://docs.openclaw.ai/start/showcase) · [FAQ](https://docs.openclaw.ai/help/faq) · [Wizard](https://docs.openclaw.ai/start/wizard) · [Nix](https://github.com/openclaw/nix-openclaw) · [Docker](https://docs.openclaw.ai/install/docker) · [Discord](https://discord.gg/clawd)
[Website](https://openclaw.ai) · [Docs](https://docs.openclaw.ai) · [Vision](VISION.md) · [DeepWiki](https://deepwiki.com/openclaw/openclaw) · [Getting Started](https://docs.openclaw.ai/start/getting-started) · [Updating](https://docs.openclaw.ai/install/updating) · [Showcase](https://docs.openclaw.ai/start/showcase) · [FAQ](https://docs.openclaw.ai/help/faq) · [Onboarding](https://docs.openclaw.ai/start/wizard) · [Nix](https://github.com/openclaw/nix-openclaw) · [Docker](https://docs.openclaw.ai/install/docker) · [Discord](https://discord.gg/clawd)
Preferred setup: run the onboarding wizard (`openclaw onboard`) in your terminal.
The wizard guides you step by step through setting up the gateway, workspace, channels, and skills. The CLI wizard is the recommended path and works on **macOS, Linux, and Windows (via WSL2; strongly recommended)**.
Preferred setup: run `openclaw onboard` in your terminal.
OpenClaw Onboard guides you step by step through setting up the gateway, workspace, channels, and skills. It is the recommended CLI setup path and works on **macOS, Linux, and Windows (via WSL2; strongly recommended)**.
Works with npm, pnpm, or bun.
New install? Start here: [Getting started](https://docs.openclaw.ai/start/getting-started)
@@ -49,7 +49,7 @@ Model note: while many providers/models are supported, for the best experience a
## Install (recommended)
Runtime: **Node ≥22**.
Runtime: **Node 24 (recommended) or Node 22.16+**.
```bash
npm install -g openclaw@latest
@@ -58,11 +58,11 @@ npm install -g openclaw@latest
openclaw onboard --install-daemon
```
The wizard installs the Gateway daemon (launchd/systemd user service) so it stays running.
OpenClaw Onboard installs the Gateway daemon (launchd/systemd user service) so it stays running.
## Quick start (TL;DR)
Runtime: **Node ≥22**.
Runtime: **Node 24 (recommended) or Node 22.16+**.
Full beginner guide (auth, pairing, channels): [Getting started](https://docs.openclaw.ai/start/getting-started)
@@ -74,7 +74,7 @@ openclaw gateway --port 18789 --verbose
# Send a message
openclaw message send --to +1234567890 --message "Hello from OpenClaw"
# Talk to the assistant (optionally deliver back to any connected channel: WhatsApp/Telegram/Slack/Discord/Google Chat/Signal/iMessage/BlueBubbles/IRC/Microsoft Teams/Matrix/Feishu/LINE/Mattermost/Nextcloud Talk/Nostr/Synology Chat/Tlon/Twitch/Zalo/Zalo Personal/WebChat)
# Talk to the assistant (optionally deliver back to any connected channel: WhatsApp/Telegram/Slack/Discord/Google Chat/Signal/iMessage/BlueBubbles/IRC/Microsoft Teams/Matrix/Feishu/LINE/Mattermost/Nextcloud Talk/Nostr/Synology Chat/Tlon/Twitch/Zalo/Zalo Personal/WeChat/WebChat)
openclaw agent --message "Ship checklist" --thinking high
```
@@ -126,13 +126,13 @@ Run `openclaw doctor` to surface risky/misconfigured DM policies.
## Highlights
- **[Local-first Gateway](https://docs.openclaw.ai/gateway)** — single control plane for sessions, channels, tools, and events.
- **[Multi-channel inbox](https://docs.openclaw.ai/channels)** — WhatsApp, Telegram, Slack, Discord, Google Chat, Signal, BlueBubbles (iMessage), iMessage (legacy), IRC, Microsoft Teams, Matrix, Feishu, LINE, Mattermost, Nextcloud Talk, Nostr, Synology Chat, Tlon, Twitch, Zalo, Zalo Personal, WebChat, macOS, iOS/Android.
- **[Multi-channel inbox](https://docs.openclaw.ai/channels)** — WhatsApp, Telegram, Slack, Discord, Google Chat, Signal, BlueBubbles (iMessage), iMessage (legacy), IRC, Microsoft Teams, Matrix, Feishu, LINE, Mattermost, Nextcloud Talk, Nostr, Synology Chat, Tlon, Twitch, Zalo, Zalo Personal, WeChat, WebChat, macOS, iOS/Android.
- **[Multi-agent routing](https://docs.openclaw.ai/gateway/configuration)** — route inbound channels/accounts/peers to isolated agents (workspaces + per-agent sessions).
- **[Voice Wake](https://docs.openclaw.ai/nodes/voicewake) + [Talk Mode](https://docs.openclaw.ai/nodes/talk)** — wake words on macOS/iOS and continuous voice on Android (ElevenLabs + system TTS fallback).
- **[Live Canvas](https://docs.openclaw.ai/platforms/mac/canvas)** — agent-driven visual workspace with [A2UI](https://docs.openclaw.ai/platforms/mac/canvas#canvas-a2ui).
- **[First-class tools](https://docs.openclaw.ai/tools)** — browser, canvas, nodes, cron, sessions, and Discord/Slack actions.
- **[Companion apps](https://docs.openclaw.ai/platforms/macos)** — macOS menu bar app + iOS/Android [nodes](https://docs.openclaw.ai/nodes).
- **[Onboarding](https://docs.openclaw.ai/start/wizard) + [skills](https://docs.openclaw.ai/tools/skills)** — wizard-driven setup with bundled/managed/workspace skills.
- **[Onboarding](https://docs.openclaw.ai/start/wizard) + [skills](https://docs.openclaw.ai/tools/skills)** — onboarding-driven setup with bundled/managed/workspace skills.
## Star History
@@ -143,14 +143,14 @@ Run `openclaw doctor` to surface risky/misconfigured DM policies.
### Core platform
- [Gateway WS control plane](https://docs.openclaw.ai/gateway) with sessions, presence, config, cron, webhooks, [Control UI](https://docs.openclaw.ai/web), and [Canvas host](https://docs.openclaw.ai/platforms/mac/canvas#canvas-a2ui).
- [CLI surface](https://docs.openclaw.ai/tools/agent-send): gateway, agent, send, [wizard](https://docs.openclaw.ai/start/wizard), and [doctor](https://docs.openclaw.ai/gateway/doctor).
- [CLI surface](https://docs.openclaw.ai/tools/agent-send): gateway, agent, send, [onboarding](https://docs.openclaw.ai/start/wizard), and [doctor](https://docs.openclaw.ai/gateway/doctor).
- [Pi agent runtime](https://docs.openclaw.ai/concepts/agent) in RPC mode with tool streaming and block streaming.
- [Session model](https://docs.openclaw.ai/concepts/session): `main` for direct chats, group isolation, activation modes, queue modes, reply-back. Group rules: [Groups](https://docs.openclaw.ai/channels/groups).
- [Media pipeline](https://docs.openclaw.ai/nodes/images): images/audio/video, transcription hooks, size caps, temp file lifecycle. Audio details: [Audio](https://docs.openclaw.ai/nodes/audio).
### Channels
- [Channels](https://docs.openclaw.ai/channels): [WhatsApp](https://docs.openclaw.ai/channels/whatsapp) (Baileys), [Telegram](https://docs.openclaw.ai/channels/telegram) (grammY), [Slack](https://docs.openclaw.ai/channels/slack) (Bolt), [Discord](https://docs.openclaw.ai/channels/discord) (discord.js), [Google Chat](https://docs.openclaw.ai/channels/googlechat) (Chat API), [Signal](https://docs.openclaw.ai/channels/signal) (signal-cli), [BlueBubbles](https://docs.openclaw.ai/channels/bluebubbles) (iMessage, recommended), [iMessage](https://docs.openclaw.ai/channels/imessage) (legacy imsg), [IRC](https://docs.openclaw.ai/channels/irc), [Microsoft Teams](https://docs.openclaw.ai/channels/msteams), [Matrix](https://docs.openclaw.ai/channels/matrix), [Feishu](https://docs.openclaw.ai/channels/feishu), [LINE](https://docs.openclaw.ai/channels/line), [Mattermost](https://docs.openclaw.ai/channels/mattermost), [Nextcloud Talk](https://docs.openclaw.ai/channels/nextcloud-talk), [Nostr](https://docs.openclaw.ai/channels/nostr), [Synology Chat](https://docs.openclaw.ai/channels/synology-chat), [Tlon](https://docs.openclaw.ai/channels/tlon), [Twitch](https://docs.openclaw.ai/channels/twitch), [Zalo](https://docs.openclaw.ai/channels/zalo), [Zalo Personal](https://docs.openclaw.ai/channels/zalouser), [WebChat](https://docs.openclaw.ai/web/webchat).
- [Channels](https://docs.openclaw.ai/channels): [WhatsApp](https://docs.openclaw.ai/channels/whatsapp) (Baileys), [Telegram](https://docs.openclaw.ai/channels/telegram) (grammY), [Slack](https://docs.openclaw.ai/channels/slack) (Bolt), [Discord](https://docs.openclaw.ai/channels/discord) (discord.js), [Google Chat](https://docs.openclaw.ai/channels/googlechat) (Chat API), [Signal](https://docs.openclaw.ai/channels/signal) (signal-cli), [BlueBubbles](https://docs.openclaw.ai/channels/bluebubbles) (iMessage, recommended), [iMessage](https://docs.openclaw.ai/channels/imessage) (legacy imsg), [IRC](https://docs.openclaw.ai/channels/irc), [Microsoft Teams](https://docs.openclaw.ai/channels/msteams), [Matrix](https://docs.openclaw.ai/channels/matrix), [Feishu](https://docs.openclaw.ai/channels/feishu), [LINE](https://docs.openclaw.ai/channels/line), [Mattermost](https://docs.openclaw.ai/channels/mattermost), [Nextcloud Talk](https://docs.openclaw.ai/channels/nextcloud-talk), [Nostr](https://docs.openclaw.ai/channels/nostr), [Synology Chat](https://docs.openclaw.ai/channels/synology-chat), [Tlon](https://docs.openclaw.ai/channels/tlon), [Twitch](https://docs.openclaw.ai/channels/twitch), [Zalo](https://docs.openclaw.ai/channels/zalo), [Zalo Personal](https://docs.openclaw.ai/channels/zalouser), WeChat (`@tencent-weixin/openclaw-weixin`), [WebChat](https://docs.openclaw.ai/web/webchat).
- [Group routing](https://docs.openclaw.ai/channels/group-messages): mention gating, reply tags, per-channel chunking and routing. Channel rules: [Channels](https://docs.openclaw.ai/channels).
### Apps + nodes
@@ -185,7 +185,7 @@ Run `openclaw doctor` to surface risky/misconfigured DM policies.
## How it works (short)
```
WhatsApp / Telegram / Slack / Discord / Google Chat / Signal / iMessage / BlueBubbles / IRC / Microsoft Teams / Matrix / Feishu / LINE / Mattermost / Nextcloud Talk / Nostr / Synology Chat / Tlon / Twitch / Zalo / Zalo Personal / WebChat
WhatsApp / Telegram / Slack / Discord / Google Chat / Signal / iMessage / BlueBubbles / IRC / Microsoft Teams / Matrix / Feishu / LINE / Mattermost / Nextcloud Talk / Nostr / Synology Chat / Tlon / Twitch / Zalo / Zalo Personal / WeChat / WebChat
┌───────────────────────────────┐
@@ -293,7 +293,7 @@ If you plan to build/run companion apps, follow the platform runbooks below.
- WebChat + debug tools.
- Remote gateway control over SSH.
Note: signed builds required for macOS permissions to stick across rebuilds (see `docs/mac/permissions.md`).
Note: signed builds required for macOS permissions to stick across rebuilds (see [macOS Permissions](https://docs.openclaw.ai/platforms/mac/permissions)).
### iOS node (optional)
@@ -364,7 +364,7 @@ Details: [Security guide](https://docs.openclaw.ai/gateway/security) · [Docker
### [Discord](https://docs.openclaw.ai/channels/discord)
- Set `DISCORD_BOT_TOKEN` or `channels.discord.token` (env wins).
- Set `DISCORD_BOT_TOKEN` or `channels.discord.token`.
- Optional: set `commands.native`, `commands.text`, or `commands.useAccessGroups`, plus `channels.discord.allowFrom`, `channels.discord.guilds`, or `channels.discord.mediaMaxMb` as needed.
```json5
@@ -397,6 +397,12 @@ Details: [Security guide](https://docs.openclaw.ai/gateway/security) · [Docker
- Configure a Teams app + Bot Framework, then add a `msteams` config section.
- Allowlist who can talk via `msteams.allowFrom`; group access via `msteams.groupAllowFrom` or `msteams.groupPolicy: "open"`.
### WeChat
- Official Tencent plugin via [`@tencent-weixin/openclaw-weixin`](https://www.npmjs.com/package/@tencent-weixin/openclaw-weixin) (iLink Bot API). Private chats only; v2.x requires OpenClaw `>=2026.3.22`.
- Install: `openclaw plugins install "@tencent-weixin/openclaw-weixin"`, then `openclaw channels login --channel openclaw-weixin` to scan the QR code.
- Requires the WeChat ClawBot plugin (WeChat > Me > Settings > Plugins); gradual rollout by Tencent.
### [WebChat](https://docs.openclaw.ai/web/webchat)
- Uses the Gateway WebSocket; no separate WebChat port/config.
@@ -422,7 +428,7 @@ Use these when youre past the onboarding flow and want the deeper reference.
- [Run the Gateway by the book with the operational runbook.](https://docs.openclaw.ai/gateway)
- [Learn how the Control UI/Web surfaces work and how to expose them safely.](https://docs.openclaw.ai/web)
- [Understand remote access over SSH tunnels or tailnets.](https://docs.openclaw.ai/gateway/remote)
- [Follow the onboarding wizard flow for a guided setup.](https://docs.openclaw.ai/start/wizard)
- [Follow OpenClaw Onboard for a guided setup.](https://docs.openclaw.ai/start/wizard)
- [Wire external triggers via the webhook surface.](https://docs.openclaw.ai/automation/webhook)
- [Set up Gmail Pub/Sub triggers.](https://docs.openclaw.ai/automation/gmail-pubsub)
- [Learn the macOS menu bar companion details.](https://docs.openclaw.ai/platforms/mac/menu-bar)

View File

@@ -2,6 +2,99 @@
<rss xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle" version="2.0">
<channel>
<title>OpenClaw</title>
<item>
<title>2026.3.24</title>
<pubDate>Wed, 25 Mar 2026 17:06:31 +0000</pubDate>
<link>https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml</link>
<sparkle:version>2026032490</sparkle:version>
<sparkle:shortVersionString>2026.3.24</sparkle:shortVersionString>
<sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion>
<description><![CDATA[<h2>OpenClaw 2026.3.24</h2>
<h3>Breaking</h3>
<h3>Changes</h3>
<ul>
<li>Gateway/OpenAI compatibility: add <code>/v1/models</code> and <code>/v1/embeddings</code>, and forward explicit model overrides through <code>/v1/chat/completions</code> and <code>/v1/responses</code> for broader client and RAG compatibility. Thanks @vincentkoc.</li>
<li>Agents/tools: make <code>/tools</code> show the tools the current agent can actually use right now, add a compact default view with an optional detailed mode, and add a live "Available Right Now" section in the Control UI so it is easier to see what will work before you ask.</li>
<li>Microsoft Teams: migrate to the official Teams SDK and add AI-agent UX best practices including streaming 1:1 replies, welcome cards with prompt starters, feedback/reflection, informative status updates, typing indicators, and native AI labeling. (#51808)</li>
<li>Microsoft Teams: add message edit and delete support for sent messages, including in-thread fallbacks when no explicit target is provided. (#49925)</li>
<li>Skills/install metadata: add one-click install recipes to bundled skills (coding-agent, gh-issues, openai-whisper-api, session-logs, tmux, trello, weather) so the CLI and Control UI can offer dependency installation when requirements are missing. (#53411) Thanks @BunsDev.</li>
<li>Control UI/skills: add status-filter tabs (All / Ready / Needs Setup / Disabled) with counts, replace inline skill cards with a click-to-detail dialog showing requirements, toggle switch, install action, API key entry, source metadata, and homepage link. (#53411) Thanks @BunsDev.</li>
<li>Slack/interactive replies: restore rich reply parity for direct deliveries, auto-render simple trailing <code>Options:</code> lines as buttons/selects, improve Slack interactive setup defaults, and isolate reply controls from plugin interactive handlers. (#53389) Thanks @vincentkoc.</li>
<li>CLI/containers: add <code>--container</code> and <code>OPENCLAW_CONTAINER</code> to run <code>openclaw</code> commands inside a running Docker or Podman OpenClaw container. (#52651) Thanks @sallyom.</li>
<li>Discord/auto threads: add optional <code>autoThreadName: "generated"</code> naming so new auto-created threads can be renamed asynchronously with concise LLM-generated titles while keeping the existing message-based naming as the default. (#43366) Thanks @davidguttman.</li>
<li>Plugins/hooks: add <code>before_dispatch</code> with canonical inbound metadata and route handled replies through the normal final-delivery path, preserving TTS and routed delivery semantics. (#50444) Thanks @gfzhx.</li>
<li>Control UI/agents: convert agent workspace file rows to expandable <code><details></code> with lazy-loaded inline markdown preview, and add comprehensive <code>.sidebar-markdown</code> styles for headings, lists, code blocks, tables, blockquotes, and details/summary elements. (#53411) Thanks @BunsDev.</li>
<li>Control UI/markdown preview: restyle the agent workspace file preview dialog with a frosted backdrop, sized panel, and styled header, and integrate <code>@create-markdown/preview</code> v2 system theme for rich markdown rendering (headings, tables, code blocks, callouts, blockquotes) that auto-adapts to the app's light/dark design tokens. (#53411) Thanks @BunsDev.</li>
<li>macOS app/config: replace horizontal pill-based subsection navigation with a collapsible tree sidebar using disclosure chevrons and indented subsection rows. (#53411) Thanks @BunsDev.</li>
<li>CLI/skills: soften missing-requirements label from "missing" to "needs setup" and surface API key setup guidance (where to get a key, CLI save command, storage path) in <code>openclaw skills info</code> output. (#53411) Thanks @BunsDev.</li>
<li>macOS app/skills: add "Get your key" homepage link and storage-path hint to the API key editor dialog, and show the config path in save confirmation messages. (#53411) Thanks @BunsDev.</li>
<li>Control UI/agents: add a "Not set" placeholder to the default agent model selector dropdown. (#53411) Thanks @BunsDev.</li>
<li>Runtime/install: lower the supported Node 22 floor to <code>22.14+</code> while continuing to recommend Node 24, so npm installs and self-updates do not strand Node 22.14 users on older releases.</li>
<li>CLI/update: preflight the target npm package <code>engines.node</code> before <code>openclaw update</code> runs a global package install, so outdated Node runtimes fail with a clear upgrade message instead of attempting an unsupported latest release.</li>
</ul>
<h3>Fixes</h3>
<ul>
<li>Outbound media/local files: align outbound media access with the configured fs policy so host-local files and inbound-media paths keep sending when <code>workspaceOnly</code> is off, while strict workspace-only agents remain sandboxed.</li>
<li>Security/sandbox media dispatch: close the <code>mediaUrl</code>/<code>fileUrl</code> alias bypass so outbound tool and message actions cannot escape media-root restrictions. (#54034)</li>
<li>Gateway/restart sentinel: wake the interrupted agent session via heartbeat after restart instead of only sending a best-effort restart note, retry outbound delivery once on transient failure, and preserve explicit thread/topic routing through the wake path so replies land in the correct Telegram topic or Slack thread. (#53940) Thanks @VACInc.</li>
<li>Docker/setup: avoid the pre-start <code>openclaw-cli</code> shared-network namespace loop by routing setup-time onboard/config writes through <code>openclaw-gateway</code>, so fresh Docker installs stop failing before the gateway comes up. (#53385) Thanks @amsminn.</li>
<li>Gateway/channels: keep channel startup sequential while isolating per-channel boot failures, so one broken channel no longer blocks later channels from starting. (#54215) Thanks @JonathanJing.</li>
<li>Embedded runs/secrets: stop unresolved <code>SecretRef</code> config from crashing embedded agent runs by falling back to the resolved runtime snapshot when needed. Fixes #45838.</li>
<li>WhatsApp/groups: track recent gateway-sent message IDs and suppress only matching group echoes, preserving owner <code>/status</code>, <code>/new</code>, and <code>/activation</code> commands from linked-account <code>fromMe</code> traffic. (#53624) Thanks @w-sss.</li>
<li>WhatsApp/reply-to-bot detection: restore implicit group reply detection by unwrapping <code>botInvokeMessage</code> payloads and reading <code>selfLid</code> from <code>creds.json</code>, so reply-based mentions reach the bot again in linked-account group chats.</li>
<li>Telegram/forum topics: recover <code>#General</code> topic <code>1</code> routing when Telegram omits forum metadata, including native commands, interactive callbacks, inbound message context, and fallback error replies. (#53699) thanks @huntharo</li>
<li>Discord/gateway supervision: centralize gateway error handling behind a lifetime-owned supervisor so early, active, and late-teardown Carbon gateway errors stay classified consistently and stop surfacing as process-killing teardown crashes.</li>
<li>Discord/timeouts: send a visible timeout reply when the inbound Discord worker times out before a final reply starts, including created auto-thread targets and queued-run ordering. (#53823) Thanks @Kimbo7870.</li>
<li>ACP/direct chats: always deliver a terminal ACP result when final TTS does not yield audio, even if block text already streamed earlier, and skip redundant empty-text final synthesis. (#53692) Thanks @w-sss.</li>
<li>Telegram/outbound errors: preserve actionable 403 membership/block/kick details and treat <code>bot not a member</code> as a permanent delivery failure so Telegram sends stop retrying doomed chats. (#53635) Thanks @w-sss.</li>
<li>Telegram/photos: preflight Telegram photo dimension and aspect-ratio rules, and fall back to document sends when image metadata is invalid or unavailable so photo uploads stop failing with <code>PHOTO_INVALID_DIMENSIONS</code>. (#52545) Thanks @hnshah.</li>
<li>Slack/runtime defaults: trim Slack DM reply overhead, restore Codex auto transport, and tighten Slack/web-search runtime defaults around DM preview threading, cache scoping, warning dedupe, and explicit web-search opt-in. (#53957) Thanks @vincentkoc.</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.3.24/OpenClaw-2026.3.24.zip" length="24749233" type="application/octet-stream" sparkle:edSignature="gLm2VvI+PPEnNy4klYSs9WmZLkJTF5BcfFparrtPdnmeE4xgc8kFfICg445I039ev9/A6xGav7pm08reUHDcAg=="/>
</item>
<item>
<title>2026.3.23</title>
<pubDate>Mon, 23 Mar 2026 16:59:51 -0700</pubDate>
<link>https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml</link>
<sparkle:version>2026032390</sparkle:version>
<sparkle:shortVersionString>2026.3.23</sparkle:shortVersionString>
<sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion>
<description><![CDATA[<h2>OpenClaw 2026.3.23</h2>
<h3>Breaking</h3>
<h3>Changes</h3>
<h3>Fixes</h3>
<ul>
<li>Browser/Chrome MCP: wait for existing-session browser tabs to become usable after attach instead of treating the initial Chrome MCP handshake as ready, which reduces user-profile timeouts and repeated consent churn on macOS Chrome attach flows. Fixes #52930. Thanks @vincentkoc.</li>
<li>Browser/CDP: reuse an already-running loopback browser after a short initial reachability miss instead of immediately falling back to relaunch detection, which fixes second-run browser start/open regressions on slower headless Linux setups. Fixes #53004. Thanks @vincentkoc.</li>
<li>ClawHub/macOS auth: honor macOS auth config and XDG auth paths for saved ClawHub credentials, so <code>openclaw skills ...</code> and gateway skill browsing keep using the signed-in auth state instead of silently falling back to unauthenticated mode. Fixes #53034.</li>
<li>ClawHub/macOS: read the local ClawHub login from the macOS Application Support path and still honor XDG config on macOS, so skill browsing uses the logged-in token on both default and XDG-style setups. Fixes #52949. Thanks @scoootscooob.</li>
<li>ClawHub/skills: resolve the local ClawHub auth token for gateway skill browsing and switch browse-all requests to search so ClawControl stops falling into unauthenticated 429s and empty authenticated skill lists. Fixes #52949. Thanks @vincentkoc.</li>
<li>Plugins/message tool: make Discord <code>components</code> and Slack <code>blocks</code> optional again, and route Feishu <code>message(..., media=...)</code> sends through the outbound media path, so pin/unpin/react flows stop failing schema validation and Feishu file/image attachments actually send. Fixes #52970 and #52962. Thanks @vincentkoc.</li>
<li>Gateway/model pricing: stop <code>openrouter/auto</code> pricing refresh from recursing indefinitely during bootstrap, so OpenRouter auto routes can populate cached pricing and <code>usage.cost</code> again. Fixes #53035. Thanks @vincentkoc.</li>
<li>Mistral/models: lower bundled Mistral max-token defaults to safe output budgets and teach <code>openclaw doctor --fix</code> to repair old persisted Mistral provider configs that still carry context-sized output limits, avoiding deterministic Mistral 422 rejects on fresh and existing setups. Fixes #52599. Thanks @vincentkoc.</li>
<li>Agents/web_search: use the active runtime <code>web_search</code> provider instead of stale/default selection, so agent turns keep hitting the provider you actually configured. Fixes #53020. Thanks @jzakirov.</li>
<li>Models/OpenAI Codex OAuth: bootstrap the env-configured HTTP/HTTPS proxy dispatcher on the stored-credential refresh path before token renewal runs, so expired Codex OAuth profiles can refresh successfully in proxy-required environments instead of locking users out after the first token expiry.</li>
<li>Plugins/memory-lancedb: bootstrap LanceDB into plugin runtime state on first use when the bundled npm install does not already have it, so <code>plugins.slots.memory="memory-lancedb"</code> works again after global npm installs without moving LanceDB into OpenClaw core dependencies. Fixes #26100.</li>
<li>Config/plugins: treat stale unknown <code>plugins.allow</code> ids as warnings instead of fatal config errors, so recovery commands like <code>plugins install</code>, <code>doctor --fix</code>, and <code>status</code> still run when a plugin is missing locally. Fixes #52992. Thanks @vincentkoc.</li>
<li>Doctor/WhatsApp: stop auto-enable from appending built-in channel ids like <code>whatsapp</code> to <code>plugins.allow</code>, so <code>openclaw doctor --fix</code> no longer writes schema-invalid plugin allowlist entries when repairing built-in channels. Fixes #52931. Thanks @vincentkoc.</li>
<li>Telegram/auto-reply: preserve same-chat inbound debounce order without stranding stale busy-session followups, and keep same-key overflow turns ordered when tracked debounce keys are saturated. (#52998) Thanks @osolmaz.</li>
<li>Discord/commands: return an explicit unauthorized reply for privileged native slash commands instead of falling through to Discord's misleading generic completion when auth gates reject the sender. Fixes #53041. Thanks @scoootscooob.</li>
<li>Channels/catalog: let external channel catalogs override shipped fallback metadata and honor overridden npm specs during channel setup, so custom channel catalogs no longer fall back to bundled packages when a channel id matches. (#52988)</li>
<li>Voice-call/Plivo: stabilize Plivo v2 replay keys so webhook retries and replay protection stop colliding on valid follow-up deliveries.</li>
<li>Agents/skills: prefer the active resolved runtime snapshot for embedded skill config and env injection, so <code>skills.entries.<skill>.apiKey</code> SecretRefs resolve correctly during embedded startup instead of failing on raw source config. Fixes #53098. Thanks @vincentkoc.</li>
<li>Agents/subagents: recheck timed-out worker waits against the latest runtime snapshot before sending completion events, so fast-finishing workers stop being reported as timed out when they actually succeeded. Fixes #53106. Thanks @vincentkoc.</li>
<li>Agents/Anthropic: preserve latest assistant thinking and redacted-thinking block ordering during transcript image sanitization so follow-up turns do not trip Anthropic's unmodified-thinking validation. (#52961) Thanks @vincentkoc.</li>
<li>Gateway/probe: stop successful gateway handshakes from timing out as unreachable while post-connect detail RPCs are still loading, so slow devices report a reachable RPC failure instead of a false negative dead gateway. Fixes #52927. Thanks @vincentkoc.</li>
<li>Gateway/supervision: stop lock conflicts from crash-looping under launchd and systemd by keeping the duplicate process in a retry wait instead of exiting as a failure while another healthy gateway still owns the lock. Fixes #52922. Thanks @vincentkoc.</li>
<li>Gateway/auth: require auth for canvas routes and admin scope for agent session reset, so anonymous canvas access and non-admin reset requests fail closed.</li>
<li>Release/install: keep previously released bundled plugins and Control UI assets in published openclaw npm installs, and fail release checks when those shipped artifacts are missing. Thanks @vincentkoc.</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.3.23/OpenClaw-2026.3.23.zip" length="24522883" type="application/octet-stream" sparkle:edSignature="ptBgHYLBqq/TSdONYCfIB5d6aP/ij/9G0gYQ5mJI9jf8Y31sbQIh5CqpJVxEEWLTMIGQKsHQir/kXZjtRvvZAg=="/>
</item>
<item>
<title>2026.3.13</title>
<pubDate>Sat, 14 Mar 2026 05:19:48 +0000</pubDate>
@@ -78,171 +171,5 @@
]]></description>
<enclosure url="https://github.com/openclaw/openclaw/releases/download/v2026.3.13/OpenClaw-2026.3.13.zip" length="23640917" type="application/octet-stream" sparkle:edSignature="Me63UHSpFLocTo5Lt7Iqsl0Hq61y3jTcZ9DUkiFl9xQvTE0+ORuqRMFWqPgYwfaKMgcgQmUbrV/uFzEoTIRHBA=="/>
</item>
<item>
<title>2026.3.12</title>
<pubDate>Fri, 13 Mar 2026 04:25:50 +0000</pubDate>
<link>https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml</link>
<sparkle:version>2026031290</sparkle:version>
<sparkle:shortVersionString>2026.3.12</sparkle:shortVersionString>
<sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion>
<description><![CDATA[<h2>OpenClaw 2026.3.12</h2>
<h3>Changes</h3>
<ul>
<li>Control UI/dashboard-v2: refresh the gateway dashboard with modular overview, chat, config, agent, and session views, plus a command palette, mobile bottom tabs, and richer chat tools like slash commands, search, export, and pinned messages. (#41503) Thanks @BunsDev.</li>
<li>OpenAI/GPT-5.4 fast mode: add configurable session-level fast toggles across <code>/fast</code>, TUI, Control UI, and ACP, with per-model config defaults and OpenAI/Codex request shaping.</li>
<li>Anthropic/Claude fast mode: map the shared <code>/fast</code> toggle and <code>params.fastMode</code> to direct Anthropic API-key <code>service_tier</code> requests, with live verification for both Anthropic and OpenAI fast-mode tiers.</li>
<li>Models/plugins: move Ollama, vLLM, and SGLang onto the provider-plugin architecture, with provider-owned onboarding, discovery, model-picker setup, and post-selection hooks so core provider wiring is more modular.</li>
<li>Docs/Kubernetes: Add a starter K8s install path with raw manifests, Kind setup, and deployment docs. Thanks @sallyom @dzianisv @egkristi</li>
<li>Agents/subagents: add <code>sessions_yield</code> so orchestrators can end the current turn immediately, skip queued tool work, and carry a hidden follow-up payload into the next session turn. (#36537) thanks @jriff</li>
<li>Slack/agent replies: support <code>channelData.slack.blocks</code> in the shared reply delivery path so agents can send Block Kit messages through standard Slack outbound delivery. (#44592) Thanks @vincentkoc.</li>
</ul>
<h3>Fixes</h3>
<ul>
<li>Security/device pairing: switch <code>/pair</code> and <code>openclaw qr</code> setup codes to short-lived bootstrap tokens so the next release no longer embeds shared gateway credentials in chat or QR pairing payloads. Thanks @lintsinghua.</li>
<li>Security/plugins: disable implicit workspace plugin auto-load so cloned repositories cannot execute workspace plugin code without an explicit trust decision. (<code>GHSA-99qw-6mr3-36qr</code>)(#44174) Thanks @lintsinghua and @vincentkoc.</li>
<li>Models/Kimi Coding: send <code>anthropic-messages</code> tools in native Anthropic format again so <code>kimi-coding</code> stops degrading tool calls into XML/plain-text pseudo invocations instead of real <code>tool_use</code> blocks. (#38669, #39907, #40552) Thanks @opriz.</li>
<li>TUI/chat log: reuse the active assistant message component for the same streaming run so <code>openclaw tui</code> no longer renders duplicate assistant replies. (#35364) Thanks @lisitan.</li>
<li>Telegram/model picker: make inline model button selections persist the chosen session model correctly, clear overrides when selecting the configured default, and include effective fallback models in <code>/models</code> button validation. (#40105) Thanks @avirweb.</li>
<li>Cron/proactive delivery: keep isolated direct cron sends out of the write-ahead resend queue so transient-send retries do not replay duplicate proactive messages after restart. (#40646) Thanks @openperf and @vincentkoc.</li>
<li>Models/Kimi Coding: send the built-in <code>User-Agent: claude-code/0.1.0</code> header by default for <code>kimi-coding</code> while still allowing explicit provider headers to override it, so Kimi Code subscription auth can work without a local header-injection proxy. (#30099) Thanks @Amineelfarssi and @vincentkoc.</li>
<li>Models/OpenAI Codex Spark: keep <code>gpt-5.3-codex-spark</code> working on the <code>openai-codex/*</code> path via resolver fallbacks and clearer Codex-only handling, while continuing to suppress the stale direct <code>openai/*</code> Spark row that OpenAI rejects live.</li>
<li>Ollama/Kimi Cloud: apply the Moonshot Kimi payload compatibility wrapper to Ollama-hosted Kimi models like <code>kimi-k2.5:cloud</code>, so tool routing no longer breaks when thinking is enabled. (#41519) Thanks @vincentkoc.</li>
<li>Moonshot CN API: respect explicit <code>baseUrl</code> (api.moonshot.cn) in implicit provider resolution so platform.moonshot.cn API keys authenticate correctly instead of returning HTTP 401. (#33637) Thanks @chengzhichao-xydt.</li>
<li>Kimi Coding/provider config: respect explicit <code>models.providers["kimi-coding"].baseUrl</code> when resolving the implicit provider so custom Kimi Coding endpoints no longer get overwritten by the built-in default. (#36353) Thanks @2233admin.</li>
<li>Gateway/main-session routing: keep TUI and other <code>mode:UI</code> main-session sends on the internal surface when <code>deliver</code> is enabled, so replies no longer inherit the session's persisted Telegram/WhatsApp route. (#43918) Thanks @obviyus.</li>
<li>BlueBubbles/self-chat echo dedupe: drop reflected duplicate webhook copies only when a matching <code>fromMe</code> event was just seen for the same chat, body, and timestamp, preventing self-chat loops without broad webhook suppression. Related to #32166. (#38442) Thanks @vincentkoc.</li>
<li>iMessage/self-chat echo dedupe: drop reflected duplicate copies only when a matching <code>is_from_me</code> event was just seen for the same chat, text, and <code>created_at</code>, preventing self-chat loops without broad text-only suppression. Related to #32166. (#38440) Thanks @vincentkoc.</li>
<li>Subagents/completion announce retries: raise the default announce timeout to 90 seconds and stop retrying gateway-timeout failures for externally delivered completion announces, preventing duplicate user-facing completion messages after slow gateway responses. Fixes #41235. Thanks @vasujain00 and @vincentkoc.</li>
<li>Mattermost/block streaming: fix duplicate message delivery (one threaded, one top-level) when block streaming is active by excluding <code>replyToId</code> from the block reply dedup key and adding an explicit <code>threading</code> dock to the Mattermost plugin. (#41362) Thanks @mathiasnagler and @vincentkoc.</li>
<li>Mattermost/reply media delivery: pass agent-scoped <code>mediaLocalRoots</code> through shared reply delivery so allowed local files upload correctly from button, slash-command, and model-picker replies. (#44021) Thanks @LyleLiu666.</li>
<li>macOS/Reminders: add the missing <code>NSRemindersUsageDescription</code> to the bundled app so <code>apple-reminders</code> can trigger the system permission prompt from OpenClaw.app. (#8559) Thanks @dinakars777.</li>
<li>Gateway/session discovery: discover disk-only and retired ACP session stores under custom templated <code>session.store</code> roots so ACP reconciliation, session-id/session-label targeting, and run-id fallback keep working after restart. (#44176) thanks @gumadeiras.</li>
<li>Plugins/env-scoped roots: fix plugin discovery/load caches and provenance tracking so same-process <code>HOME</code>/<code>OPENCLAW_HOME</code> changes no longer reuse stale plugin state or misreport <code>~/...</code> plugins as untracked. (#44046) thanks @gumadeiras.</li>
<li>Models/OpenRouter native ids: canonicalize native OpenRouter model keys across config writes, runtime lookups, fallback management, and <code>models list --plain</code>, and migrate legacy duplicated <code>openrouter/openrouter/...</code> config entries forward on write.</li>
<li>Windows/native update: make package installs use the npm update path instead of the git path, carry portable Git into native Windows updates, and mirror the installer's Windows npm env so <code>openclaw update</code> no longer dies early on missing <code>git</code> or <code>node-llama-cpp</code> download setup.</li>
<li>Sandbox/write: preserve pinned mutation-helper payload stdin so sandboxed <code>write</code> no longer reports success while creating empty files. (#43876) Thanks @glitch418x.</li>
<li>Security/exec approvals: escape invisible Unicode format characters in approval prompts so zero-width command text renders as visible <code>\u{...}</code> escapes instead of spoofing the reviewed command. (<code>GHSA-pcqg-f7rg-xfvv</code>)(#43687) Thanks @EkiXu and @vincentkoc.</li>
<li>Hooks/loader: fail closed when workspace hook paths cannot be resolved with <code>realpath</code>, so unreadable or broken internal hook paths are skipped instead of falling back to unresolved imports. (#44437) Thanks @vincentkoc.</li>
<li>Hooks/agent deliveries: dedupe repeated hook requests by optional idempotency key so webhook retries can reuse the first run instead of launching duplicate agent executions. (#44438) Thanks @vincentkoc.</li>
<li>Security/exec detection: normalize compatibility Unicode and strip invisible formatting code points before obfuscation checks so zero-width and fullwidth command tricks no longer suppress heuristic detection. (<code>GHSA-9r3v-37xh-2cf6</code>)(#44091) Thanks @wooluo and @vincentkoc.</li>
<li>Security/exec allowlist: preserve POSIX case sensitivity and keep <code>?</code> within a single path segment so exact-looking allowlist patterns no longer overmatch executables across case or directory boundaries. (<code>GHSA-f8r2-vg7x-gh8m</code>)(#43798) Thanks @zpbrent and @vincentkoc.</li>
<li>Security/commands: require sender ownership for <code>/config</code> and <code>/debug</code> so authorized non-owner senders can no longer reach owner-only config and runtime debug surfaces. (<code>GHSA-r7vr-gr74-94p8</code>)(#44305) Thanks @tdjackey and @vincentkoc.</li>
<li>Security/gateway auth: clear unbound client-declared scopes on shared-token WebSocket connects so device-less shared-token operators cannot self-declare elevated scopes. (<code>GHSA-rqpp-rjj8-7wv8</code>)(#44306) Thanks @LUOYEcode and @vincentkoc.</li>
<li>Security/browser.request: block persistent browser profile create/delete routes from write-scoped <code>browser.request</code> so callers can no longer persist admin-only browser profile changes through the browser control surface. (<code>GHSA-vmhq-cqm9-6p7q</code>)(#43800) Thanks @tdjackey and @vincentkoc.</li>
<li>Security/agent: reject public spawned-run lineage fields and keep workspace inheritance on the internal spawned-session path so external <code>agent</code> callers can no longer override the gateway workspace boundary. (<code>GHSA-2rqg-gjgv-84jm</code>)(#43801) Thanks @tdjackey and @vincentkoc.</li>
<li>Security/session_status: enforce sandbox session-tree visibility and shared agent-to-agent access guards before reading or mutating target session state, so sandboxed subagents can no longer inspect parent session metadata or write parent model overrides via <code>session_status</code>. (<code>GHSA-wcxr-59v9-rxr8</code>)(#43754) Thanks @tdjackey and @vincentkoc.</li>
<li>Security/agent tools: mark <code>nodes</code> as explicitly owner-only and document/test that <code>canvas</code> remains a shared trusted-operator surface unless a real boundary bypass exists.</li>
<li>Security/exec approvals: fail closed for Ruby approval flows that use <code>-r</code>, <code>--require</code>, or <code>-I</code> so approval-backed commands no longer bind only the main script while extra local code-loading flags remain outside the reviewed file snapshot.</li>
<li>Security/device pairing: cap issued and verified device-token scopes to each paired device's approved scope baseline so stale or overbroad tokens cannot exceed approved access. (<code>GHSA-2pwv-x786-56f8</code>)(#43686) Thanks @tdjackey and @vincentkoc.</li>
<li>Docs/onboarding: align the legacy wizard reference and <code>openclaw onboard</code> command docs with the Ollama onboarding flow so all onboarding reference paths now document <code>--auth-choice ollama</code>, Cloud + Local mode, and non-interactive usage. (#43473) Thanks @BruceMacD.</li>
<li>Models/secrets: enforce source-managed SecretRef markers in generated <code>models.json</code> so runtime-resolved provider secrets are not persisted when runtime projection is skipped. (#43759) Thanks @joshavant.</li>
<li>Security/WebSocket preauth: shorten unauthenticated handshake retention and reject oversized pre-auth frames before application-layer parsing to reduce pre-pairing exposure on unsupported public deployments. (<code>GHSA-jv4g-m82p-2j93</code>)(#44089) (<code>GHSA-xwx2-ppv2-wx98</code>)(#44089) Thanks @ez-lbz and @vincentkoc.</li>
<li>Security/proxy attachments: restore the shared media-store size cap for persisted browser proxy files so oversized payloads are rejected instead of overriding the intended 5 MB limit. (<code>GHSA-6rph-mmhp-h7h9</code>)(#43684) Thanks @tdjackey and @vincentkoc.</li>
<li>Security/host env: block inherited <code>GIT_EXEC_PATH</code> from sanitized host exec environments so Git helper resolution cannot be steered by host environment state. (<code>GHSA-jf5v-pqgw-gm5m</code>)(#43685) Thanks @zpbrent and @vincentkoc.</li>
<li>Security/Feishu webhook: require <code>encryptKey</code> alongside <code>verificationToken</code> in webhook mode so unsigned forged events are rejected instead of being processed with token-only configuration. (<code>GHSA-g353-mgv3-8pcj</code>)(#44087) Thanks @lintsinghua and @vincentkoc.</li>
<li>Security/Feishu reactions: preserve looked-up group chat typing and fail closed on ambiguous reaction context so group authorization and mention gating cannot be bypassed through synthetic <code>p2p</code> reactions. (<code>GHSA-m69h-jm2f-2pv8</code>)(#44088) Thanks @zpbrent and @vincentkoc.</li>
<li>Security/LINE webhook: require signatures for empty-event POST probes too so unsigned requests no longer confirm webhook reachability with a <code>200</code> response. (<code>GHSA-mhxh-9pjm-w7q5</code>)(#44090) Thanks @TerminalsandCoffee and @vincentkoc.</li>
<li>Security/Zalo webhook: rate limit invalid secret guesses before auth so weak webhook secrets cannot be brute-forced through unauthenticated churned requests without pre-auth <code>429</code> responses. (<code>GHSA-5m9r-p9g7-679c</code>)(#44173) Thanks @zpbrent and @vincentkoc.</li>
<li>Security/Zalouser groups: require stable group IDs for allowlist auth by default and gate mutable group-name matching behind <code>channels.zalouser.dangerouslyAllowNameMatching</code>. Thanks @zpbrent.</li>
<li>Security/Slack and Teams routing: require stable channel and team IDs for allowlist routing by default, with mutable name matching only via each channel's <code>dangerouslyAllowNameMatching</code> break-glass flag.</li>
<li>Security/exec approvals: fail closed for ambiguous inline loader and shell-payload script execution, bind the real script after POSIX shell value-taking flags, and unwrap <code>pnpm</code>/<code>npm exec</code>/<code>npx</code> script runners before approval binding. (<code>GHSA-57jw-9722-6rf2</code>)(<code>GHSA-jvqh-rfmh-jh27</code>)(<code>GHSA-x7pp-23xv-mmr4</code>)(<code>GHSA-jc5j-vg4r-j5jx</code>)(#44247) Thanks @tdjackey and @vincentkoc.</li>
<li>Doctor/gateway service audit: canonicalize service entrypoint paths before comparing them so symlink-vs-realpath installs no longer trigger false "entrypoint does not match the current install" repair prompts. (#43882) Thanks @ngutman.</li>
<li>Doctor/gateway service audit: earlier groundwork for this fix landed in the superseded #28338 branch. Thanks @realriphub.</li>
<li>Gateway/session stores: regenerate the Swift push-test protocol models and align Windows native session-store realpath handling so protocol checks and sync session discovery stop drifting on Windows. (#44266) thanks @jalehman.</li>
<li>Context engine/session routing: forward optional <code>sessionKey</code> through context-engine lifecycle calls so plugins can see structured routing metadata during bootstrap, assembly, post-turn ingestion, and compaction. (#44157) thanks @jalehman.</li>
<li>Agents/failover: classify z.ai <code>network_error</code> stop reasons as retryable timeouts so provider connectivity failures trigger fallback instead of surfacing raw unhandled-stop-reason errors. (#43884) Thanks @hougangdev.</li>
<li>Memory/session sync: add mode-aware post-compaction session reindexing with <code>agents.defaults.compaction.postIndexSync</code> plus <code>agents.defaults.memorySearch.sync.sessions.postCompactionForce</code>, so compacted session memory can refresh immediately without forcing every deployment into synchronous reindexing. (#25561) thanks @rodrigouroz.</li>
<li>Telegram/model picker: make inline model button selections persist the chosen session model correctly, clear overrides when selecting the configured default, and include effective fallback models in <code>/models</code> button validation. (#40105) Thanks @avirweb.</li>
<li>Telegram/native command sync: suppress expected <code>BOT_COMMANDS_TOO_MUCH</code> retry error noise, add a final fallback summary log, and document the difference between command-menu overflow and real Telegram network failures.</li>
<li>Mattermost/reply media delivery: pass agent-scoped <code>mediaLocalRoots</code> through shared reply delivery so allowed local files upload correctly from button, slash-command, and model-picker replies. (#44021) Thanks @LyleLiu666.</li>
<li>Plugins/env-scoped roots: fix plugin discovery/load caches and provenance tracking so same-process <code>HOME</code>/<code>OPENCLAW_HOME</code> changes no longer reuse stale plugin state or misreport <code>~/...</code> plugins as untracked. (#44046) thanks @gumadeiras.</li>
<li>Gateway/session discovery: discover disk-only and retired ACP session stores under custom templated <code>session.store</code> roots so ACP reconciliation, session-id/session-label targeting, and run-id fallback keep working after restart. (#44176) thanks @gumadeiras.</li>
<li>Models/OpenRouter native ids: canonicalize native OpenRouter model keys across config writes, runtime lookups, fallback management, and <code>models list --plain</code>, and migrate legacy duplicated <code>openrouter/openrouter/...</code> config entries forward on write.</li>
<li>Gateway/hooks: bucket hook auth failures by forwarded client IP behind trusted proxies and warn when <code>hooks.allowedAgentIds</code> leaves hook routing unrestricted.</li>
<li>Agents/compaction: skip the post-compaction <code>cache-ttl</code> marker write when a compaction completed in the same attempt, preventing the next turn from immediately triggering a second tiny compaction. (#28548) thanks @MoerAI.</li>
<li>Native chat/macOS: add <code>/new</code>, <code>/reset</code>, and <code>/clear</code> reset triggers, keep shared main-session aliases aligned, and ignore stale model-selection completions so native chat state stays in sync across reset and fast model changes. (#10898) Thanks @Nachx639.</li>
<li>Agents/compaction safeguard: route missing-model and missing-API-key cancellation warnings through the shared subsystem logger so they land in structured and file logs. (#9974) Thanks @dinakars777.</li>
<li>Cron/doctor: stop flagging canonical <code>agentTurn</code> and <code>systemEvent</code> payload kinds as legacy cron storage, while still normalizing whitespace-padded and non-canonical variants. (#44012) Thanks @shuicici.</li>
<li>ACP/client final-message delivery: preserve terminal assistant text snapshots before resolving <code>end_turn</code>, so ACP clients no longer drop the last visible reply when the gateway sends the final message body on the terminal chat event. (#17615) Thanks @pjeby.</li>
<li>Telegram/Discord status reactions: show a temporary compacting reaction during auto-compaction pauses and restore thinking afterward so the bot no longer appears frozen while context is being compacted. (#35474) thanks @Cypherm.</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.3.12/OpenClaw-2026.3.12.zip" length="23628700" type="application/octet-stream" sparkle:edSignature="o6Zdcw36l3I0jUg14H+RBqNwrhuuSsq1WMDi4tBRa1+5TC3VCVdFKZ2hzmH2Xjru9lDEzVMP8v2A6RexSbOCBQ=="/>
</item>
<item>
<title>2026.3.8-beta.1</title>
<pubDate>Mon, 09 Mar 2026 07:19:57 +0000</pubDate>
<link>https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml</link>
<sparkle:version>2026030801</sparkle:version>
<sparkle:shortVersionString>2026.3.8-beta.1</sparkle:shortVersionString>
<sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion>
<description><![CDATA[<h2>OpenClaw 2026.3.8-beta.1</h2>
<h3>Changes</h3>
<ul>
<li>CLI/backup: add <code>openclaw backup create</code> and <code>openclaw backup verify</code> for local state archives, including <code>--only-config</code>, <code>--no-include-workspace</code>, manifest/payload validation, and backup guidance in destructive flows. (#40163) thanks @shichangs.</li>
<li>macOS/onboarding: add a remote gateway token field for remote mode, preserve existing non-plaintext <code>gateway.remote.token</code> config values until explicitly replaced, and warn when the loaded token shape cannot be used directly from the macOS app. (#40187, supersedes #34614) Thanks @cgdusek.</li>
<li>Talk mode: add top-level <code>talk.silenceTimeoutMs</code> config so Talk waits a configurable amount of silence before auto-sending the current transcript, while keeping each platform's existing default pause window when unset. (#39607) Thanks @danodoesdesign. Fixes #17147.</li>
<li>TUI: infer the active agent from the current workspace when launched inside a configured agent workspace, while preserving explicit <code>agent:</code> session targets. (#39591) thanks @arceus77-7.</li>
<li>Tools/Brave web search: add opt-in <code>tools.web.search.brave.mode: "llm-context"</code> so <code>web_search</code> can call Brave's LLM Context endpoint and return extracted grounding snippets with source metadata, plus config/docs/test coverage. (#33383) Thanks @thirumaleshp.</li>
<li>CLI/install: include the short git commit hash in <code>openclaw --version</code> output when metadata is available, and keep installer version checks compatible with the decorated format. (#39712) thanks @sourman.</li>
<li>CLI/backup: improve archive naming for date sorting, add config-only backup mode, and harden backup planning, publication, and verification edge cases. (#40163) Thanks @gumadeiras.</li>
<li>ACP/Provenance: add optional ACP ingress provenance metadata and visible receipt injection (<code>openclaw acp --provenance off|meta|meta+receipt</code>) so OpenClaw agents can retain and report ACP-origin context with session trace IDs. (#40473) thanks @mbelinky.</li>
<li>Tools/web search: alphabetize provider ordering across runtime selection, onboarding/configure pickers, and config metadata, so provider lists stay neutral and multi-key auto-detect now prefers Grok before Kimi. (#40259) thanks @kesku.</li>
<li>Docs/Web search: restore $5/month free-credit details, replace defunct "Data for Search"/"Data for AI" plan names with current "Search" plan, and note legacy subscription validity in Brave setup docs. Follows up on #26860. (#40111) Thanks @remusao.</li>
<li>Extensions/ACPX tests: move the shared runtime fixture helper from <code>src/runtime-internals/</code> to <code>src/test-utils/</code> so the test-only helper no longer looks like shipped runtime code.</li>
</ul>
<h3>Fixes</h3>
<ul>
<li>macOS app/chat UI: route browser proxy through the local node browser service, preserve plain-text paste semantics, strip completed assistant trace/debug wrapper noise from transcripts, refresh permission state after returning from System Settings, and tolerate malformed cron rows in the macOS tab. (#39516) Thanks @Imhermes1.</li>
<li>Android/Play distribution: remove self-update, background location, <code>screen.record</code>, and background mic capture from the Android app, narrow the foreground service to <code>dataSync</code> only, and clean up the legacy <code>location.enabledMode=always</code> preference migration. (#39660) Thanks @obviyus.</li>
<li>Telegram/DM routing: dedupe inbound Telegram DMs per agent instead of per session key so the same DM cannot trigger duplicate replies when both <code>agent:main:main</code> and <code>agent:main:telegram:direct:<id></code> resolve for one agent. Fixes #40005. Supersedes #40116. (#40519) thanks @obviyus.</li>
<li>Cron/Telegram announce delivery: route text-only announce jobs through the real outbound adapters after finalizing descendant output so plain Telegram targets no longer report <code>delivered: true</code> when no message actually reached Telegram. (#40575) thanks @obviyus.</li>
<li>Matrix/DM routing: add safer fallback detection for broken <code>m.direct</code> homeservers, honor explicit room bindings over DM classification, and preserve room-bound agent selection for Matrix DM rooms. (#19736) Thanks @derbronko.</li>
<li>Feishu/plugin onboarding: clear the short-lived plugin discovery cache before reloading the registry after installing a channel plugin, so onboarding no longer re-prompts to download Feishu immediately after a successful install. Fixes #39642. (#39752) Thanks @GazeKingNuWu.</li>
<li>Plugins/channel onboarding: prefer bundled channel plugins over duplicate npm-installed copies during onboarding and release-channel sync, preventing bundled plugins from being shadowed by npm installs with the same plugin ID. (#40092)</li>
<li>Config/runtime snapshots: keep secrets-runtime-resolved config and auth-profile snapshots intact after config writes so follow-up reads still see file-backed secret values while picking up the persisted config update. (#37313) thanks @bbblending.</li>
<li>Gateway/Control UI: resolve bundled dashboard assets through symlinked global wrappers and auto-detected package roots, while keeping configured and custom roots on the strict hardlink boundary. (#40385) Thanks @LarytheLord.</li>
<li>Browser/extension relay: add <code>browser.relayBindHost</code> so the Chrome relay can bind to an explicit non-loopback address for WSL2 and other cross-namespace setups, while preserving loopback-only defaults. (#39364) Thanks @mvanhorn.</li>
<li>Browser/CDP: normalize loopback direct WebSocket CDP URLs back to HTTP(S) for <code>/json/*</code> tab operations so local <code>ws://</code> / <code>wss://</code> profiles can still list, focus, open, and close tabs after the new direct-WS support lands. (#31085) Thanks @shrey150.</li>
<li>Browser/CDP: rewrite wildcard <code>ws://0.0.0.0</code> and <code>ws://[::]</code> debugger URLs from remote <code>/json/version</code> responses back to the external CDP host/port, fixing Browserless-style container endpoints. (#17760) Thanks @joeharouni.</li>
<li>Browser/extension relay: wait briefly for a previously attached Chrome tab to reappear after transient relay drops before failing with <code>tab not found</code>, reducing noisy reconnect flakes. (#32461) Thanks @AaronWander.</li>
<li>macOS/Tailscale gateway discovery: keep Tailscale Serve probing alive when other remote gateways are already discovered, prefer direct transport for resolved <code>.ts.net</code> and Tailscale Serve gateways, and set <code>TERM=dumb</code> for GUI-launched Tailscale CLI discovery. (#40167) thanks @ngutman.</li>
<li>TUI/theme: detect light terminal backgrounds via <code>COLORFGBG</code> and pick a WCAG AA-compliant light palette, with <code>OPENCLAW_THEME=light|dark</code> override for terminals without auto-detection. (#38636) Thanks @ademczuk and @vincentkoc.</li>
<li>Agents/openai-codex: normalize <code>gpt-5.4</code> fallback transport back to <code>openai-codex-responses</code> on <code>chatgpt.com/backend-api</code> when config drifts to the generic OpenAI responses endpoint. (#38736) Thanks @0xsline.</li>
<li>Models/openai-codex GPT-5.4 forward-compat: use the GPT-5.4 1,050,000-token context window and 128,000 max tokens for <code>openai-codex/gpt-5.4</code> instead of inheriting stale legacy Codex limits in resolver fallbacks and model listing. (#37876) thanks @yuweuii.</li>
<li>Tools/web search: restore Perplexity OpenRouter/Sonar compatibility for legacy <code>OPENROUTER_API_KEY</code>, <code>sk-or-...</code>, and explicit <code>perplexity.baseUrl</code> / <code>model</code> setups while keeping direct Perplexity keys on the native Search API path. (#39937) Thanks @obviyus.</li>
<li>Agents/failover: detect Amazon Bedrock <code>Too many tokens per day</code> quota errors as rate limits across fallback, cron retry, and memory embeddings while keeping context-window <code>too many tokens per request</code> errors out of the rate-limit lane. (#39377) Thanks @gambletan.</li>
<li>Mattermost replies: keep <code>root_id</code> pinned to the existing thread root when an agent replies inside a thread, while still using reply-target threading for top-level posts. (#27744) thanks @hnykda.</li>
<li>Telegram/DM partial streaming: keep DM preview lanes on real message edits instead of native draft materialization so final replies no longer flash a second duplicate copy before collapsing back to one.</li>
<li>macOS overlays: fix VoiceWake, Talk, and Notify overlay exclusivity crashes by removing shared <code>inout</code> visibility mutation from <code>OverlayPanelFactory.present</code>, and add a repeated Talk overlay smoke test. (#39275, #39321) Thanks @fellanH.</li>
<li>macOS Talk Mode: set the speech recognition request <code>taskHint</code> to <code>.dictation</code> for mic capture, and add regression coverage for the request defaults. (#38445) Thanks @dmiv.</li>
<li>macOS release packaging: default <code>scripts/package-mac-app.sh</code> to universal binaries for <code>BUILD_CONFIG=release</code>, and clarify that <code>scripts/package-mac-dist.sh</code> already produces the release zip + DMG. (#33891) Thanks @cgdusek.</li>
<li>Hooks/session-memory: keep <code>/new</code> and <code>/reset</code> memory artifacts in the bound agent workspace and align saved reset session keys with that workspace when stale main-agent keys leak into the hook path. (#39875) thanks @rbutera.</li>
<li>Sessions/model switch: clear stale cached <code>contextTokens</code> when a session changes models so status and runtime paths recompute against the active model window. (#38044) thanks @yuweuii.</li>
<li>ACP/session history: persist transcripts for successful ACP child runs, preserve exact transcript text, record ACP spawned-session lineage, and keep spawn-time transcript-path persistence best-effort so history storage failures do not block execution. (#40137) thanks @mbelinky.</li>
<li>Docs/browser: add a layered WSL2 + Windows remote Chrome CDP troubleshooting guide, including Control UI origin pitfalls and extension-relay bind-address guidance. (#39407) Thanks @Owlock.</li>
<li>Context engine registry/bundled builds: share the registry state through a <code>globalThis</code> singleton so duplicated bundled module copies can resolve engines registered by each other at runtime, with regression coverage for duplicate-module imports. (#40115) thanks @jalehman.</li>
<li>Podman/setup: fix <code>cannot chdir: Permission denied</code> in <code>run_as_user</code> when <code>setup-podman.sh</code> is invoked from a directory the target user cannot access, by wrapping user-switch calls in a subshell that cd's to <code>/tmp</code> with <code>/</code> fallback. (#39435) Thanks @langdon and @jlcbk.</li>
<li>Podman/SELinux: auto-detect SELinux enforcing/permissive mode and add <code>:Z</code> relabel to bind mounts in <code>run-openclaw-podman.sh</code> and the Quadlet template, fixing <code>EACCES</code> on Fedora/RHEL hosts. Supports <code>OPENCLAW_BIND_MOUNT_OPTIONS</code> override. (#39449) Thanks @langdon and @githubbzxs.</li>
<li>Agents/context-engine plugins: bootstrap runtime plugins once at embedded-run, compaction, and subagent boundaries so plugin-provided context engines and hooks load from the active workspace before runtime resolution. (#40232)</li>
<li>Docs/Changelog: correct the contributor credit for the bundled Control UI global-install fix to @LarytheLord. (#40420) Thanks @velvet-shark.</li>
<li>Telegram/media downloads: time out only stalled body reads so polling recovers from hung file downloads without aborting slow downloads that are still streaming data. (#40098) thanks @tysoncung.</li>
<li>Docker/runtime image: prune dev dependencies, strip build-only dist metadata for smaller Docker images. (#40307) Thanks @vincentkoc.</li>
<li>Gateway/restart timeout recovery: exit non-zero when restart-triggered shutdown drains time out so launchd/systemd restart the gateway instead of treating the failed restart as a clean stop. Landed from contributor PR #40380 by @dsantoreis. Thanks @dsantoreis.</li>
<li>Gateway/config restart guard: validate config before service start/restart and keep post-SIGUSR1 startup failures from crashing the gateway process, reducing invalid-config restart loops and macOS permission loss. Landed from contributor PR #38699 by @lml2468. Thanks @lml2468.</li>
<li>Gateway/launchd respawn detection: treat <code>XPC_SERVICE_NAME</code> as a launchd supervision hint so macOS restarts exit cleanly under launchd instead of attempting detached self-respawn. Landed from contributor PR #20555 by @dimat. Thanks @dimat.</li>
<li>Telegram/poll restart cleanup: abort the in-flight Telegram API fetch when shutdown or forced polling restarts stop a runner, preventing stale <code>getUpdates</code> long polls from colliding with the replacement runner. Landed from contributor PR #23950 by @Gkinthecodeland. Thanks @Gkinthecodeland.</li>
<li>Cron/restart catch-up staggering: limit immediate missed-job replay on startup and reschedule the deferred remainder from the post-catchup clock so restart bursts do not starve the gateway or silently skip overdue recurring jobs. Landed from contributor PR #18925 by @rexlunae. Thanks @rexlunae.</li>
<li>Cron/owner-only tools: pass trusted isolated cron runs into the embedded agent with owner context so <code>cron</code>/<code>gateway</code> tooling remains available after the owner-auth hardening narrowed direct-message ownership inference.</li>
<li>Browser/SSRF: block private-network intermediate redirect hops in strict browser navigation flows and fail closed when remote tab-open paths cannot inspect redirect chains. Thanks @zpbrent.</li>
<li>MS Teams/authz: keep <code>groupPolicy: "allowlist"</code> enforcing sender allowlists even when a team/channel route allowlist is configured, so route matches no longer widen group access to every sender in that route. Thanks @zpbrent.</li>
<li>Security/system.run: bind approved <code>bun</code> and <code>deno run</code> script operands to on-disk file snapshots so post-approval script rewrites are denied before execution.</li>
<li>Skills/download installs: pin the validated per-skill tools root before writing downloaded archives, so rebinding the lexical tools path cannot redirect download writes outside the intended tools directory. Thanks @tdjackey.</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.3.8-beta.1/OpenClaw-2026.3.8-beta.1.zip" length="23407015" type="application/octet-stream" sparkle:edSignature="KCqhSmu4b0tHf55RqcQOHorsc55CgBI5BUmK/NTizxNq04INn/7QvsamHYQou9DbB2IW6B2nawBC4nn4au5yDA=="/>
</item>
</channel>
</rss>
</rss>

View File

@@ -27,14 +27,34 @@ Status: **extremely alpha**. The app is actively being rebuilt from the ground u
```bash
cd apps/android
./gradlew :app:assembleDebug
./gradlew :app:installDebug
./gradlew :app:testDebugUnitTest
./gradlew :app:assemblePlayDebug
./gradlew :app:installPlayDebug
./gradlew :app:testPlayDebugUnitTest
cd ../..
bun run android:bundle:release
```
`bun run android:bundle:release` auto-bumps Android `versionName`/`versionCode` in `apps/android/app/build.gradle.kts`, then builds a signed release `.aab`.
Third-party debug flavor:
```bash
cd apps/android
./gradlew :app:assembleThirdPartyDebug
./gradlew :app:installThirdPartyDebug
./gradlew :app:testThirdPartyDebugUnitTest
```
`bun run android:bundle:release` auto-bumps Android `versionName`/`versionCode` in `apps/android/app/build.gradle.kts`, then builds two signed release bundles:
- Play build: `apps/android/build/release-bundles/openclaw-<version>-play-release.aab`
- Third-party build: `apps/android/build/release-bundles/openclaw-<version>-third-party-release.aab`
Flavor-specific direct Gradle tasks:
```bash
cd apps/android
./gradlew :app:bundlePlayRelease
./gradlew :app:bundleThirdPartyRelease
```
## Kotlin Lint + Format
@@ -176,6 +196,48 @@ More details: `docs/platforms/android.md`.
- `CAMERA` for `camera.snap` and `camera.clip`
- `RECORD_AUDIO` for `camera.clip` when `includeAudio=true`
## Google Play Restricted Permissions
As of March 19, 2026, these manifest permissions are the main Google Play policy risk for this app:
- `READ_SMS`
- `SEND_SMS`
- `READ_CALL_LOG`
Why these matter:
- Google Play treats SMS and Call Log access as highly restricted. In most cases, Play only allows them for the default SMS app, default Phone app, default Assistant, or a narrow policy exception.
- Review usually involves a `Permissions Declaration Form`, policy justification, and demo video evidence in Play Console.
- If we want a Play-safe build, these should be the first permissions removed behind a dedicated product flavor / variant.
Current OpenClaw Android implication:
- APK / sideload build can keep SMS and Call Log features.
- Google Play build should exclude SMS send/search and Call Log search unless the product is intentionally positioned and approved as a default-handler exception case.
- The repo now ships this split as Android product flavors:
- `play`: removes `READ_SMS`, `SEND_SMS`, and `READ_CALL_LOG`, and hides SMS / Call Log surfaces in onboarding, settings, and advertised node capabilities.
- `thirdParty`: keeps the full permission set and the existing SMS / Call Log functionality.
Policy links:
- [Google Play SMS and Call Log policy](https://support.google.com/googleplay/android-developer/answer/10208820?hl=en)
- [Google Play sensitive permissions policy hub](https://support.google.com/googleplay/android-developer/answer/16558241)
- [Android default handlers guide](https://developer.android.com/guide/topics/permissions/default-handlers)
Other Play-restricted surfaces to watch if added later:
- `ACCESS_BACKGROUND_LOCATION`
- `MANAGE_EXTERNAL_STORAGE`
- `QUERY_ALL_PACKAGES`
- `REQUEST_INSTALL_PACKAGES`
- `AccessibilityService`
Reference links:
- [Background location policy](https://support.google.com/googleplay/android-developer/answer/9799150)
- [AccessibilityService policy](https://support.google.com/googleplay/android-developer/answer/10964491?hl=en-GB)
- [Photo and Video Permissions policy](https://support.google.com/googleplay/android-developer/answer/14594990)
## Integration Capability Test (Preconditioned)
This suite assumes setup is already done manually. It does **not** install/run/pair automatically.

View File

@@ -65,14 +65,29 @@ android {
applicationId = "ai.openclaw.app"
minSdk = 31
targetSdk = 36
versionCode = 2026031400
versionName = "2026.3.14"
versionCode = 2026032500
versionName = "2026.3.25"
ndk {
// Support all major ABIs — native libs are tiny (~47 KB per ABI)
abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
}
}
flavorDimensions += "store"
productFlavors {
create("play") {
dimension = "store"
buildConfigField("boolean", "OPENCLAW_ENABLE_SMS", "false")
buildConfigField("boolean", "OPENCLAW_ENABLE_CALL_LOG", "false")
}
create("thirdParty") {
dimension = "store"
buildConfigField("boolean", "OPENCLAW_ENABLE_SMS", "true")
buildConfigField("boolean", "OPENCLAW_ENABLE_CALL_LOG", "true")
}
}
buildTypes {
release {
if (hasAndroidReleaseSigning) {
@@ -140,8 +155,13 @@ androidComponents {
.forEach { output ->
val versionName = output.versionName.orNull ?: "0"
val buildType = variant.buildType
val outputFileName = "openclaw-$versionName-$buildType.apk"
val flavorName = variant.flavorName?.takeIf { it.isNotBlank() }
val outputFileName =
if (flavorName == null) {
"openclaw-$versionName-$buildType.apk"
} else {
"openclaw-$versionName-$flavorName-$buildType.apk"
}
output.outputFileName = outputFileName
}
}

View File

@@ -12,6 +12,7 @@
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.SEND_SMS" />
<uses-permission android:name="android.permission.READ_SMS" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />
<uses-permission

View File

@@ -129,7 +129,13 @@ class MainViewModel(app: Application) : AndroidViewModel(app) {
fun setForeground(value: Boolean) {
foreground = value
runtimeRef.value?.setForeground(value)
val runtime =
if (value && prefs.onboardingCompleted.value) {
ensureRuntime()
} else {
runtimeRef.value
}
runtime?.setForeground(value)
}
fun setDisplayName(value: String) {
@@ -231,6 +237,10 @@ class MainViewModel(app: Application) : AndroidViewModel(app) {
ensureRuntime().handleCanvasA2UIActionFromWebView(payloadJson)
}
fun isTrustedCanvasActionUrl(rawUrl: String?): Boolean {
return ensureRuntime().isTrustedCanvasActionUrl(rawUrl)
}
fun requestCanvasRehydrate(source: String = "screen_tab") {
ensureRuntime().requestCanvasRehydrate(source = source, force = true)
}

View File

@@ -89,6 +89,8 @@ class NodeRuntime(
private val deviceHandler: DeviceHandler = DeviceHandler(
appContext = appContext,
smsEnabled = BuildConfig.OPENCLAW_ENABLE_SMS,
callLogEnabled = BuildConfig.OPENCLAW_ENABLE_CALL_LOG,
)
private val notificationsHandler: NotificationsHandler = NotificationsHandler(
@@ -137,7 +139,9 @@ class NodeRuntime(
voiceWakeMode = { VoiceWakeMode.Off },
motionActivityAvailable = { motionHandler.isActivityAvailable() },
motionPedometerAvailable = { motionHandler.isPedometerAvailable() },
smsAvailable = { sms.canSendSms() },
sendSmsAvailable = { BuildConfig.OPENCLAW_ENABLE_SMS && sms.canSendSms() },
readSmsAvailable = { BuildConfig.OPENCLAW_ENABLE_SMS && sms.canReadSms() },
callLogAvailable = { BuildConfig.OPENCLAW_ENABLE_CALL_LOG },
hasRecordAudioPermission = { hasRecordAudioPermission() },
manualTls = { manualTls.value },
)
@@ -160,7 +164,9 @@ class NodeRuntime(
isForeground = { _isForeground.value },
cameraEnabled = { cameraEnabled.value },
locationEnabled = { locationMode.value != LocationMode.Off },
smsAvailable = { sms.canSendSms() },
sendSmsAvailable = { BuildConfig.OPENCLAW_ENABLE_SMS && sms.canSendSms() },
readSmsAvailable = { BuildConfig.OPENCLAW_ENABLE_SMS && sms.canReadSms() },
callLogAvailable = { BuildConfig.OPENCLAW_ENABLE_CALL_LOG },
debugBuild = { BuildConfig.DEBUG },
refreshNodeCanvasCapability = { nodeSession.refreshNodeCanvasCapability() },
onCanvasA2uiPush = {
@@ -566,43 +572,8 @@ class NodeRuntime(
scope.launch(Dispatchers.Default) {
gateways.collect { list ->
if (list.isNotEmpty()) {
// Security: don't let an unauthenticated discovery feed continuously steer autoconnect.
// UX parity with iOS: only set once when unset.
if (lastDiscoveredStableId.value.trim().isEmpty()) {
prefs.setLastDiscoveredStableId(list.first().stableId)
}
}
if (didAutoConnect) return@collect
if (_isConnected.value) return@collect
if (manualEnabled.value) {
val host = manualHost.value.trim()
val port = manualPort.value
if (host.isNotEmpty() && port in 1..65535) {
// Security: autoconnect only to previously trusted gateways (stored TLS pin).
if (!manualTls.value) return@collect
val stableId = GatewayEndpoint.manual(host = host, port = port).stableId
val storedFingerprint = prefs.loadGatewayTlsFingerprint(stableId)?.trim().orEmpty()
if (storedFingerprint.isEmpty()) return@collect
didAutoConnect = true
connect(GatewayEndpoint.manual(host = host, port = port))
}
return@collect
}
val targetStableId = lastDiscoveredStableId.value.trim()
if (targetStableId.isEmpty()) return@collect
val target = list.firstOrNull { it.stableId == targetStableId } ?: return@collect
// Security: autoconnect only to previously trusted gateways (stored TLS pin).
val storedFingerprint = prefs.loadGatewayTlsFingerprint(target.stableId)?.trim().orEmpty()
if (storedFingerprint.isEmpty()) return@collect
didAutoConnect = true
connect(target)
seedLastDiscoveredGateway(list)
autoConnectIfNeeded()
}
}
@@ -627,11 +598,53 @@ class NodeRuntime(
fun setForeground(value: Boolean) {
_isForeground.value = value
if (!value) {
if (value) {
reconnectPreferredGatewayOnForeground()
} else {
stopActiveVoiceSession()
}
}
private fun seedLastDiscoveredGateway(list: List<GatewayEndpoint>) {
if (list.isEmpty()) return
if (lastDiscoveredStableId.value.trim().isNotEmpty()) return
prefs.setLastDiscoveredStableId(list.first().stableId)
}
private fun resolvePreferredGatewayEndpoint(): GatewayEndpoint? {
if (manualEnabled.value) {
val host = manualHost.value.trim()
val port = manualPort.value
if (host.isEmpty() || port !in 1..65535) return null
return GatewayEndpoint.manual(host = host, port = port)
}
val targetStableId = lastDiscoveredStableId.value.trim()
if (targetStableId.isEmpty()) return null
val endpoint = gateways.value.firstOrNull { it.stableId == targetStableId } ?: return null
val storedFingerprint = prefs.loadGatewayTlsFingerprint(endpoint.stableId)?.trim().orEmpty()
if (storedFingerprint.isEmpty()) return null
return endpoint
}
private fun autoConnectIfNeeded() {
if (didAutoConnect) return
if (_isConnected.value) return
val endpoint = resolvePreferredGatewayEndpoint() ?: return
didAutoConnect = true
connect(endpoint)
}
private fun reconnectPreferredGatewayOnForeground() {
if (_isConnected.value) return
if (_pendingGatewayTrust.value != null) return
if (connectedEndpoint != null) {
refreshGatewayConnection()
return
}
resolvePreferredGatewayEndpoint()?.let(::connect)
}
fun setDisplayName(value: String) {
prefs.setDisplayName(value)
}
@@ -891,6 +904,10 @@ class NodeRuntime(
}
}
fun isTrustedCanvasActionUrl(rawUrl: String?): Boolean {
return a2uiHandler.isTrustedCanvasActionUrl(rawUrl)
}
fun loadChat(sessionKey: String) {
val key = sessionKey.trim().ifEmpty { resolveMainSessionKey() }
chat.load(key)

View File

@@ -4,6 +4,8 @@ import android.content.pm.PackageManager
import android.content.Intent
import android.Manifest
import android.net.Uri
import android.os.Handler
import android.os.Looper
import android.provider.Settings
import androidx.appcompat.app.AlertDialog
import androidx.activity.ComponentActivity
@@ -11,17 +13,21 @@ import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat
import androidx.core.app.ActivityCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import kotlinx.coroutines.suspendCancellableCoroutine
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.coroutines.resume
class PermissionRequester(private val activity: ComponentActivity) {
private val mutex = Mutex()
private var pending: CompletableDeferred<Map<String, Boolean>>? = null
private val mainHandler = Handler(Looper.getMainLooper())
private val launcher: ActivityResultLauncher<Array<String>> =
activity.registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { result ->
@@ -86,32 +92,84 @@ class PermissionRequester(private val activity: ComponentActivity) {
private suspend fun showRationaleDialog(permissions: List<String>): Boolean =
withContext(Dispatchers.Main) {
if (activity.isFinishing || activity.isDestroyed) {
return@withContext false
}
suspendCancellableCoroutine { cont ->
AlertDialog.Builder(activity)
.setTitle("Permission required")
.setMessage(buildRationaleMessage(permissions))
.setPositiveButton("Continue") { _, _ -> cont.resume(true) }
.setNegativeButton("Not now") { _, _ -> cont.resume(false) }
.setOnCancelListener { cont.resume(false) }
.show()
val lifecycle = activity.lifecycle
var dialog: AlertDialog? = null
var observer: LifecycleEventObserver? = null
val finished = AtomicBoolean(false)
val removeObserver = {
observer?.let(lifecycle::removeObserver)
observer = null
}
fun finish(result: Boolean?) {
if (!finished.compareAndSet(false, true)) return
removeObserver()
dialog?.dismiss()
if (result != null) {
cont.resume(result)
}
}
val actualObserver =
LifecycleEventObserver { _, event ->
if (event != Lifecycle.Event.ON_DESTROY) return@LifecycleEventObserver
finish(false)
}
observer = actualObserver
lifecycle.addObserver(actualObserver)
cont.invokeOnCancellation {
mainHandler.post {
finish(null)
}
}
dialog =
AlertDialog.Builder(activity)
.setTitle("Permission required")
.setMessage(buildRationaleMessage(permissions))
.setPositiveButton("Continue") { _, _ -> finish(true) }
.setNegativeButton("Not now") { _, _ -> finish(false) }
.setOnCancelListener { finish(false) }
.show()
}
}
private fun showSettingsDialog(permissions: List<String>) {
AlertDialog.Builder(activity)
.setTitle("Enable permission in Settings")
.setMessage(buildSettingsMessage(permissions))
.setPositiveButton("Open Settings") { _, _ ->
val intent =
Intent(
Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
Uri.fromParts("package", activity.packageName, null),
)
activity.startActivity(intent)
private suspend fun showSettingsDialog(permissions: List<String>) =
withContext(Dispatchers.Main) {
if (activity.isFinishing || activity.isDestroyed) return@withContext
val lifecycle = activity.lifecycle
var dialog: AlertDialog? = null
var observer: LifecycleEventObserver? = null
val removeObserver = {
observer?.let(lifecycle::removeObserver)
observer = null
}
.setNegativeButton("Cancel", null)
.show()
}
val actualObserver =
LifecycleEventObserver { _, event ->
if (event != Lifecycle.Event.ON_DESTROY) return@LifecycleEventObserver
removeObserver()
dialog?.dismiss()
}
observer = actualObserver
lifecycle.addObserver(actualObserver)
dialog =
AlertDialog.Builder(activity)
.setTitle("Enable permission in Settings")
.setMessage(buildSettingsMessage(permissions))
.setPositiveButton("Open Settings") { _, _ ->
if (activity.isFinishing || activity.isDestroyed) return@setPositiveButton
val intent =
Intent(
Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
Uri.fromParts("package", activity.packageName, null),
)
activity.startActivity(intent)
}
.setNegativeButton("Cancel", null)
.setOnDismissListener { removeObserver() }
.show()
}
private fun buildRationaleMessage(permissions: List<String>): String {
val labels = permissions.map { permissionLabel(it) }

View File

@@ -75,7 +75,7 @@ class ChatController(
fun load(sessionKey: String) {
val key = sessionKey.trim().ifEmpty { "main" }
_sessionKey.value = key
scope.launch { bootstrap(forceHealth = true) }
scope.launch { bootstrap(forceHealth = true, refreshSessions = true) }
}
fun applyMainSessionKey(mainSessionKey: String) {
@@ -84,11 +84,11 @@ class ChatController(
if (_sessionKey.value == trimmed) return
if (_sessionKey.value != "main") return
_sessionKey.value = trimmed
scope.launch { bootstrap(forceHealth = true) }
scope.launch { bootstrap(forceHealth = true, refreshSessions = true) }
}
fun refresh() {
scope.launch { bootstrap(forceHealth = true) }
scope.launch { bootstrap(forceHealth = true, refreshSessions = true) }
}
fun refreshSessions(limit: Int? = null) {
@@ -106,7 +106,9 @@ class ChatController(
if (key.isEmpty()) return
if (key == _sessionKey.value) return
_sessionKey.value = key
scope.launch { bootstrap(forceHealth = true) }
// Keep the thread switch path lean: history + health are needed immediately,
// but the session list is usually unchanged and can refresh on explicit pull-to-refresh.
scope.launch { bootstrap(forceHealth = true, refreshSessions = false) }
}
fun sendMessage(
@@ -249,7 +251,7 @@ class ChatController(
}
}
private suspend fun bootstrap(forceHealth: Boolean) {
private suspend fun bootstrap(forceHealth: Boolean, refreshSessions: Boolean) {
_errorText.value = null
_healthOk.value = false
clearPendingRuns()
@@ -271,7 +273,9 @@ class ChatController(
history.thinkingLevel?.trim()?.takeIf { it.isNotEmpty() }?.let { _thinkingLevel.value = it }
pollHealthIfNeeded(force = forceHealth)
fetchSessions(limit = 50)
if (refreshSessions) {
fetchSessions(limit = 50)
}
} catch (err: Throwable) {
_errorText.value = err.message
}

View File

@@ -13,6 +13,13 @@ class A2UIHandler(
private val getNodeCanvasHostUrl: () -> String?,
private val getOperatorCanvasHostUrl: () -> String?,
) {
fun isTrustedCanvasActionUrl(rawUrl: String?): Boolean {
return CanvasActionTrust.isTrustedCanvasActionUrl(
rawUrl = rawUrl,
trustedA2uiUrls = listOfNotNull(resolveA2uiHostUrl()),
)
}
fun resolveA2uiHostUrl(): String? {
val nodeRaw = getNodeCanvasHostUrl()?.trim().orEmpty()
val operatorRaw = getOperatorCanvasHostUrl()?.trim().orEmpty()

View File

@@ -121,42 +121,48 @@ class CameraCaptureManager(private val context: Context) {
(rotated.height.toDouble() * (maxWidth.toDouble() / rotated.width.toDouble()))
.toInt()
.coerceAtLeast(1)
rotated.scale(maxWidth, h)
val s = rotated.scale(maxWidth, h)
if (s !== rotated) rotated.recycle()
s
} else {
rotated
}
val maxPayloadBytes = 5 * 1024 * 1024
// Base64 inflates payloads by ~4/3; cap encoded bytes so the payload stays under 5MB (API limit).
val maxEncodedBytes = (maxPayloadBytes / 4) * 3
val result =
JpegSizeLimiter.compressToLimit(
initialWidth = scaled.width,
initialHeight = scaled.height,
startQuality = (quality * 100.0).roundToInt().coerceIn(10, 100),
maxBytes = maxEncodedBytes,
encode = { width, height, q ->
val bitmap =
if (width == scaled.width && height == scaled.height) {
scaled
} else {
scaled.scale(width, height)
try {
val maxPayloadBytes = 5 * 1024 * 1024
// Base64 inflates payloads by ~4/3; cap encoded bytes so the payload stays under 5MB (API limit).
val maxEncodedBytes = (maxPayloadBytes / 4) * 3
val result =
JpegSizeLimiter.compressToLimit(
initialWidth = scaled.width,
initialHeight = scaled.height,
startQuality = (quality * 100.0).roundToInt().coerceIn(10, 100),
maxBytes = maxEncodedBytes,
encode = { width, height, q ->
val bitmap =
if (width == scaled.width && height == scaled.height) {
scaled
} else {
scaled.scale(width, height)
}
val out = ByteArrayOutputStream()
if (!bitmap.compress(Bitmap.CompressFormat.JPEG, q, out)) {
if (bitmap !== scaled) bitmap.recycle()
throw IllegalStateException("UNAVAILABLE: failed to encode JPEG")
}
val out = ByteArrayOutputStream()
if (!bitmap.compress(Bitmap.CompressFormat.JPEG, q, out)) {
if (bitmap !== scaled) bitmap.recycle()
throw IllegalStateException("UNAVAILABLE: failed to encode JPEG")
}
if (bitmap !== scaled) {
bitmap.recycle()
}
out.toByteArray()
},
if (bitmap !== scaled) {
bitmap.recycle()
}
out.toByteArray()
},
)
val base64 = Base64.encodeToString(result.bytes, Base64.NO_WRAP)
Payload(
"""{"format":"jpg","base64":"$base64","width":${result.width},"height":${result.height}}""",
)
val base64 = Base64.encodeToString(result.bytes, Base64.NO_WRAP)
Payload(
"""{"format":"jpg","base64":"$base64","width":${result.width},"height":${result.height}}""",
)
} finally {
scaled.recycle()
}
}
@SuppressLint("MissingPermission")

View File

@@ -134,9 +134,11 @@ class CameraHandler(
}
val bytes = withContext(Dispatchers.IO) {
val b = filePayload.file.readBytes()
filePayload.file.delete()
b
try {
filePayload.file.readBytes()
} finally {
filePayload.file.delete()
}
}
val base64 = android.util.Base64.encodeToString(bytes, android.util.Base64.NO_WRAP)
clipLog("returning base64 payload")

View File

@@ -0,0 +1,50 @@
package ai.openclaw.app.node
import java.net.URI
object CanvasActionTrust {
const val scaffoldAssetUrl: String = "file:///android_asset/CanvasScaffold/scaffold.html"
fun isTrustedCanvasActionUrl(rawUrl: String?, trustedA2uiUrls: List<String>): Boolean {
val candidate = rawUrl?.trim().orEmpty()
if (candidate.isEmpty()) return false
if (candidate == scaffoldAssetUrl) return true
val candidateUri = parseUri(candidate) ?: return false
if (candidateUri.scheme.equals("file", ignoreCase = true)) {
return false
}
return trustedA2uiUrls.any { trusted ->
isTrustedA2uiPage(candidateUri, trusted)
}
}
private fun isTrustedA2uiPage(candidateUri: URI, trustedUrl: String): Boolean {
val trustedUri = parseUri(trustedUrl) ?: return false
if (!candidateUri.scheme.equals(trustedUri.scheme, ignoreCase = true)) return false
if (candidateUri.host?.equals(trustedUri.host, ignoreCase = true) != true) return false
if (effectivePort(candidateUri) != effectivePort(trustedUri)) return false
val trustedPath = trustedUri.rawPath?.takeIf { it.isNotBlank() } ?: return false
val candidatePath = candidateUri.rawPath?.takeIf { it.isNotBlank() } ?: return false
val trustedPrefix = if (trustedPath.endsWith("/")) trustedPath else "$trustedPath/"
return candidatePath == trustedPath || candidatePath.startsWith(trustedPrefix)
}
private fun effectivePort(uri: URI): Int {
if (uri.port >= 0) return uri.port
return when (uri.scheme?.lowercase()) {
"https" -> 443
"http" -> 80
else -> -1
}
}
private fun parseUri(raw: String): URI? =
try {
URI(raw)
} catch (_: Throwable) {
null
}
}

View File

@@ -180,27 +180,41 @@ class CanvasController {
withContext(Dispatchers.Main) {
val wv = webView ?: throw IllegalStateException("no webview")
val bmp = wv.captureBitmap()
val scaled = bmp.scaleForMaxWidth(maxWidth)
val out = ByteArrayOutputStream()
scaled.compress(Bitmap.CompressFormat.PNG, 100, out)
Base64.encodeToString(out.toByteArray(), Base64.NO_WRAP)
try {
val scaled = bmp.scaleForMaxWidth(maxWidth)
try {
val out = ByteArrayOutputStream()
scaled.compress(Bitmap.CompressFormat.PNG, 100, out)
Base64.encodeToString(out.toByteArray(), Base64.NO_WRAP)
} finally {
if (scaled !== bmp) scaled.recycle()
}
} finally {
bmp.recycle()
}
}
suspend fun snapshotBase64(format: SnapshotFormat, quality: Double?, maxWidth: Int?): String =
withContext(Dispatchers.Main) {
val wv = webView ?: throw IllegalStateException("no webview")
val bmp = wv.captureBitmap()
val scaled = bmp.scaleForMaxWidth(maxWidth)
val out = ByteArrayOutputStream()
val (compressFormat, compressQuality) =
when (format) {
SnapshotFormat.Png -> Bitmap.CompressFormat.PNG to 100
SnapshotFormat.Jpeg -> Bitmap.CompressFormat.JPEG to clampJpegQuality(quality)
try {
val scaled = bmp.scaleForMaxWidth(maxWidth)
try {
val out = ByteArrayOutputStream()
val (compressFormat, compressQuality) =
when (format) {
SnapshotFormat.Png -> Bitmap.CompressFormat.PNG to 100
SnapshotFormat.Jpeg -> Bitmap.CompressFormat.JPEG to clampJpegQuality(quality)
}
scaled.compress(compressFormat, compressQuality, out)
Base64.encodeToString(out.toByteArray(), Base64.NO_WRAP)
} finally {
if (scaled !== bmp) scaled.recycle()
}
scaled.compress(compressFormat, compressQuality, out)
Base64.encodeToString(out.toByteArray(), Base64.NO_WRAP)
} finally {
bmp.recycle()
}
}
private suspend fun WebView.captureBitmap(): Bitmap =

View File

@@ -17,7 +17,9 @@ class ConnectionManager(
private val voiceWakeMode: () -> VoiceWakeMode,
private val motionActivityAvailable: () -> Boolean,
private val motionPedometerAvailable: () -> Boolean,
private val smsAvailable: () -> Boolean,
private val sendSmsAvailable: () -> Boolean,
private val readSmsAvailable: () -> Boolean,
private val callLogAvailable: () -> Boolean,
private val hasRecordAudioPermission: () -> Boolean,
private val manualTls: () -> Boolean,
) {
@@ -78,7 +80,9 @@ class ConnectionManager(
NodeRuntimeFlags(
cameraEnabled = cameraEnabled(),
locationEnabled = locationMode() != LocationMode.Off,
smsAvailable = smsAvailable(),
sendSmsAvailable = sendSmsAvailable(),
readSmsAvailable = readSmsAvailable(),
callLogAvailable = callLogAvailable(),
voiceWakeEnabled = voiceWakeMode() != VoiceWakeMode.Off && hasRecordAudioPermission(),
motionActivityAvailable = motionActivityAvailable(),
motionPedometerAvailable = motionPedometerAvailable(),

View File

@@ -76,8 +76,8 @@ private object SystemContactsDataSource : ContactsDataSource {
selection = null
selectionArgs = null
} else {
selection = "${ContactsContract.Contacts.DISPLAY_NAME_PRIMARY} LIKE ?"
selectionArgs = arrayOf("%${request.query}%")
selection = "${ContactsContract.Contacts.DISPLAY_NAME_PRIMARY} LIKE ? ESCAPE '\\'"
selectionArgs = arrayOf("%${escapeLikePattern(request.query)}%")
}
val sortOrder = "${ContactsContract.Contacts.DISPLAY_NAME_PRIMARY} COLLATE NOCASE ASC LIMIT ${request.limit}"
resolver.query(
@@ -247,6 +247,9 @@ private object SystemContactsDataSource : ContactsDataSource {
}
}
private fun escapeLikePattern(pattern: String): String =
pattern.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_")
private fun loadPhones(resolver: ContentResolver, contactId: Long): List<String> {
return queryContactValues(
resolver = resolver,

View File

@@ -25,6 +25,8 @@ import kotlinx.serialization.json.put
class DeviceHandler(
private val appContext: Context,
private val smsEnabled: Boolean = BuildConfig.OPENCLAW_ENABLE_SMS,
private val callLogEnabled: Boolean = BuildConfig.OPENCLAW_ENABLE_CALL_LOG,
) {
private data class BatterySnapshot(
val status: Int,
@@ -173,8 +175,8 @@ class DeviceHandler(
put(
"sms",
permissionStateJson(
granted = hasPermission(Manifest.permission.SEND_SMS) && canSendSms,
promptableWhenDenied = canSendSms,
granted = smsEnabled && hasPermission(Manifest.permission.SEND_SMS) && canSendSms,
promptableWhenDenied = smsEnabled && canSendSms,
),
)
put(
@@ -215,8 +217,8 @@ class DeviceHandler(
put(
"callLog",
permissionStateJson(
granted = hasPermission(Manifest.permission.READ_CALL_LOG),
promptableWhenDenied = true,
granted = callLogEnabled && hasPermission(Manifest.permission.READ_CALL_LOG),
promptableWhenDenied = callLogEnabled,
),
)
put(

View File

@@ -18,7 +18,9 @@ import ai.openclaw.app.protocol.OpenClawSystemCommand
data class NodeRuntimeFlags(
val cameraEnabled: Boolean,
val locationEnabled: Boolean,
val smsAvailable: Boolean,
val sendSmsAvailable: Boolean,
val readSmsAvailable: Boolean,
val callLogAvailable: Boolean,
val voiceWakeEnabled: Boolean,
val motionActivityAvailable: Boolean,
val motionPedometerAvailable: Boolean,
@@ -29,7 +31,9 @@ enum class InvokeCommandAvailability {
Always,
CameraEnabled,
LocationEnabled,
SmsAvailable,
SendSmsAvailable,
ReadSmsAvailable,
CallLogAvailable,
MotionActivityAvailable,
MotionPedometerAvailable,
DebugBuild,
@@ -40,6 +44,7 @@ enum class NodeCapabilityAvailability {
CameraEnabled,
LocationEnabled,
SmsAvailable,
CallLogAvailable,
VoiceWakeEnabled,
MotionAvailable,
}
@@ -85,7 +90,10 @@ object InvokeCommandRegistry {
name = OpenClawCapability.Motion.rawValue,
availability = NodeCapabilityAvailability.MotionAvailable,
),
NodeCapabilitySpec(name = OpenClawCapability.CallLog.rawValue),
NodeCapabilitySpec(
name = OpenClawCapability.CallLog.rawValue,
availability = NodeCapabilityAvailability.CallLogAvailable,
),
)
val all: List<InvokeCommandSpec> =
@@ -187,10 +195,15 @@ object InvokeCommandRegistry {
),
InvokeCommandSpec(
name = OpenClawSmsCommand.Send.rawValue,
availability = InvokeCommandAvailability.SmsAvailable,
availability = InvokeCommandAvailability.SendSmsAvailable,
),
InvokeCommandSpec(
name = OpenClawSmsCommand.Search.rawValue,
availability = InvokeCommandAvailability.ReadSmsAvailable,
),
InvokeCommandSpec(
name = OpenClawCallLogCommand.Search.rawValue,
availability = InvokeCommandAvailability.CallLogAvailable,
),
InvokeCommandSpec(
name = "debug.logs",
@@ -213,7 +226,8 @@ object InvokeCommandRegistry {
NodeCapabilityAvailability.Always -> true
NodeCapabilityAvailability.CameraEnabled -> flags.cameraEnabled
NodeCapabilityAvailability.LocationEnabled -> flags.locationEnabled
NodeCapabilityAvailability.SmsAvailable -> flags.smsAvailable
NodeCapabilityAvailability.SmsAvailable -> flags.sendSmsAvailable || flags.readSmsAvailable
NodeCapabilityAvailability.CallLogAvailable -> flags.callLogAvailable
NodeCapabilityAvailability.VoiceWakeEnabled -> flags.voiceWakeEnabled
NodeCapabilityAvailability.MotionAvailable -> flags.motionActivityAvailable || flags.motionPedometerAvailable
}
@@ -228,7 +242,9 @@ object InvokeCommandRegistry {
InvokeCommandAvailability.Always -> true
InvokeCommandAvailability.CameraEnabled -> flags.cameraEnabled
InvokeCommandAvailability.LocationEnabled -> flags.locationEnabled
InvokeCommandAvailability.SmsAvailable -> flags.smsAvailable
InvokeCommandAvailability.SendSmsAvailable -> flags.sendSmsAvailable
InvokeCommandAvailability.ReadSmsAvailable -> flags.readSmsAvailable
InvokeCommandAvailability.CallLogAvailable -> flags.callLogAvailable
InvokeCommandAvailability.MotionActivityAvailable -> flags.motionActivityAvailable
InvokeCommandAvailability.MotionPedometerAvailable -> flags.motionPedometerAvailable
InvokeCommandAvailability.DebugBuild -> flags.debugBuild

View File

@@ -32,7 +32,9 @@ class InvokeDispatcher(
private val isForeground: () -> Boolean,
private val cameraEnabled: () -> Boolean,
private val locationEnabled: () -> Boolean,
private val smsAvailable: () -> Boolean,
private val sendSmsAvailable: () -> Boolean,
private val readSmsAvailable: () -> Boolean,
private val callLogAvailable: () -> Boolean,
private val debugBuild: () -> Boolean,
private val refreshNodeCanvasCapability: suspend () -> Boolean,
private val onCanvasA2uiPush: () -> Unit,
@@ -162,6 +164,7 @@ class InvokeDispatcher(
// SMS command
OpenClawSmsCommand.Send.rawValue -> smsHandler.handleSmsSend(paramsJson)
OpenClawSmsCommand.Search.rawValue -> smsHandler.handleSmsSearch(paramsJson)
// CallLog command
OpenClawCallLogCommand.Search.rawValue -> callLogHandler.handleCallLogSearch(paramsJson)
@@ -256,8 +259,8 @@ class InvokeDispatcher(
message = "PEDOMETER_UNAVAILABLE: step counter not available",
)
}
InvokeCommandAvailability.SmsAvailable ->
if (smsAvailable()) {
InvokeCommandAvailability.SendSmsAvailable ->
if (sendSmsAvailable()) {
null
} else {
GatewaySession.InvokeResult.error(
@@ -265,6 +268,24 @@ class InvokeDispatcher(
message = "SMS_UNAVAILABLE: SMS not available on this device",
)
}
InvokeCommandAvailability.ReadSmsAvailable ->
if (readSmsAvailable()) {
null
} else {
GatewaySession.InvokeResult.error(
code = "SMS_UNAVAILABLE",
message = "SMS_UNAVAILABLE: SMS not available on this device",
)
}
InvokeCommandAvailability.CallLogAvailable ->
if (callLogAvailable()) {
null
} else {
GatewaySession.InvokeResult.error(
code = "CALL_LOG_UNAVAILABLE",
message = "CALL_LOG_UNAVAILABLE: call log not available on this build",
)
}
InvokeCommandAvailability.DebugBuild ->
if (debugBuild()) {
null

View File

@@ -12,8 +12,6 @@ import java.time.format.DateTimeFormatter
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeout
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlinx.coroutines.suspendCancellableCoroutine
class LocationCaptureManager(private val context: Context) {
@@ -100,18 +98,15 @@ class LocationCaptureManager(private val context: Context) {
val resolved =
providers.firstOrNull { manager.isProviderEnabled(it) }
?: throw IllegalStateException("LOCATION_UNAVAILABLE: no providers available")
return withTimeout(timeoutMs.coerceAtLeast(1)) {
suspendCancellableCoroutine { cont ->
val location = withTimeout(timeoutMs.coerceAtLeast(1)) {
suspendCancellableCoroutine<Location?> { cont ->
val signal = CancellationSignal()
cont.invokeOnCancellation { signal.cancel() }
manager.getCurrentLocation(resolved, signal, context.mainExecutor) { location ->
if (location != null) {
cont.resume(location)
} else {
cont.resumeWithException(IllegalStateException("LOCATION_UNAVAILABLE: no fix"))
}
cont.resume(location) { _, _, _ -> }
}
}
}
return location ?: throw IllegalStateException("LOCATION_UNAVAILABLE: no fix")
}
}

View File

@@ -8,27 +8,85 @@ import androidx.core.content.ContextCompat
import ai.openclaw.app.gateway.GatewaySession
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
class LocationHandler(
internal interface LocationDataSource {
fun hasFinePermission(context: Context): Boolean
fun hasCoarsePermission(context: Context): Boolean
suspend fun fetchLocation(
desiredProviders: List<String>,
maxAgeMs: Long?,
timeoutMs: Long,
isPrecise: Boolean,
): LocationCaptureManager.Payload
}
private class DefaultLocationDataSource(
private val capture: LocationCaptureManager,
) : LocationDataSource {
override fun hasFinePermission(context: Context): Boolean =
ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) ==
PackageManager.PERMISSION_GRANTED
override fun hasCoarsePermission(context: Context): Boolean =
ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) ==
PackageManager.PERMISSION_GRANTED
override suspend fun fetchLocation(
desiredProviders: List<String>,
maxAgeMs: Long?,
timeoutMs: Long,
isPrecise: Boolean,
): LocationCaptureManager.Payload =
capture.getLocation(
desiredProviders = desiredProviders,
maxAgeMs = maxAgeMs,
timeoutMs = timeoutMs,
isPrecise = isPrecise,
)
}
class LocationHandler private constructor(
private val appContext: Context,
private val location: LocationCaptureManager,
private val dataSource: LocationDataSource,
private val json: Json,
private val isForeground: () -> Boolean,
private val locationPreciseEnabled: () -> Boolean,
) {
fun hasFineLocationPermission(): Boolean {
return (
ContextCompat.checkSelfPermission(appContext, Manifest.permission.ACCESS_FINE_LOCATION) ==
PackageManager.PERMISSION_GRANTED
)
}
constructor(
appContext: Context,
location: LocationCaptureManager,
json: Json,
isForeground: () -> Boolean,
locationPreciseEnabled: () -> Boolean,
) : this(
appContext = appContext,
dataSource = DefaultLocationDataSource(location),
json = json,
isForeground = isForeground,
locationPreciseEnabled = locationPreciseEnabled,
)
fun hasCoarseLocationPermission(): Boolean {
return (
ContextCompat.checkSelfPermission(appContext, Manifest.permission.ACCESS_COARSE_LOCATION) ==
PackageManager.PERMISSION_GRANTED
fun hasFineLocationPermission(): Boolean = dataSource.hasFinePermission(appContext)
fun hasCoarseLocationPermission(): Boolean = dataSource.hasCoarsePermission(appContext)
companion object {
internal fun forTesting(
appContext: Context,
dataSource: LocationDataSource,
json: Json = Json { ignoreUnknownKeys = true },
isForeground: () -> Boolean = { true },
locationPreciseEnabled: () -> Boolean = { true },
): LocationHandler =
LocationHandler(
appContext = appContext,
dataSource = dataSource,
json = json,
isForeground = isForeground,
locationPreciseEnabled = locationPreciseEnabled,
)
}
@@ -39,7 +97,7 @@ class LocationHandler(
message = "LOCATION_BACKGROUND_UNAVAILABLE: location requires OpenClaw to stay open",
)
}
if (!hasFineLocationPermission() && !hasCoarseLocationPermission()) {
if (!dataSource.hasFinePermission(appContext) && !dataSource.hasCoarsePermission(appContext)) {
return GatewaySession.InvokeResult.error(
code = "LOCATION_PERMISSION_REQUIRED",
message = "LOCATION_PERMISSION_REQUIRED: grant Location permission",
@@ -49,9 +107,9 @@ class LocationHandler(
val preciseEnabled = locationPreciseEnabled()
val accuracy =
when (desiredAccuracy) {
"precise" -> if (preciseEnabled && hasFineLocationPermission()) "precise" else "balanced"
"precise" -> if (preciseEnabled && dataSource.hasFinePermission(appContext)) "precise" else "balanced"
"coarse" -> "coarse"
else -> if (preciseEnabled && hasFineLocationPermission()) "precise" else "balanced"
else -> if (preciseEnabled && dataSource.hasFinePermission(appContext)) "precise" else "balanced"
}
val providers =
when (accuracy) {
@@ -61,7 +119,7 @@ class LocationHandler(
}
try {
val payload =
location.getLocation(
dataSource.fetchLocation(
desiredProviders = providers,
maxAgeMs = maxAgeMs,
timeoutMs = timeoutMs,

View File

@@ -10,6 +10,7 @@ import android.os.SystemClock
import androidx.core.content.ContextCompat
import ai.openclaw.app.gateway.GatewaySession
import java.time.Instant
import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withTimeoutOrNull
import kotlinx.serialization.json.Json
@@ -18,7 +19,6 @@ import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.buildJsonArray
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put
import kotlin.coroutines.resume
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.sqrt
@@ -142,19 +142,18 @@ private object SystemMotionDataSource : MotionDataSource {
val averageDelta: Double,
)
@OptIn(InternalCoroutinesApi::class)
private suspend fun readStepCounter(sensorManager: SensorManager, sensor: Sensor): Int? {
val sample =
withTimeoutOrNull(1200L) {
suspendCancellableCoroutine<Float?> { cont ->
var resumed = false
val listener =
object : SensorEventListener {
override fun onSensorChanged(event: SensorEvent?) {
if (resumed) return
val value = event?.values?.firstOrNull()
resumed = true
val token = cont.tryResume(value) ?: return
cont.completeResume(token)
sensorManager.unregisterListener(this)
cont.resume(value)
}
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) = Unit
@@ -162,8 +161,7 @@ private object SystemMotionDataSource : MotionDataSource {
val registered = sensorManager.registerListener(listener, sensor, SensorManager.SENSOR_DELAY_NORMAL)
if (!registered) {
sensorManager.unregisterListener(listener)
resumed = true
cont.resume(null)
cont.resume(null) { _, _, _ -> }
return@suspendCancellableCoroutine
}
cont.invokeOnCancellation { sensorManager.unregisterListener(listener) }
@@ -172,6 +170,7 @@ private object SystemMotionDataSource : MotionDataSource {
return sample?.toInt()?.takeIf { it >= 0 }
}
@OptIn(InternalCoroutinesApi::class)
private suspend fun readAccelerometerSample(
sensorManager: SensorManager,
sensor: Sensor,
@@ -181,7 +180,6 @@ private object SystemMotionDataSource : MotionDataSource {
suspendCancellableCoroutine<AccelerometerSample?> { cont ->
var count = 0
var sumDelta = 0.0
var resumed = false
val listener =
object : SensorEventListener {
override fun onSensorChanged(event: SensorEvent?) {
@@ -195,15 +193,14 @@ private object SystemMotionDataSource : MotionDataSource {
).toDouble()
sumDelta += abs(magnitude - SensorManager.GRAVITY_EARTH.toDouble())
count += 1
if (count >= ACCELEROMETER_SAMPLE_TARGET && !resumed) {
resumed = true
sensorManager.unregisterListener(this)
cont.resume(
AccelerometerSample(
samples = count,
averageDelta = if (count == 0) 0.0 else sumDelta / count,
),
if (count >= ACCELEROMETER_SAMPLE_TARGET) {
val result = AccelerometerSample(
samples = count,
averageDelta = sumDelta / count,
)
val token = cont.tryResume(result) ?: return
cont.completeResume(token)
sensorManager.unregisterListener(this)
}
}
@@ -211,8 +208,7 @@ private object SystemMotionDataSource : MotionDataSource {
}
val registered = sensorManager.registerListener(listener, sensor, SensorManager.SENSOR_DELAY_NORMAL)
if (!registered) {
resumed = true
cont.resume(null)
cont.resume(null) { _, _, _ -> }
return@suspendCancellableCoroutine
}
cont.invokeOnCancellation { sensorManager.unregisterListener(listener) }

View File

@@ -71,17 +71,22 @@ private object SystemPhotosDataSource : PhotosDataSource {
for (row in rows) {
if (remainingBudget <= 0) break
val bitmap = decodeScaledBitmap(resolver, row.uri, request.maxWidth) ?: continue
val encoded = encodeJpegUnderBudget(bitmap, request.quality, MAX_PER_PHOTO_BASE64_CHARS) ?: continue
if (encoded.base64.length > remainingBudget) break
remainingBudget -= encoded.base64.length
out +=
EncodedPhotoPayload(
format = "jpeg",
base64 = encoded.base64,
width = encoded.width,
height = encoded.height,
createdAt = row.createdAtMs?.let { Instant.ofEpochMilli(it).toString() },
)
try {
val encoded = encodeJpegUnderBudget(bitmap, request.quality, MAX_PER_PHOTO_BASE64_CHARS)
if (encoded == null) continue
if (encoded.base64.length > remainingBudget) break
remainingBudget -= encoded.base64.length
out +=
EncodedPhotoPayload(
format = "jpeg",
base64 = encoded.base64,
width = encoded.width,
height = encoded.height,
createdAt = row.createdAtMs?.let { Instant.ofEpochMilli(it).toString() },
)
} finally {
bitmap.recycle()
}
}
return out
}
@@ -159,7 +164,11 @@ private object SystemPhotosDataSource : PhotosDataSource {
if (decoded.width <= maxWidth) return decoded
val targetHeight = max(1, ((decoded.height.toDouble() * maxWidth) / decoded.width).roundToInt())
return decoded.scale(maxWidth, targetHeight, true)
return try {
decoded.scale(maxWidth, targetHeight, true)
} finally {
decoded.recycle()
}
}
private fun computeInSampleSize(width: Int, maxWidth: Int): Int {
@@ -178,30 +187,36 @@ private object SystemPhotosDataSource : PhotosDataSource {
maxBase64Chars: Int,
): EncodedJpeg? {
var working = bitmap
var jpegQuality = (quality.coerceIn(0.1, 1.0) * 100.0).roundToInt().coerceIn(10, 100)
repeat(10) {
val out = ByteArrayOutputStream()
val ok = working.compress(Bitmap.CompressFormat.JPEG, jpegQuality, out)
if (!ok) return null
val bytes = out.toByteArray()
val base64 = android.util.Base64.encodeToString(bytes, android.util.Base64.NO_WRAP)
if (base64.length <= maxBase64Chars) {
return EncodedJpeg(
base64 = base64,
width = working.width,
height = working.height,
)
try {
var jpegQuality = (quality.coerceIn(0.1, 1.0) * 100.0).roundToInt().coerceIn(10, 100)
repeat(10) {
val out = ByteArrayOutputStream()
val ok = working.compress(Bitmap.CompressFormat.JPEG, jpegQuality, out)
if (!ok) return null
val bytes = out.toByteArray()
val base64 = android.util.Base64.encodeToString(bytes, android.util.Base64.NO_WRAP)
if (base64.length <= maxBase64Chars) {
return EncodedJpeg(
base64 = base64,
width = working.width,
height = working.height,
)
}
if (jpegQuality > 35) {
jpegQuality = max(25, jpegQuality - 15)
return@repeat
}
val nextWidth = max(240, (working.width * 0.75f).roundToInt())
if (nextWidth >= working.width) return null
val nextHeight = max(1, ((working.height.toDouble() * nextWidth) / working.width).roundToInt())
val previous = working
working = working.scale(nextWidth, nextHeight, true)
if (previous !== bitmap) previous.recycle()
}
if (jpegQuality > 35) {
jpegQuality = max(25, jpegQuality - 15)
return@repeat
}
val nextWidth = max(240, (working.width * 0.75f).roundToInt())
if (nextWidth >= working.width) return null
val nextHeight = max(1, ((working.height.toDouble() * nextWidth) / working.width).roundToInt())
working = working.scale(nextWidth, nextHeight, true)
return null
} finally {
if (working !== bitmap) working.recycle()
}
return null
}
}

View File

@@ -16,4 +16,16 @@ class SmsHandler(
return GatewaySession.InvokeResult.error(code = code, message = error)
}
}
suspend fun handleSmsSearch(paramsJson: String?): GatewaySession.InvokeResult {
val res = sms.search(paramsJson)
if (res.ok) {
return GatewaySession.InvokeResult.ok(res.payloadJson)
} else {
val error = res.error ?: "SMS_SEARCH_FAILED"
val idx = error.indexOf(':')
val code = if (idx > 0) error.substring(0, idx).trim() else "SMS_SEARCH_FAILED"
return GatewaySession.InvokeResult.error(code = code, message = error)
}
}
}

View File

@@ -3,19 +3,27 @@ package ai.openclaw.app.node
import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.database.Cursor
import android.net.Uri
import android.provider.ContactsContract
import android.provider.Telephony
import android.telephony.SmsManager as AndroidSmsManager
import androidx.core.content.ContextCompat
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.encodeToString
import kotlinx.serialization.Serializable
import ai.openclaw.app.PermissionRequester
/**
* Sends SMS messages via the Android SMS API.
* Requires SEND_SMS permission to be granted.
*
* Also provides SMS query functionality with READ_SMS permission.
*/
class SmsManager(private val context: Context) {
@@ -30,6 +38,30 @@ class SmsManager(private val context: Context) {
val payloadJson: String,
)
/**
* Represents a single SMS message
*/
@Serializable
data class SmsMessage(
val id: Long,
val threadId: Long,
val address: String?,
val person: String?,
val date: Long,
val dateSent: Long,
val read: Boolean,
val type: Int,
val body: String?,
val status: Int,
)
data class SearchResult(
val ok: Boolean,
val messages: List<SmsMessage>,
val error: String? = null,
val payloadJson: String,
)
internal data class ParsedParams(
val to: String,
val message: String,
@@ -44,12 +76,30 @@ class SmsManager(private val context: Context) {
) : ParseResult()
}
internal data class QueryParams(
val startTime: Long? = null,
val endTime: Long? = null,
val contactName: String? = null,
val phoneNumber: String? = null,
val keyword: String? = null,
val type: Int? = null,
val isRead: Boolean? = null,
val limit: Int = DEFAULT_SMS_LIMIT,
val offset: Int = 0,
)
internal sealed class QueryParseResult {
data class Ok(val params: QueryParams) : QueryParseResult()
data class Error(val error: String) : QueryParseResult()
}
internal data class SendPlan(
val parts: List<String>,
val useMultipart: Boolean,
)
companion object {
private const val DEFAULT_SMS_LIMIT = 25
internal val JsonConfig = Json { ignoreUnknownKeys = true }
internal fun parseParams(paramsJson: String?, json: Json = JsonConfig): ParseResult {
@@ -88,6 +138,52 @@ class SmsManager(private val context: Context) {
return ParseResult.Ok(ParsedParams(to = to, message = message))
}
internal fun parseQueryParams(paramsJson: String?, json: Json = JsonConfig): QueryParseResult {
val params = paramsJson?.trim().orEmpty()
if (params.isEmpty()) {
return QueryParseResult.Ok(QueryParams())
}
val obj = try {
json.parseToJsonElement(params).jsonObject
} catch (_: Throwable) {
return QueryParseResult.Error("INVALID_REQUEST: expected JSON object")
}
val startTime = (obj["startTime"] as? JsonPrimitive)?.content?.toLongOrNull()
val endTime = (obj["endTime"] as? JsonPrimitive)?.content?.toLongOrNull()
val contactName = (obj["contactName"] as? JsonPrimitive)?.content?.trim()
val phoneNumber = (obj["phoneNumber"] as? JsonPrimitive)?.content?.trim()
val keyword = (obj["keyword"] as? JsonPrimitive)?.content?.trim()
val type = (obj["type"] as? JsonPrimitive)?.content?.toIntOrNull()
val isRead = (obj["isRead"] as? JsonPrimitive)?.content?.toBooleanStrictOrNull()
val limit = ((obj["limit"] as? JsonPrimitive)?.content?.toIntOrNull() ?: DEFAULT_SMS_LIMIT)
.coerceIn(1, 200)
val offset = ((obj["offset"] as? JsonPrimitive)?.content?.toIntOrNull() ?: 0)
.coerceAtLeast(0)
// Validate time range
if (startTime != null && endTime != null && startTime > endTime) {
return QueryParseResult.Error("INVALID_REQUEST: startTime must be less than or equal to endTime")
}
return QueryParseResult.Ok(QueryParams(
startTime = startTime,
endTime = endTime,
contactName = contactName,
phoneNumber = phoneNumber,
keyword = keyword,
type = type,
isRead = isRead,
limit = limit,
offset = offset,
))
}
private fun normalizePhoneNumber(phone: String): String {
return phone.replace(Regex("""[\s\-()]"""), "")
}
internal fun buildSendPlan(
message: String,
divider: (String) -> List<String>,
@@ -112,6 +208,25 @@ class SmsManager(private val context: Context) {
}
return json.encodeToString(JsonObject.serializer(), JsonObject(payload))
}
internal fun buildQueryPayloadJson(
json: Json = JsonConfig,
ok: Boolean,
messages: List<SmsMessage>,
error: String? = null,
): String {
val messagesArray = json.encodeToString(messages)
val messagesElement = json.parseToJsonElement(messagesArray)
val payload = mutableMapOf<String, JsonElement>(
"ok" to JsonPrimitive(ok),
"count" to JsonPrimitive(messages.size),
"messages" to messagesElement
)
if (!ok && error != null) {
payload["error"] = JsonPrimitive(error)
}
return json.encodeToString(JsonObject.serializer(), JsonObject(payload))
}
}
fun hasSmsPermission(): Boolean {
@@ -121,10 +236,28 @@ class SmsManager(private val context: Context) {
) == PackageManager.PERMISSION_GRANTED
}
fun hasReadSmsPermission(): Boolean {
return ContextCompat.checkSelfPermission(
context,
Manifest.permission.READ_SMS
) == PackageManager.PERMISSION_GRANTED
}
fun hasReadContactsPermission(): Boolean {
return ContextCompat.checkSelfPermission(
context,
Manifest.permission.READ_CONTACTS
) == PackageManager.PERMISSION_GRANTED
}
fun canSendSms(): Boolean {
return hasSmsPermission() && hasTelephonyFeature()
}
fun canReadSms(): Boolean {
return hasReadSmsPermission() && hasTelephonyFeature()
}
fun hasTelephonyFeature(): Boolean {
return context.packageManager?.hasSystemFeature(PackageManager.FEATURE_TELEPHONY) == true
}
@@ -208,6 +341,20 @@ class SmsManager(private val context: Context) {
return results[Manifest.permission.SEND_SMS] == true
}
private suspend fun ensureReadSmsPermission(): Boolean {
if (hasReadSmsPermission()) return true
val requester = permissionRequester ?: return false
val results = requester.requestIfMissing(listOf(Manifest.permission.READ_SMS))
return results[Manifest.permission.READ_SMS] == true
}
private suspend fun ensureReadContactsPermission(): Boolean {
if (hasReadContactsPermission()) return true
val requester = permissionRequester ?: return false
val results = requester.requestIfMissing(listOf(Manifest.permission.READ_CONTACTS))
return results[Manifest.permission.READ_CONTACTS] == true
}
private fun okResult(to: String, message: String): SendResult {
return SendResult(
ok = true,
@@ -227,4 +374,240 @@ class SmsManager(private val context: Context) {
payloadJson = buildPayloadJson(json = json, ok = false, to = to, error = error),
)
}
/**
* search SMS messages with the specified parameters.
*
* @param paramsJson JSON with optional fields:
* - startTime (Long): Start time in milliseconds
* - endTime (Long): End time in milliseconds
* - contactName (String): Contact name to search
* - phoneNumber (String): Phone number to search (supports partial matching)
* - keyword (String): Keyword to search in message body
* - type (Int): SMS type (1=Inbox, 2=Sent, 3=Draft, etc.)
* - isRead (Boolean): Read status
* - limit (Int): Number of records to return (default: 25, range: 1-200)
* - offset (Int): Number of records to skip (default: 0)
* @return SearchResult containing the list of SMS messages or an error
*/
suspend fun search(paramsJson: String?): SearchResult = withContext(Dispatchers.IO) {
if (!hasTelephonyFeature()) {
return@withContext SearchResult(
ok = false,
messages = emptyList(),
error = "SMS_UNAVAILABLE: telephony not available",
payloadJson = buildQueryPayloadJson(json, ok = false, messages = emptyList(), error = "SMS_UNAVAILABLE: telephony not available")
)
}
if (!ensureReadSmsPermission()) {
return@withContext SearchResult(
ok = false,
messages = emptyList(),
error = "SMS_PERMISSION_REQUIRED: grant READ_SMS permission",
payloadJson = buildQueryPayloadJson(json, ok = false, messages = emptyList(), error = "SMS_PERMISSION_REQUIRED: grant READ_SMS permission")
)
}
val parseResult = parseQueryParams(paramsJson, json)
if (parseResult is QueryParseResult.Error) {
return@withContext SearchResult(
ok = false,
messages = emptyList(),
error = parseResult.error,
payloadJson = buildQueryPayloadJson(json, ok = false, messages = emptyList(), error = parseResult.error)
)
}
val params = (parseResult as QueryParseResult.Ok).params
return@withContext try {
// Get phone numbers from contact name if provided
val phoneNumbers = if (!params.contactName.isNullOrEmpty()) {
if (!ensureReadContactsPermission()) {
return@withContext SearchResult(
ok = false,
messages = emptyList(),
error = "CONTACTS_PERMISSION_REQUIRED: grant READ_CONTACTS permission",
payloadJson = buildQueryPayloadJson(json, ok = false, messages = emptyList(), error = "CONTACTS_PERMISSION_REQUIRED: grant READ_CONTACTS permission")
)
}
getPhoneNumbersFromContactName(params.contactName)
} else {
emptyList()
}
val messages = querySmsMessages(params, phoneNumbers)
SearchResult(
ok = true,
messages = messages,
error = null,
payloadJson = buildQueryPayloadJson(json, ok = true, messages = messages)
)
} catch (e: SecurityException) {
SearchResult(
ok = false,
messages = emptyList(),
error = "SMS_PERMISSION_REQUIRED: ${e.message}",
payloadJson = buildQueryPayloadJson(json, ok = false, messages = emptyList(), error = "SMS_PERMISSION_REQUIRED: ${e.message}")
)
} catch (e: Throwable) {
SearchResult(
ok = false,
messages = emptyList(),
error = "SMS_QUERY_FAILED: ${e.message ?: "unknown error"}",
payloadJson = buildQueryPayloadJson(json, ok = false, messages = emptyList(), error = "SMS_QUERY_FAILED: ${e.message ?: "unknown error"}")
)
}
}
/**
* Get all phone numbers associated with a contact name
*/
private fun getPhoneNumbersFromContactName(contactName: String): List<String> {
val phoneNumbers = mutableListOf<String>()
val selection = "${ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME} LIKE ?"
val selectionArgs = arrayOf("%$contactName%")
val cursor = context.contentResolver.query(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
arrayOf(ContactsContract.CommonDataKinds.Phone.NUMBER),
selection,
selectionArgs,
null
)
cursor?.use {
val numberIndex = it.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)
while (it.moveToNext()) {
val number = it.getString(numberIndex)
if (!number.isNullOrBlank()) {
phoneNumbers.add(normalizePhoneNumber(number))
}
}
}
return phoneNumbers
}
/**
* Query SMS messages based on the provided parameters
*/
private fun querySmsMessages(params: QueryParams, phoneNumbers: List<String>): List<SmsMessage> {
val messages = mutableListOf<SmsMessage>()
// Build selection and selectionArgs
val selections = mutableListOf<String>()
val selectionArgs = mutableListOf<String>()
// Time range
if (params.startTime != null) {
selections.add("${Telephony.Sms.DATE} >= ?")
selectionArgs.add(params.startTime.toString())
}
if (params.endTime != null) {
selections.add("${Telephony.Sms.DATE} <= ?")
selectionArgs.add(params.endTime.toString())
}
// Phone numbers (from contact name or direct phone number)
val allPhoneNumbers = if (!params.phoneNumber.isNullOrEmpty()) {
phoneNumbers + normalizePhoneNumber(params.phoneNumber)
} else {
phoneNumbers
}
if (allPhoneNumbers.isNotEmpty()) {
val addressSelection = allPhoneNumbers.joinToString(" OR ") {
"${Telephony.Sms.ADDRESS} LIKE ?"
}
selections.add("($addressSelection)")
allPhoneNumbers.forEach {
selectionArgs.add("%$it%")
}
}
// Keyword in body
if (!params.keyword.isNullOrEmpty()) {
selections.add("${Telephony.Sms.BODY} LIKE ?")
selectionArgs.add("%${params.keyword}%")
}
// Type
if (params.type != null) {
selections.add("${Telephony.Sms.TYPE} = ?")
selectionArgs.add(params.type.toString())
}
// Read status
if (params.isRead != null) {
selections.add("${Telephony.Sms.READ} = ?")
selectionArgs.add(if (params.isRead) "1" else "0")
}
val selection = if (selections.isNotEmpty()) {
selections.joinToString(" AND ")
} else {
null
}
val selectionArgsArray = if (selectionArgs.isNotEmpty()) {
selectionArgs.toTypedArray()
} else {
null
}
// Query SMS with SQL-level LIMIT and OFFSET to avoid loading all matching rows
val sortOrder = "${Telephony.Sms.DATE} DESC LIMIT ${params.limit} OFFSET ${params.offset}"
val cursor = context.contentResolver.query(
Telephony.Sms.CONTENT_URI,
arrayOf(
Telephony.Sms._ID,
Telephony.Sms.THREAD_ID,
Telephony.Sms.ADDRESS,
Telephony.Sms.PERSON,
Telephony.Sms.DATE,
Telephony.Sms.DATE_SENT,
Telephony.Sms.READ,
Telephony.Sms.TYPE,
Telephony.Sms.BODY,
Telephony.Sms.STATUS
),
selection,
selectionArgsArray,
sortOrder
)
cursor?.use {
val idIndex = it.getColumnIndex(Telephony.Sms._ID)
val threadIdIndex = it.getColumnIndex(Telephony.Sms.THREAD_ID)
val addressIndex = it.getColumnIndex(Telephony.Sms.ADDRESS)
val personIndex = it.getColumnIndex(Telephony.Sms.PERSON)
val dateIndex = it.getColumnIndex(Telephony.Sms.DATE)
val dateSentIndex = it.getColumnIndex(Telephony.Sms.DATE_SENT)
val readIndex = it.getColumnIndex(Telephony.Sms.READ)
val typeIndex = it.getColumnIndex(Telephony.Sms.TYPE)
val bodyIndex = it.getColumnIndex(Telephony.Sms.BODY)
val statusIndex = it.getColumnIndex(Telephony.Sms.STATUS)
var count = 0
while (it.moveToNext() && count < params.limit) {
val message = SmsMessage(
id = it.getLong(idIndex),
threadId = it.getLong(threadIdIndex),
address = it.getString(addressIndex),
person = it.getString(personIndex),
date = it.getLong(dateIndex),
dateSent = it.getLong(dateSentIndex),
read = it.getInt(readIndex) == 1,
type = it.getInt(typeIndex),
body = it.getString(bodyIndex),
status = it.getInt(statusIndex)
)
messages.add(message)
count++
}
}
return messages
}
}

View File

@@ -58,9 +58,12 @@ object OpenClawCanvasA2UIAction {
}
fun jsDispatchA2UIActionStatus(actionId: String, ok: Boolean, error: String?): String {
val err = (error ?: "").replace("\\", "\\\\").replace("\"", "\\\"")
val err = jsonStringLiteral(error ?: "")
val okLiteral = if (ok) "true" else "false"
val idEscaped = actionId.replace("\\", "\\\\").replace("\"", "\\\"")
return "window.dispatchEvent(new CustomEvent('openclaw:a2ui-action-status', { detail: { id: \"${idEscaped}\", ok: ${okLiteral}, error: \"${err}\" } }));"
val idLiteral = jsonStringLiteral(actionId)
return "window.dispatchEvent(new CustomEvent('openclaw:a2ui-action-status', { detail: { id: ${idLiteral}, ok: ${okLiteral}, error: ${err} } }));"
}
private fun jsonStringLiteral(raw: String): String =
JsonPrimitive(raw).toString().replace("\u2028", "\\u2028").replace("\u2029", "\\u2029")
}

View File

@@ -53,6 +53,7 @@ enum class OpenClawCameraCommand(val rawValue: String) {
enum class OpenClawSmsCommand(val rawValue: String) {
Send("sms.send"),
Search("sms.search"),
;
companion object {

View File

@@ -22,13 +22,15 @@ import androidx.compose.ui.viewinterop.AndroidView
import androidx.webkit.WebSettingsCompat
import androidx.webkit.WebViewFeature
import ai.openclaw.app.MainViewModel
import java.util.concurrent.atomic.AtomicReference
@SuppressLint("SetJavaScriptEnabled")
@Composable
fun CanvasScreen(viewModel: MainViewModel, modifier: Modifier = Modifier) {
fun CanvasScreen(viewModel: MainViewModel, visible: Boolean, modifier: Modifier = Modifier) {
val context = LocalContext.current
val isDebuggable = (context.applicationInfo.flags and android.content.pm.ApplicationInfo.FLAG_DEBUGGABLE) != 0
val webViewRef = remember { mutableStateOf<WebView?>(null) }
val currentPageUrlRef = remember { AtomicReference<String?>(null) }
DisposableEffect(viewModel) {
onDispose {
@@ -45,6 +47,7 @@ fun CanvasScreen(viewModel: MainViewModel, modifier: Modifier = Modifier) {
modifier = modifier,
factory = {
WebView(context).apply {
visibility = if (visible) View.VISIBLE else View.INVISIBLE
settings.javaScriptEnabled = true
settings.domStorageEnabled = true
settings.mixedContentMode = WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE
@@ -67,6 +70,14 @@ fun CanvasScreen(viewModel: MainViewModel, modifier: Modifier = Modifier) {
isHorizontalScrollBarEnabled = true
webViewClient =
object : WebViewClient() {
override fun onPageStarted(
view: WebView,
url: String?,
favicon: android.graphics.Bitmap?,
) {
currentPageUrlRef.set(url)
}
override fun onReceivedError(
view: WebView,
request: WebResourceRequest,
@@ -89,6 +100,7 @@ fun CanvasScreen(viewModel: MainViewModel, modifier: Modifier = Modifier) {
}
override fun onPageFinished(view: WebView, url: String?) {
currentPageUrlRef.set(url)
if (isDebuggable) {
Log.d("OpenClawWebView", "onPageFinished: $url")
}
@@ -121,12 +133,27 @@ fun CanvasScreen(viewModel: MainViewModel, modifier: Modifier = Modifier) {
}
}
val bridge = CanvasA2UIActionBridge { payload -> viewModel.handleCanvasA2UIActionFromWebView(payload) }
val bridge =
CanvasA2UIActionBridge(
isTrustedPage = { viewModel.isTrustedCanvasActionUrl(currentPageUrlRef.get()) },
) { payload ->
viewModel.handleCanvasA2UIActionFromWebView(payload)
}
addJavascriptInterface(bridge, CanvasA2UIActionBridge.interfaceName)
viewModel.canvas.attach(this)
webViewRef.value = this
}
},
update = { webView ->
webView.visibility = if (visible) View.VISIBLE else View.INVISIBLE
if (visible) {
webView.resumeTimers()
webView.onResume()
} else {
webView.onPause()
webView.pauseTimers()
}
},
)
}
@@ -136,11 +163,15 @@ private fun disableForceDarkIfSupported(settings: WebSettings) {
WebSettingsCompat.setForceDark(settings, WebSettingsCompat.FORCE_DARK_OFF)
}
private class CanvasA2UIActionBridge(private val onMessage: (String) -> Unit) {
private class CanvasA2UIActionBridge(
private val isTrustedPage: () -> Boolean,
private val onMessage: (String) -> Unit,
) {
@JavascriptInterface
fun postMessage(payload: String?) {
val msg = payload?.trim().orEmpty()
if (msg.isEmpty()) return
if (!isTrustedPage()) return
onMessage(msg)
}

View File

@@ -1,7 +1,7 @@
package ai.openclaw.app.ui
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.BorderStroke
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -20,6 +20,7 @@ import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Cloud
import androidx.compose.material.icons.filled.ContentCopy
import androidx.compose.material.icons.filled.ExpandLess
import androidx.compose.material.icons.filled.ExpandMore
import androidx.compose.material.icons.filled.Link
@@ -49,6 +50,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import ai.openclaw.app.MainViewModel
import ai.openclaw.app.ui.mobileCardSurface
@@ -60,6 +62,7 @@ private enum class ConnectInputMode {
@Composable
fun ConnectTabScreen(viewModel: MainViewModel) {
val context = LocalContext.current
val statusText by viewModel.statusText.collectAsState()
val isConnected by viewModel.isConnected.collectAsState()
val remoteAddress by viewModel.remoteAddress.collectAsState()
@@ -134,7 +137,8 @@ fun ConnectTabScreen(viewModel: MainViewModel) {
}
}
val primaryLabel = if (isConnected) "Disconnect Gateway" else "Connect Gateway"
val showDiagnostics = !isConnected && gatewayStatusHasDiagnostics(statusText)
val statusLabel = gatewayStatusForDisplay(statusText)
Column(
modifier = Modifier.verticalScroll(rememberScrollState()).padding(horizontal = 20.dp, vertical = 16.dp),
@@ -279,6 +283,46 @@ fun ConnectTabScreen(viewModel: MainViewModel) {
}
}
if (showDiagnostics) {
Surface(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(14.dp),
color = mobileWarningSoft,
border = BorderStroke(1.dp, mobileWarning.copy(alpha = 0.25f)),
) {
Column(
modifier = Modifier.fillMaxWidth().padding(horizontal = 14.dp, vertical = 14.dp),
verticalArrangement = Arrangement.spacedBy(10.dp),
) {
Text("Last gateway error", style = mobileHeadline, color = mobileWarning)
Text(statusLabel, style = mobileBody.copy(fontFamily = FontFamily.Monospace), color = mobileText)
Text("OpenClaw Android ${openClawAndroidVersionLabel()}", style = mobileCaption1, color = mobileTextSecondary)
Button(
onClick = {
copyGatewayDiagnosticsReport(
context = context,
screen = "connect tab",
gatewayAddress = activeEndpoint,
statusText = statusLabel,
)
},
modifier = Modifier.fillMaxWidth().height(46.dp),
shape = RoundedCornerShape(12.dp),
colors =
ButtonDefaults.buttonColors(
containerColor = mobileCardSurface,
contentColor = mobileWarning,
),
border = BorderStroke(1.dp, mobileWarning.copy(alpha = 0.3f)),
) {
Icon(Icons.Default.ContentCopy, contentDescription = null, modifier = Modifier.size(18.dp))
Spacer(modifier = Modifier.width(8.dp))
Text("Copy Report for Claw", style = mobileCallout.copy(fontWeight = FontWeight.Bold))
}
}
}
}
Surface(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(14.dp),

View File

@@ -97,8 +97,25 @@ internal fun parseGatewayEndpoint(rawInput: String): GatewayEndpointConfig? {
"wss", "https" -> true
else -> true
}
val port = uri.port.takeIf { it in 1..65535 } ?: if (tls) 443 else 18789
val displayUrl = "${if (tls) "https" else "http"}://$host:$port"
val defaultPort =
when (scheme) {
"wss", "https" -> 443
"ws", "http" -> 18789
else -> 443
}
val displayPort =
when (scheme) {
"wss", "https" -> 443
"ws", "http" -> 80
else -> 443
}
val port = uri.port.takeIf { it in 1..65535 } ?: defaultPort
val displayUrl =
if (port == displayPort && defaultPort == displayPort) {
"${if (tls) "https" else "http"}://$host"
} else {
"${if (tls) "https" else "http"}://$host:$port"
}
return GatewayEndpointConfig(host = host, port = port, tls = tls, displayUrl = displayUrl)
}

View File

@@ -0,0 +1,77 @@
package ai.openclaw.app.ui
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.os.Build
import android.widget.Toast
import ai.openclaw.app.BuildConfig
internal fun openClawAndroidVersionLabel(): String {
val versionName = BuildConfig.VERSION_NAME.trim().ifEmpty { "dev" }
return if (BuildConfig.DEBUG && !versionName.contains("dev", ignoreCase = true)) {
"$versionName-dev"
} else {
versionName
}
}
internal fun gatewayStatusForDisplay(statusText: String): String {
return statusText.trim().ifEmpty { "Offline" }
}
internal fun gatewayStatusHasDiagnostics(statusText: String): Boolean {
val lower = gatewayStatusForDisplay(statusText).lowercase()
return lower != "offline" && !lower.contains("connecting")
}
internal fun gatewayStatusLooksLikePairing(statusText: String): Boolean {
val lower = gatewayStatusForDisplay(statusText).lowercase()
return lower.contains("pair") || lower.contains("approve")
}
internal fun buildGatewayDiagnosticsReport(
screen: String,
gatewayAddress: String,
statusText: String,
): String {
val device =
listOfNotNull(Build.MANUFACTURER, Build.MODEL)
.joinToString(" ")
.trim()
.ifEmpty { "Android" }
val androidVersion = Build.VERSION.RELEASE?.trim().orEmpty().ifEmpty { Build.VERSION.SDK_INT.toString() }
val endpoint = gatewayAddress.trim().ifEmpty { "unknown" }
val status = gatewayStatusForDisplay(statusText)
return """
Help diagnose this OpenClaw Android gateway connection failure.
Please:
- pick one route only: same machine, same LAN, Tailscale, or public URL
- classify this as pairing/auth, TLS trust, wrong advertised route, wrong address/port, or gateway down
- quote the exact app status/error below
- tell me whether `openclaw devices list` should show a pending pairing request
- if more signal is needed, ask for `openclaw qr --json`, `openclaw devices list`, and `openclaw nodes status`
- give the next exact command or tap
Debug info:
- screen: $screen
- app version: ${openClawAndroidVersionLabel()}
- device: $device
- android: $androidVersion (SDK ${Build.VERSION.SDK_INT})
- gateway address: $endpoint
- status/error: $status
""".trimIndent()
}
internal fun copyGatewayDiagnosticsReport(
context: Context,
screen: String,
gatewayAddress: String,
statusText: String,
) {
val clipboard = context.getSystemService(ClipboardManager::class.java) ?: return
val report = buildGatewayDiagnosticsReport(screen = screen, gatewayAddress = gatewayAddress, statusText = statusText)
clipboard.setPrimaryClip(ClipData.newPlainText("OpenClaw gateway diagnostics", report))
Toast.makeText(context, "Copied gateway diagnostics", Toast.LENGTH_SHORT).show()
}

View File

@@ -9,6 +9,7 @@ import android.hardware.SensorManager
import android.net.Uri
import android.os.Build
import android.provider.Settings
import androidx.compose.foundation.BorderStroke
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.AnimatedVisibility
@@ -60,6 +61,7 @@ import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.ChatBubble
import androidx.compose.material.icons.filled.CheckCircle
import androidx.compose.material.icons.filled.Cloud
import androidx.compose.material.icons.filled.ContentCopy
import androidx.compose.material.icons.filled.ExpandLess
import androidx.compose.material.icons.filled.ExpandMore
import androidx.compose.material.icons.filled.Link
@@ -91,6 +93,7 @@ import androidx.core.content.ContextCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.compose.LocalLifecycleOwner
import ai.openclaw.app.BuildConfig
import ai.openclaw.app.LocationMode
import ai.openclaw.app.MainViewModel
import ai.openclaw.app.node.DeviceNotificationListenerService
@@ -236,8 +239,10 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) {
val smsAvailable =
remember(context) {
context.packageManager?.hasSystemFeature(PackageManager.FEATURE_TELEPHONY) == true
BuildConfig.OPENCLAW_ENABLE_SMS &&
context.packageManager?.hasSystemFeature(PackageManager.FEATURE_TELEPHONY) == true
}
val callLogAvailable = remember { BuildConfig.OPENCLAW_ENABLE_CALL_LOG }
val motionAvailable =
remember(context) {
hasMotionCapabilities(context)
@@ -287,11 +292,15 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) {
}
var enableSms by
rememberSaveable {
mutableStateOf(smsAvailable && isPermissionGranted(context, Manifest.permission.SEND_SMS))
mutableStateOf(
smsAvailable &&
isPermissionGranted(context, Manifest.permission.SEND_SMS) &&
isPermissionGranted(context, Manifest.permission.READ_SMS)
)
}
var enableCallLog by
rememberSaveable {
mutableStateOf(isPermissionGranted(context, Manifest.permission.READ_CALL_LOG))
mutableStateOf(callLogAvailable && isPermissionGranted(context, Manifest.permission.READ_CALL_LOG))
}
var pendingPermissionToggle by remember { mutableStateOf<PermissionToggle?>(null) }
@@ -309,7 +318,7 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) {
PermissionToggle.Calendar -> enableCalendar = enabled
PermissionToggle.Motion -> enableMotion = enabled && motionAvailable
PermissionToggle.Sms -> enableSms = enabled && smsAvailable
PermissionToggle.CallLog -> enableCallLog = enabled
PermissionToggle.CallLog -> enableCallLog = enabled && callLogAvailable
}
}
@@ -336,8 +345,11 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) {
!motionPermissionRequired ||
isPermissionGranted(context, Manifest.permission.ACTIVITY_RECOGNITION)
PermissionToggle.Sms ->
!smsAvailable || isPermissionGranted(context, Manifest.permission.SEND_SMS)
PermissionToggle.CallLog -> isPermissionGranted(context, Manifest.permission.READ_CALL_LOG)
!smsAvailable ||
(isPermissionGranted(context, Manifest.permission.SEND_SMS) &&
isPermissionGranted(context, Manifest.permission.READ_SMS))
PermissionToggle.CallLog ->
!callLogAvailable || isPermissionGranted(context, Manifest.permission.READ_CALL_LOG)
}
fun setSpecialAccessToggleEnabled(toggle: SpecialAccessToggle, enabled: Boolean) {
@@ -361,6 +373,7 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) {
enableSms,
enableCallLog,
smsAvailable,
callLogAvailable,
motionAvailable,
) {
val enabled = mutableListOf<String>()
@@ -375,7 +388,7 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) {
if (enableCalendar) enabled += "Calendar"
if (enableMotion && motionAvailable) enabled += "Motion"
if (smsAvailable && enableSms) enabled += "SMS"
if (enableCallLog) enabled += "Call Log"
if (callLogAvailable && enableCallLog) enabled += "Call Log"
if (enabled.isEmpty()) "None selected" else enabled.joinToString(", ")
}
@@ -604,6 +617,7 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) {
motionPermissionRequired = motionPermissionRequired,
enableSms = enableSms,
smsAvailable = smsAvailable,
callLogAvailable = callLogAvailable,
enableCallLog = enableCallLog,
context = context,
onDiscoveryChange = { checked ->
@@ -698,16 +712,20 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) {
requestPermissionToggle(
PermissionToggle.Sms,
checked,
listOf(Manifest.permission.SEND_SMS),
listOf(Manifest.permission.SEND_SMS, Manifest.permission.READ_SMS),
)
}
},
onCallLogChange = { checked ->
requestPermissionToggle(
PermissionToggle.CallLog,
checked,
listOf(Manifest.permission.READ_CALL_LOG),
)
if (!callLogAvailable) {
setPermissionToggleEnabled(PermissionToggle.CallLog, false)
} else {
requestPermissionToggle(
PermissionToggle.CallLog,
checked,
listOf(Manifest.permission.READ_CALL_LOG),
)
}
},
)
OnboardingStep.FinalCheck ->
@@ -1299,6 +1317,7 @@ private fun PermissionsStep(
motionPermissionRequired: Boolean,
enableSms: Boolean,
smsAvailable: Boolean,
callLogAvailable: Boolean,
enableCallLog: Boolean,
context: Context,
onDiscoveryChange: (Boolean) -> Unit,
@@ -1437,20 +1456,24 @@ private fun PermissionsStep(
InlineDivider()
PermissionToggleRow(
title = "SMS",
subtitle = "Send text messages via the gateway",
subtitle = "Send and search text messages via the gateway",
checked = enableSms,
granted = isPermissionGranted(context, Manifest.permission.SEND_SMS),
granted =
isPermissionGranted(context, Manifest.permission.SEND_SMS) &&
isPermissionGranted(context, Manifest.permission.READ_SMS),
onCheckedChange = onSmsChange,
)
}
InlineDivider()
PermissionToggleRow(
title = "Call Log",
subtitle = "callLog.search",
checked = enableCallLog,
granted = isPermissionGranted(context, Manifest.permission.READ_CALL_LOG),
onCheckedChange = onCallLogChange,
)
if (callLogAvailable) {
InlineDivider()
PermissionToggleRow(
title = "Call Log",
subtitle = "callLog.search",
checked = enableCallLog,
granted = isPermissionGranted(context, Manifest.permission.READ_CALL_LOG),
onCheckedChange = onCallLogChange,
)
}
Text("All settings can be changed later in Settings.", style = onboardingCalloutStyle, color = onboardingTextSecondary)
}
}
@@ -1511,6 +1534,12 @@ private fun FinalStep(
enabledPermissions: String,
methodLabel: String,
) {
val context = androidx.compose.ui.platform.LocalContext.current
val gatewayAddress = parsedGateway?.displayUrl ?: "Invalid gateway URL"
val statusLabel = gatewayStatusForDisplay(statusText)
val showDiagnostics = gatewayStatusHasDiagnostics(statusText)
val pairingRequired = gatewayStatusLooksLikePairing(statusText)
Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
Text("Review", style = onboardingTitle1Style, color = onboardingText)
@@ -1523,7 +1552,7 @@ private fun FinalStep(
SummaryCard(
icon = Icons.Default.Cloud,
label = "Gateway",
value = parsedGateway?.displayUrl ?: "Invalid gateway URL",
value = gatewayAddress,
accentColor = Color(0xFF7C5AC7),
)
SummaryCard(
@@ -1607,7 +1636,7 @@ private fun FinalStep(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(14.dp),
color = onboardingWarningSoft,
border = androidx.compose.foundation.BorderStroke(1.dp, onboardingWarning.copy(alpha = 0.2f)),
border = BorderStroke(1.dp, onboardingWarning.copy(alpha = 0.2f)),
) {
Column(
modifier = Modifier.padding(14.dp),
@@ -1632,13 +1661,66 @@ private fun FinalStep(
)
}
Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
Text("Pairing Required", style = onboardingHeadlineStyle, color = onboardingWarning)
Text("Run these on your gateway host:", style = onboardingCalloutStyle, color = onboardingTextSecondary)
Text(
if (pairingRequired) "Pairing Required" else "Connection Failed",
style = onboardingHeadlineStyle,
color = onboardingWarning,
)
Text(
if (pairingRequired) {
"Approve this phone on the gateway host, or copy the report below."
} else {
"Copy this report and give it to your Claw."
},
style = onboardingCalloutStyle,
color = onboardingTextSecondary,
)
}
}
CommandBlock("openclaw devices list")
CommandBlock("openclaw devices approve <requestId>")
Text("Then tap Connect again.", style = onboardingCalloutStyle, color = onboardingTextSecondary)
if (showDiagnostics) {
Text("Error", style = onboardingCaption1Style.copy(fontWeight = FontWeight.Bold), color = onboardingTextSecondary)
Surface(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(12.dp),
color = onboardingCommandBg,
border = BorderStroke(1.dp, onboardingCommandBorder),
) {
Text(
statusLabel,
modifier = Modifier.padding(horizontal = 14.dp, vertical = 12.dp),
style = onboardingCalloutStyle.copy(fontFamily = FontFamily.Monospace),
color = onboardingCommandText,
)
}
Text(
"OpenClaw Android ${openClawAndroidVersionLabel()}",
style = onboardingCaption1Style,
color = onboardingTextSecondary,
)
Button(
onClick = {
copyGatewayDiagnosticsReport(
context = context,
screen = "onboarding final check",
gatewayAddress = gatewayAddress,
statusText = statusLabel,
)
},
modifier = Modifier.fillMaxWidth().height(48.dp),
shape = RoundedCornerShape(12.dp),
colors = ButtonDefaults.buttonColors(containerColor = onboardingSurface, contentColor = onboardingWarning),
border = BorderStroke(1.dp, onboardingWarning.copy(alpha = 0.3f)),
) {
Icon(Icons.Default.ContentCopy, contentDescription = null, modifier = Modifier.size(18.dp))
Spacer(modifier = Modifier.width(8.dp))
Text("Copy Report for Claw", style = onboardingCalloutStyle.copy(fontWeight = FontWeight.Bold))
}
}
if (pairingRequired) {
CommandBlock("openclaw devices list")
CommandBlock("openclaw devices approve <requestId>")
Text("Then tap Connect again.", style = onboardingCalloutStyle, color = onboardingTextSecondary)
}
}
}
}

View File

@@ -1,13 +1,17 @@
package ai.openclaw.app.ui
import android.app.Activity
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.core.view.WindowCompat
@Composable
fun OpenClawTheme(content: @Composable () -> Unit) {
@@ -16,6 +20,15 @@ fun OpenClawTheme(content: @Composable () -> Unit) {
val colorScheme = if (isDark) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
val mobileColors = if (isDark) darkMobileColors() else lightMobileColors()
val view = LocalView.current
if (!view.isInEditMode) {
SideEffect {
val window = (view.context as Activity).window
WindowCompat.getInsetsController(window, window.decorView)
.isAppearanceLightStatusBars = !isDark
}
}
CompositionLocalProvider(LocalMobileColors provides mobileColors) {
MaterialTheme(colorScheme = colorScheme, content = content)
}

View File

@@ -39,7 +39,9 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.zIndex
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.font.FontWeight
@@ -68,10 +70,19 @@ private enum class StatusVisual {
@Composable
fun PostOnboardingTabs(viewModel: MainViewModel, modifier: Modifier = Modifier) {
var activeTab by rememberSaveable { mutableStateOf(HomeTab.Connect) }
var chatTabStarted by rememberSaveable { mutableStateOf(false) }
var screenTabStarted by rememberSaveable { mutableStateOf(false) }
// Stop TTS when user navigates away from voice tab
// Stop TTS when user navigates away from voice tab, and lazily keep the Chat/Screen tabs
// alive after the first visit so repeated tab switches do not rebuild their UI trees.
LaunchedEffect(activeTab) {
viewModel.setVoiceScreenActive(activeTab == HomeTab.Voice)
if (activeTab == HomeTab.Chat) {
chatTabStarted = true
}
if (activeTab == HomeTab.Screen) {
screenTabStarted = true
}
}
val statusText by viewModel.statusText.collectAsState()
@@ -120,11 +131,35 @@ fun PostOnboardingTabs(viewModel: MainViewModel, modifier: Modifier = Modifier)
.consumeWindowInsets(innerPadding)
.background(mobileBackgroundGradient),
) {
if (chatTabStarted) {
Box(
modifier =
Modifier
.matchParentSize()
.alpha(if (activeTab == HomeTab.Chat) 1f else 0f)
.zIndex(if (activeTab == HomeTab.Chat) 1f else 0f),
) {
ChatSheet(viewModel = viewModel)
}
}
if (screenTabStarted) {
ScreenTabScreen(
viewModel = viewModel,
visible = activeTab == HomeTab.Screen,
modifier =
Modifier
.matchParentSize()
.alpha(if (activeTab == HomeTab.Screen) 1f else 0f)
.zIndex(if (activeTab == HomeTab.Screen) 1f else 0f),
)
}
when (activeTab) {
HomeTab.Connect -> ConnectTabScreen(viewModel = viewModel)
HomeTab.Chat -> ChatSheet(viewModel = viewModel)
HomeTab.Chat -> if (!chatTabStarted) ChatSheet(viewModel = viewModel)
HomeTab.Voice -> VoiceTabScreen(viewModel = viewModel)
HomeTab.Screen -> ScreenTabScreen(viewModel = viewModel)
HomeTab.Screen -> Unit
HomeTab.Settings -> SettingsSheet(viewModel = viewModel)
}
}
@@ -132,16 +167,19 @@ fun PostOnboardingTabs(viewModel: MainViewModel, modifier: Modifier = Modifier)
}
@Composable
private fun ScreenTabScreen(viewModel: MainViewModel) {
private fun ScreenTabScreen(viewModel: MainViewModel, visible: Boolean, modifier: Modifier = Modifier) {
val isConnected by viewModel.isConnected.collectAsState()
LaunchedEffect(isConnected) {
if (isConnected) {
var refreshedForCurrentConnection by rememberSaveable(isConnected) { mutableStateOf(false) }
LaunchedEffect(isConnected, visible, refreshedForCurrentConnection) {
if (visible && isConnected && !refreshedForCurrentConnection) {
viewModel.refreshHomeCanvasOverviewIfConnected()
refreshedForCurrentConnection = true
}
}
Box(modifier = Modifier.fillMaxSize()) {
CanvasScreen(viewModel = viewModel, modifier = Modifier.fillMaxSize())
Box(modifier = modifier.fillMaxSize()) {
CanvasScreen(viewModel = viewModel, visible = visible, modifier = Modifier.fillMaxSize())
}
}

View File

@@ -149,8 +149,10 @@ fun SettingsSheet(viewModel: MainViewModel) {
val smsPermissionAvailable =
remember {
context.packageManager?.hasSystemFeature(PackageManager.FEATURE_TELEPHONY) == true
BuildConfig.OPENCLAW_ENABLE_SMS &&
context.packageManager?.hasSystemFeature(PackageManager.FEATURE_TELEPHONY) == true
}
val callLogPermissionAvailable = remember { BuildConfig.OPENCLAW_ENABLE_CALL_LOG }
val photosPermission =
if (Build.VERSION.SDK_INT >= 33) {
Manifest.permission.READ_MEDIA_IMAGES
@@ -247,12 +249,16 @@ fun SettingsSheet(viewModel: MainViewModel) {
remember {
mutableStateOf(
ContextCompat.checkSelfPermission(context, Manifest.permission.SEND_SMS) ==
PackageManager.PERMISSION_GRANTED &&
ContextCompat.checkSelfPermission(context, Manifest.permission.READ_SMS) ==
PackageManager.PERMISSION_GRANTED,
)
}
val smsPermissionLauncher =
rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
smsPermissionGranted = granted
rememberLauncherForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { perms ->
val sendOk = perms[Manifest.permission.SEND_SMS] == true
val readOk = perms[Manifest.permission.READ_SMS] == true
smsPermissionGranted = sendOk && readOk
viewModel.refreshGatewayConnection()
}
@@ -287,6 +293,8 @@ fun SettingsSheet(viewModel: MainViewModel) {
PackageManager.PERMISSION_GRANTED
smsPermissionGranted =
ContextCompat.checkSelfPermission(context, Manifest.permission.SEND_SMS) ==
PackageManager.PERMISSION_GRANTED &&
ContextCompat.checkSelfPermission(context, Manifest.permission.READ_SMS) ==
PackageManager.PERMISSION_GRANTED
}
}
@@ -507,7 +515,7 @@ fun SettingsSheet(viewModel: MainViewModel) {
colors = listItemColors,
headlineContent = { Text("SMS", style = mobileHeadline) },
supportingContent = {
Text("Send SMS from this device.", style = mobileCallout)
Text("Send and search SMS from this device.", style = mobileCallout)
},
trailingContent = {
Button(
@@ -515,7 +523,7 @@ fun SettingsSheet(viewModel: MainViewModel) {
if (smsPermissionGranted) {
openAppSettings(context)
} else {
smsPermissionLauncher.launch(Manifest.permission.SEND_SMS)
smsPermissionLauncher.launch(arrayOf(Manifest.permission.SEND_SMS, Manifest.permission.READ_SMS))
}
},
colors = settingsPrimaryButtonColors(),
@@ -616,31 +624,33 @@ fun SettingsSheet(viewModel: MainViewModel) {
}
},
)
HorizontalDivider(color = mobileBorder)
ListItem(
modifier = Modifier.fillMaxWidth(),
colors = listItemColors,
headlineContent = { Text("Call Log", style = mobileHeadline) },
supportingContent = { Text("Search recent call history.", style = mobileCallout) },
trailingContent = {
Button(
onClick = {
if (callLogPermissionGranted) {
openAppSettings(context)
} else {
callLogPermissionLauncher.launch(Manifest.permission.READ_CALL_LOG)
}
},
colors = settingsPrimaryButtonColors(),
shape = RoundedCornerShape(14.dp),
) {
Text(
if (callLogPermissionGranted) "Manage" else "Grant",
style = mobileCallout.copy(fontWeight = FontWeight.Bold),
)
}
},
)
if (callLogPermissionAvailable) {
HorizontalDivider(color = mobileBorder)
ListItem(
modifier = Modifier.fillMaxWidth(),
colors = listItemColors,
headlineContent = { Text("Call Log", style = mobileHeadline) },
supportingContent = { Text("Search recent call history.", style = mobileCallout) },
trailingContent = {
Button(
onClick = {
if (callLogPermissionGranted) {
openAppSettings(context)
} else {
callLogPermissionLauncher.launch(Manifest.permission.READ_CALL_LOG)
}
},
colors = settingsPrimaryButtonColors(),
shape = RoundedCornerShape(14.dp),
) {
Text(
if (callLogPermissionGranted) "Manage" else "Grant",
style = mobileCallout.copy(fontWeight = FontWeight.Bold),
)
}
},
)
}
if (motionAvailable) {
HorizontalDivider(color = mobileBorder)
ListItem(

View File

@@ -63,7 +63,6 @@ fun ChatSheetContent(viewModel: MainViewModel) {
LaunchedEffect(mainSessionKey) {
viewModel.loadChat(mainSessionKey)
viewModel.refreshChatSessions(limit = 200)
}
val context = LocalContext.current

View File

@@ -1,338 +0,0 @@
package ai.openclaw.app.voice
import android.media.AudioAttributes
import android.media.AudioFormat
import android.media.AudioManager
import android.media.AudioTrack
import android.util.Base64
import android.util.Log
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import okhttp3.*
import org.json.JSONObject
import kotlin.math.max
/**
* Streams text chunks to ElevenLabs WebSocket API and plays audio in real-time.
*
* Usage:
* 1. Create instance with voice/API config
* 2. Call [start] to open WebSocket + AudioTrack
* 3. Call [sendText] with incremental text chunks as they arrive
* 4. Call [finish] when the full response is ready (sends EOS to ElevenLabs)
* 5. Call [stop] to cancel/cleanup at any time
*
* Audio playback begins as soon as the first audio chunk arrives from ElevenLabs,
* typically within ~100ms of the first text chunk for eleven_flash_v2_5.
*
* Note: eleven_v3 does NOT support WebSocket streaming. Use eleven_flash_v2_5
* or eleven_flash_v2 for lowest latency.
*/
class ElevenLabsStreamingTts(
private val scope: CoroutineScope,
private val voiceId: String,
private val apiKey: String,
private val modelId: String = "eleven_flash_v2_5",
private val outputFormat: String = "pcm_24000",
private val sampleRate: Int = 24000,
) {
companion object {
private const val TAG = "ElevenLabsStreamTTS"
private const val BASE_URL = "wss://api.elevenlabs.io/v1/text-to-speech"
/** Models that support WebSocket input streaming */
val STREAMING_MODELS = setOf(
"eleven_flash_v2_5",
"eleven_flash_v2",
"eleven_multilingual_v2",
"eleven_turbo_v2_5",
"eleven_turbo_v2",
"eleven_monolingual_v1",
)
fun supportsStreaming(modelId: String): Boolean = modelId in STREAMING_MODELS
}
private val _isPlaying = MutableStateFlow(false)
val isPlaying: StateFlow<Boolean> = _isPlaying
private var webSocket: WebSocket? = null
private var audioTrack: AudioTrack? = null
private var trackStarted = false
private var client: OkHttpClient? = null
@Volatile private var stopped = false
@Volatile private var finished = false
@Volatile var hasReceivedAudio = false
private set
private var drainJob: Job? = null
// Track text already sent so we only send incremental chunks
private var sentTextLength = 0
@Volatile private var wsReady = false
private val pendingText = mutableListOf<String>()
/**
* Open the WebSocket connection and prepare AudioTrack.
* Must be called before [sendText].
*/
fun start() {
stopped = false
finished = false
hasReceivedAudio = false
sentTextLength = 0
trackStarted = false
wsReady = false
sentFullText = ""
synchronized(pendingText) { pendingText.clear() }
// Prepare AudioTrack
val minBuffer = AudioTrack.getMinBufferSize(
sampleRate,
AudioFormat.CHANNEL_OUT_MONO,
AudioFormat.ENCODING_PCM_16BIT,
)
val bufferSize = max(minBuffer * 2, 8 * 1024)
val track = AudioTrack(
AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.setUsage(AudioAttributes.USAGE_MEDIA)
.build(),
AudioFormat.Builder()
.setSampleRate(sampleRate)
.setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
.build(),
bufferSize,
AudioTrack.MODE_STREAM,
AudioManager.AUDIO_SESSION_ID_GENERATE,
)
if (track.state != AudioTrack.STATE_INITIALIZED) {
track.release()
Log.e(TAG, "AudioTrack init failed")
return
}
audioTrack = track
_isPlaying.value = true
// Open WebSocket
val url = "$BASE_URL/$voiceId/stream-input?model_id=$modelId&output_format=$outputFormat"
val okClient = OkHttpClient.Builder()
.readTimeout(30, java.util.concurrent.TimeUnit.SECONDS)
.writeTimeout(10, java.util.concurrent.TimeUnit.SECONDS)
.build()
client = okClient
val request = Request.Builder()
.url(url)
.header("xi-api-key", apiKey)
.build()
webSocket = okClient.newWebSocket(request, object : WebSocketListener() {
override fun onOpen(webSocket: WebSocket, response: Response) {
Log.d(TAG, "WebSocket connected")
// Send initial config with voice settings
val config = JSONObject().apply {
put("text", " ")
put("voice_settings", JSONObject().apply {
put("stability", 0.5)
put("similarity_boost", 0.8)
put("use_speaker_boost", false)
})
put("generation_config", JSONObject().apply {
put("chunk_length_schedule", org.json.JSONArray(listOf(120, 160, 250, 290)))
})
}
webSocket.send(config.toString())
wsReady = true
// Flush any text that was queued before WebSocket was ready
synchronized(pendingText) {
for (queued in pendingText) {
val msg = JSONObject().apply { put("text", queued) }
webSocket.send(msg.toString())
Log.d(TAG, "flushed queued chunk: ${queued.length} chars")
}
pendingText.clear()
}
// Send deferred EOS if finish() was called before WebSocket was ready
if (finished) {
val eos = JSONObject().apply { put("text", "") }
webSocket.send(eos.toString())
Log.d(TAG, "sent deferred EOS")
}
}
override fun onMessage(webSocket: WebSocket, text: String) {
if (stopped) return
try {
val json = JSONObject(text)
val audio = json.optString("audio", "")
if (audio.isNotEmpty()) {
val pcmBytes = Base64.decode(audio, Base64.DEFAULT)
writeToTrack(pcmBytes)
}
} catch (e: Exception) {
Log.e(TAG, "Error parsing WebSocket message: ${e.message}")
}
}
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
Log.e(TAG, "WebSocket error: ${t.message}")
stopped = true
cleanup()
}
override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
Log.d(TAG, "WebSocket closed: $code $reason")
// Wait for AudioTrack to finish playing buffered audio, then cleanup
drainJob = scope.launch(Dispatchers.IO) {
drainAudioTrack()
cleanup()
}
}
})
}
/**
* Send incremental text. Call with the full accumulated text so far —
* only the new portion (since last send) will be transmitted.
*/
// Track the full text we've sent so we can detect replacement vs append
private var sentFullText = ""
/**
// If we already sent a superset of this text, it's just a stale/out-of-order
// event from a different thread — not a real divergence. Ignore it.
if (sentFullText.startsWith(fullText)) return true
* Returns true if text was accepted, false if text diverged (caller should restart).
*/
@Synchronized
fun sendText(fullText: String): Boolean {
if (stopped) return false
if (finished) return true // Already finishing — not a diverge, don't restart
// Detect text replacement: if the new text doesn't start with what we already sent,
// the stream has diverged (e.g., tool call interrupted and text was replaced).
if (sentFullText.isNotEmpty() && !fullText.startsWith(sentFullText)) {
// If we already sent a superset of this text, it's just a stale/out-of-order
// event from a different thread — not a real divergence. Ignore it.
if (sentFullText.startsWith(fullText)) return true
Log.d(TAG, "text diverged — sent='${sentFullText.take(60)}' new='${fullText.take(60)}'")
return false
}
if (fullText.length > sentTextLength) {
val newText = fullText.substring(sentTextLength)
sentTextLength = fullText.length
sentFullText = fullText
val ws = webSocket
if (ws != null && wsReady) {
val msg = JSONObject().apply { put("text", newText) }
ws.send(msg.toString())
Log.d(TAG, "sent chunk: ${newText.length} chars")
} else {
// Queue if WebSocket not connected yet (ws null = still connecting, wsReady false = handshake pending)
synchronized(pendingText) { pendingText.add(newText) }
Log.d(TAG, "queued chunk: ${newText.length} chars (ws not ready)")
}
}
return true
}
/**
* Signal that no more text is coming. Sends EOS to ElevenLabs.
* The WebSocket will close after generating remaining audio.
*/
@Synchronized
fun finish() {
if (stopped || finished) return
finished = true
val ws = webSocket
if (ws != null && wsReady) {
// Send empty text to signal end of stream
val eos = JSONObject().apply { put("text", "") }
ws.send(eos.toString())
Log.d(TAG, "sent EOS")
}
// else: WebSocket not ready yet; onOpen will send EOS after flushing queued text
}
/**
* Immediately stop playback and close everything.
*/
fun stop() {
stopped = true
finished = true
drainJob?.cancel()
drainJob = null
webSocket?.cancel()
webSocket = null
val track = audioTrack
audioTrack = null
if (track != null) {
try {
track.pause()
track.flush()
track.release()
} catch (_: Throwable) {}
}
_isPlaying.value = false
client?.dispatcher?.executorService?.shutdown()
client = null
}
private fun writeToTrack(pcmBytes: ByteArray) {
val track = audioTrack ?: return
if (stopped) return
// Start playback on first audio chunk — avoids underrun
if (!trackStarted) {
track.play()
trackStarted = true
hasReceivedAudio = true
Log.d(TAG, "AudioTrack started on first chunk")
}
var offset = 0
while (offset < pcmBytes.size && !stopped) {
val wrote = track.write(pcmBytes, offset, pcmBytes.size - offset)
if (wrote <= 0) {
if (stopped) return
Log.w(TAG, "AudioTrack write returned $wrote")
break
}
offset += wrote
}
}
private fun drainAudioTrack() {
if (stopped) return
// Wait up to 10s for audio to finish playing
val deadline = System.currentTimeMillis() + 10_000
while (!stopped && System.currentTimeMillis() < deadline) {
// Check if track is still playing
val track = audioTrack ?: return
if (track.playState != AudioTrack.PLAYSTATE_PLAYING) return
try {
Thread.sleep(100)
} catch (_: InterruptedException) {
return
}
}
}
private fun cleanup() {
val track = audioTrack
audioTrack = null
if (track != null) {
try {
track.stop()
track.release()
} catch (_: Throwable) {}
}
_isPlaying.value = false
client?.dispatcher?.executorService?.shutdown()
client = null
}
}

View File

@@ -1,98 +0,0 @@
package ai.openclaw.app.voice
import android.media.MediaDataSource
import kotlin.math.min
internal class StreamingMediaDataSource : MediaDataSource() {
private data class Chunk(val start: Long, val data: ByteArray)
private val lock = Object()
private val chunks = ArrayList<Chunk>()
private var totalSize: Long = 0
private var closed = false
private var finished = false
private var lastReadIndex = 0
fun append(data: ByteArray) {
if (data.isEmpty()) return
synchronized(lock) {
if (closed || finished) return
val chunk = Chunk(totalSize, data)
chunks.add(chunk)
totalSize += data.size.toLong()
lock.notifyAll()
}
}
fun finish() {
synchronized(lock) {
if (closed) return
finished = true
lock.notifyAll()
}
}
fun fail() {
synchronized(lock) {
closed = true
lock.notifyAll()
}
}
override fun readAt(position: Long, buffer: ByteArray, offset: Int, size: Int): Int {
if (position < 0) return -1
synchronized(lock) {
while (!closed && !finished && position >= totalSize) {
lock.wait()
}
if (closed) return -1
if (position >= totalSize && finished) return -1
val available = (totalSize - position).toInt()
val toRead = min(size, available)
var remaining = toRead
var destOffset = offset
var pos = position
var index = findChunkIndex(pos)
while (remaining > 0 && index < chunks.size) {
val chunk = chunks[index]
val inChunkOffset = (pos - chunk.start).toInt()
if (inChunkOffset >= chunk.data.size) {
index++
continue
}
val copyLen = min(remaining, chunk.data.size - inChunkOffset)
System.arraycopy(chunk.data, inChunkOffset, buffer, destOffset, copyLen)
remaining -= copyLen
destOffset += copyLen
pos += copyLen
if (inChunkOffset + copyLen >= chunk.data.size) {
index++
}
}
return toRead - remaining
}
}
override fun getSize(): Long = -1
override fun close() {
synchronized(lock) {
closed = true
lock.notifyAll()
}
}
private fun findChunkIndex(position: Long): Int {
var index = lastReadIndex
while (index < chunks.size) {
val chunk = chunks[index]
if (position < chunk.start + chunk.data.size) break
index++
}
lastReadIndex = index
return index
}
}

View File

@@ -4,116 +4,23 @@ import ai.openclaw.app.normalizeMainKey
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.booleanOrNull
import kotlinx.serialization.json.contentOrNull
internal data class TalkProviderConfigSelection(
val provider: String,
val config: JsonObject,
val normalizedPayload: Boolean,
)
internal data class TalkModeGatewayConfigState(
val activeProvider: String,
val normalizedPayload: Boolean,
val missingResolvedPayload: Boolean,
val mainSessionKey: String,
val defaultVoiceId: String?,
val voiceAliases: Map<String, String>,
val defaultModelId: String,
val defaultOutputFormat: String,
val apiKey: String?,
val interruptOnSpeech: Boolean?,
val silenceTimeoutMs: Long,
)
internal object TalkModeGatewayConfigParser {
private const val defaultTalkProvider = "elevenlabs"
fun parse(
config: JsonObject?,
defaultProvider: String,
defaultModelIdFallback: String,
defaultOutputFormatFallback: String,
envVoice: String?,
sagVoice: String?,
envKey: String?,
): TalkModeGatewayConfigState {
fun parse(config: JsonObject?): TalkModeGatewayConfigState {
val talk = config?.get("talk").asObjectOrNull()
val selection = selectTalkProviderConfig(talk)
val activeProvider = selection?.provider ?: defaultProvider
val activeConfig = selection?.config
val sessionCfg = config?.get("session").asObjectOrNull()
val mainKey = normalizeMainKey(sessionCfg?.get("mainKey").asStringOrNull())
val voice = activeConfig?.get("voiceId")?.asStringOrNull()?.trim()?.takeIf { it.isNotEmpty() }
val aliases =
activeConfig?.get("voiceAliases").asObjectOrNull()?.entries?.mapNotNull { (key, value) ->
val id = value.asStringOrNull()?.trim()?.takeIf { it.isNotEmpty() } ?: return@mapNotNull null
normalizeTalkAliasKey(key).takeIf { it.isNotEmpty() }?.let { it to id }
}?.toMap().orEmpty()
val model = activeConfig?.get("modelId")?.asStringOrNull()?.trim()?.takeIf { it.isNotEmpty() }
val outputFormat =
activeConfig?.get("outputFormat")?.asStringOrNull()?.trim()?.takeIf { it.isNotEmpty() }
val key = activeConfig?.get("apiKey")?.asStringOrNull()?.trim()?.takeIf { it.isNotEmpty() }
val interrupt = talk?.get("interruptOnSpeech")?.asBooleanOrNull()
val silenceTimeoutMs = resolvedSilenceTimeoutMs(talk)
return TalkModeGatewayConfigState(
activeProvider = activeProvider,
normalizedPayload = selection?.normalizedPayload == true,
missingResolvedPayload = talk != null && selection == null,
mainSessionKey = mainKey,
defaultVoiceId =
if (activeProvider == defaultProvider) {
voice ?: envVoice?.takeIf { it.isNotEmpty() } ?: sagVoice?.takeIf { it.isNotEmpty() }
} else {
voice
},
voiceAliases = aliases,
defaultModelId = model ?: defaultModelIdFallback,
defaultOutputFormat = outputFormat ?: defaultOutputFormatFallback,
apiKey = key ?: envKey?.takeIf { it.isNotEmpty() },
interruptOnSpeech = interrupt,
silenceTimeoutMs = silenceTimeoutMs,
)
}
fun fallback(
defaultProvider: String,
defaultModelIdFallback: String,
defaultOutputFormatFallback: String,
envVoice: String?,
sagVoice: String?,
envKey: String?,
): TalkModeGatewayConfigState =
TalkModeGatewayConfigState(
activeProvider = defaultProvider,
normalizedPayload = false,
missingResolvedPayload = false,
mainSessionKey = "main",
defaultVoiceId = envVoice?.takeIf { it.isNotEmpty() } ?: sagVoice?.takeIf { it.isNotEmpty() },
voiceAliases = emptyMap(),
defaultModelId = defaultModelIdFallback,
defaultOutputFormat = defaultOutputFormatFallback,
apiKey = envKey?.takeIf { it.isNotEmpty() },
interruptOnSpeech = null,
silenceTimeoutMs = TalkDefaults.defaultSilenceTimeoutMs,
)
fun selectTalkProviderConfig(talk: JsonObject?): TalkProviderConfigSelection? {
if (talk == null) return null
selectResolvedTalkProviderConfig(talk)?.let { return it }
val rawProvider = talk["provider"].asStringOrNull()
val rawProviders = talk["providers"].asObjectOrNull()
val hasNormalizedPayload = rawProvider != null || rawProviders != null
if (hasNormalizedPayload) {
return null
}
return TalkProviderConfigSelection(
provider = defaultTalkProvider,
config = talk,
normalizedPayload = false,
mainSessionKey = normalizeMainKey(sessionCfg?.get("mainKey").asStringOrNull()),
interruptOnSpeech = talk?.get("interruptOnSpeech").asBooleanOrNull(),
silenceTimeoutMs = resolvedSilenceTimeoutMs(talk),
)
}
@@ -127,26 +34,8 @@ internal object TalkModeGatewayConfigParser {
}
return timeout.toLong()
}
private fun selectResolvedTalkProviderConfig(talk: JsonObject): TalkProviderConfigSelection? {
val resolved = talk["resolved"].asObjectOrNull() ?: return null
val providerId = normalizeTalkProviderId(resolved["provider"].asStringOrNull()) ?: return null
return TalkProviderConfigSelection(
provider = providerId,
config = resolved["config"].asObjectOrNull() ?: buildJsonObject {},
normalizedPayload = true,
)
}
private fun normalizeTalkProviderId(raw: String?): String? {
val trimmed = raw?.trim()?.lowercase().orEmpty()
return trimmed.takeIf { it.isNotEmpty() }
}
}
private fun normalizeTalkAliasKey(value: String): String =
value.trim().lowercase()
private fun JsonElement?.asStringOrNull(): String? =
this?.let { element ->
element as? JsonPrimitive

View File

@@ -1,122 +0,0 @@
package ai.openclaw.app.voice
import java.net.HttpURLConnection
import java.net.URL
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
internal data class ElevenLabsVoice(val voiceId: String, val name: String?)
internal data class TalkModeResolvedVoice(
val voiceId: String?,
val fallbackVoiceId: String?,
val defaultVoiceId: String?,
val currentVoiceId: String?,
val selectedVoiceName: String? = null,
)
internal object TalkModeVoiceResolver {
fun resolveVoiceAlias(value: String?, voiceAliases: Map<String, String>): String? {
val trimmed = value?.trim().orEmpty()
if (trimmed.isEmpty()) return null
val normalized = normalizeAliasKey(trimmed)
voiceAliases[normalized]?.let { return it }
if (voiceAliases.values.any { it.equals(trimmed, ignoreCase = true) }) return trimmed
return if (isLikelyVoiceId(trimmed)) trimmed else null
}
suspend fun resolveVoiceId(
preferred: String?,
fallbackVoiceId: String?,
defaultVoiceId: String?,
currentVoiceId: String?,
voiceOverrideActive: Boolean,
listVoices: suspend () -> List<ElevenLabsVoice>,
): TalkModeResolvedVoice {
val trimmed = preferred?.trim().orEmpty()
if (trimmed.isNotEmpty()) {
return TalkModeResolvedVoice(
voiceId = trimmed,
fallbackVoiceId = fallbackVoiceId,
defaultVoiceId = defaultVoiceId,
currentVoiceId = currentVoiceId,
)
}
if (!fallbackVoiceId.isNullOrBlank()) {
return TalkModeResolvedVoice(
voiceId = fallbackVoiceId,
fallbackVoiceId = fallbackVoiceId,
defaultVoiceId = defaultVoiceId,
currentVoiceId = currentVoiceId,
)
}
val first = listVoices().firstOrNull()
if (first == null) {
return TalkModeResolvedVoice(
voiceId = null,
fallbackVoiceId = fallbackVoiceId,
defaultVoiceId = defaultVoiceId,
currentVoiceId = currentVoiceId,
)
}
return TalkModeResolvedVoice(
voiceId = first.voiceId,
fallbackVoiceId = first.voiceId,
defaultVoiceId = if (defaultVoiceId.isNullOrBlank()) first.voiceId else defaultVoiceId,
currentVoiceId = if (voiceOverrideActive) currentVoiceId else first.voiceId,
selectedVoiceName = first.name,
)
}
suspend fun listVoices(apiKey: String, json: Json): List<ElevenLabsVoice> {
return withContext(Dispatchers.IO) {
val url = URL("https://api.elevenlabs.io/v1/voices")
val conn = url.openConnection() as HttpURLConnection
try {
conn.requestMethod = "GET"
conn.connectTimeout = 15_000
conn.readTimeout = 15_000
conn.setRequestProperty("xi-api-key", apiKey)
val code = conn.responseCode
val stream = if (code >= 400) conn.errorStream else conn.inputStream
val data = stream?.use { it.readBytes() } ?: byteArrayOf()
if (code >= 400) {
val message = data.toString(Charsets.UTF_8)
throw IllegalStateException("ElevenLabs voices failed: $code $message")
}
val root = json.parseToJsonElement(data.toString(Charsets.UTF_8)).asObjectOrNull()
val voices = (root?.get("voices") as? JsonArray) ?: JsonArray(emptyList())
voices.mapNotNull { entry ->
val obj = entry.asObjectOrNull() ?: return@mapNotNull null
val voiceId = obj["voice_id"].asStringOrNull() ?: return@mapNotNull null
val name = obj["name"].asStringOrNull()
ElevenLabsVoice(voiceId, name)
}
} finally {
conn.disconnect()
}
}
}
private fun isLikelyVoiceId(value: String): Boolean {
if (value.length < 10) return false
return value.all { it.isLetterOrDigit() || it == '-' || it == '_' }
}
private fun normalizeAliasKey(value: String): String =
value.trim().lowercase()
}
private fun JsonElement?.asObjectOrNull(): JsonObject? = this as? JsonObject
private fun JsonElement?.asStringOrNull(): String? =
(this as? JsonPrimitive)?.takeIf { it.isString }?.content

View File

@@ -0,0 +1,13 @@
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission
android:name="android.permission.SEND_SMS"
tools:node="remove" />
<uses-permission
android:name="android.permission.READ_SMS"
tools:node="remove" />
<uses-permission
android:name="android.permission.READ_CALL_LOG"
tools:node="remove" />
</manifest>

View File

@@ -0,0 +1,42 @@
package ai.openclaw.app.node
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
class CanvasActionTrustTest {
@Test
fun acceptsBundledScaffoldAsset() {
assertTrue(CanvasActionTrust.isTrustedCanvasActionUrl(CanvasActionTrust.scaffoldAssetUrl, emptyList()))
}
@Test
fun acceptsTrustedA2uiPageOnAdvertisedCanvasHost() {
assertTrue(
CanvasActionTrust.isTrustedCanvasActionUrl(
rawUrl = "https://canvas.example.com:9443/__openclaw__/cap/token/__openclaw__/a2ui/?platform=android",
trustedA2uiUrls = listOf("https://canvas.example.com:9443/__openclaw__/cap/token/__openclaw__/a2ui/?platform=android"),
),
)
}
@Test
fun rejectsDifferentOriginEvenIfPathMatches() {
assertFalse(
CanvasActionTrust.isTrustedCanvasActionUrl(
rawUrl = "https://evil.example.com:9443/__openclaw__/cap/token/__openclaw__/a2ui/?platform=android",
trustedA2uiUrls = listOf("https://canvas.example.com:9443/__openclaw__/cap/token/__openclaw__/a2ui/?platform=android"),
),
)
}
@Test
fun rejectsUntrustedCanvasPagePathOnTrustedOrigin() {
assertFalse(
CanvasActionTrust.isTrustedCanvasActionUrl(
rawUrl = "https://canvas.example.com:9443/untrusted/index.html",
trustedA2uiUrls = listOf("https://canvas.example.com:9443/__openclaw__/cap/token/__openclaw__/a2ui/?platform=android"),
),
)
}
}

View File

@@ -26,7 +26,6 @@ class InvokeCommandRegistryTest {
OpenClawCapability.Photos.rawValue,
OpenClawCapability.Contacts.rawValue,
OpenClawCapability.Calendar.rawValue,
OpenClawCapability.CallLog.rawValue,
)
private val optionalCapabilities =
@@ -34,6 +33,7 @@ class InvokeCommandRegistryTest {
OpenClawCapability.Camera.rawValue,
OpenClawCapability.Location.rawValue,
OpenClawCapability.Sms.rawValue,
OpenClawCapability.CallLog.rawValue,
OpenClawCapability.VoiceWake.rawValue,
OpenClawCapability.Motion.rawValue,
)
@@ -52,7 +52,6 @@ class InvokeCommandRegistryTest {
OpenClawContactsCommand.Add.rawValue,
OpenClawCalendarCommand.Events.rawValue,
OpenClawCalendarCommand.Add.rawValue,
OpenClawCallLogCommand.Search.rawValue,
)
private val optionalCommands =
@@ -64,6 +63,8 @@ class InvokeCommandRegistryTest {
OpenClawMotionCommand.Activity.rawValue,
OpenClawMotionCommand.Pedometer.rawValue,
OpenClawSmsCommand.Send.rawValue,
OpenClawSmsCommand.Search.rawValue,
OpenClawCallLogCommand.Search.rawValue,
)
private val debugCommands = setOf("debug.logs", "debug.ed25519")
@@ -83,7 +84,9 @@ class InvokeCommandRegistryTest {
defaultFlags(
cameraEnabled = true,
locationEnabled = true,
smsAvailable = true,
sendSmsAvailable = true,
readSmsAvailable = true,
callLogAvailable = true,
voiceWakeEnabled = true,
motionActivityAvailable = true,
motionPedometerAvailable = true,
@@ -108,7 +111,9 @@ class InvokeCommandRegistryTest {
defaultFlags(
cameraEnabled = true,
locationEnabled = true,
smsAvailable = true,
sendSmsAvailable = true,
readSmsAvailable = true,
callLogAvailable = true,
motionActivityAvailable = true,
motionPedometerAvailable = true,
debugBuild = true,
@@ -125,7 +130,9 @@ class InvokeCommandRegistryTest {
NodeRuntimeFlags(
cameraEnabled = false,
locationEnabled = false,
smsAvailable = false,
sendSmsAvailable = false,
readSmsAvailable = false,
callLogAvailable = false,
voiceWakeEnabled = false,
motionActivityAvailable = true,
motionPedometerAvailable = false,
@@ -137,10 +144,58 @@ class InvokeCommandRegistryTest {
assertFalse(commands.contains(OpenClawMotionCommand.Pedometer.rawValue))
}
@Test
fun advertisedCommands_splitsSmsSendAndSearchAvailability() {
val readOnlyCommands =
InvokeCommandRegistry.advertisedCommands(
defaultFlags(readSmsAvailable = true),
)
val sendOnlyCommands =
InvokeCommandRegistry.advertisedCommands(
defaultFlags(sendSmsAvailable = true),
)
assertTrue(readOnlyCommands.contains(OpenClawSmsCommand.Search.rawValue))
assertFalse(readOnlyCommands.contains(OpenClawSmsCommand.Send.rawValue))
assertTrue(sendOnlyCommands.contains(OpenClawSmsCommand.Send.rawValue))
assertFalse(sendOnlyCommands.contains(OpenClawSmsCommand.Search.rawValue))
}
@Test
fun advertisedCapabilities_includeSmsWhenEitherSmsPathIsAvailable() {
val readOnlyCapabilities =
InvokeCommandRegistry.advertisedCapabilities(
defaultFlags(readSmsAvailable = true),
)
val sendOnlyCapabilities =
InvokeCommandRegistry.advertisedCapabilities(
defaultFlags(sendSmsAvailable = true),
)
assertTrue(readOnlyCapabilities.contains(OpenClawCapability.Sms.rawValue))
assertTrue(sendOnlyCapabilities.contains(OpenClawCapability.Sms.rawValue))
}
@Test
fun advertisedCommands_excludesCallLogWhenUnavailable() {
val commands = InvokeCommandRegistry.advertisedCommands(defaultFlags(callLogAvailable = false))
assertFalse(commands.contains(OpenClawCallLogCommand.Search.rawValue))
}
@Test
fun advertisedCapabilities_excludesCallLogWhenUnavailable() {
val capabilities = InvokeCommandRegistry.advertisedCapabilities(defaultFlags(callLogAvailable = false))
assertFalse(capabilities.contains(OpenClawCapability.CallLog.rawValue))
}
private fun defaultFlags(
cameraEnabled: Boolean = false,
locationEnabled: Boolean = false,
smsAvailable: Boolean = false,
sendSmsAvailable: Boolean = false,
readSmsAvailable: Boolean = false,
callLogAvailable: Boolean = false,
voiceWakeEnabled: Boolean = false,
motionActivityAvailable: Boolean = false,
motionPedometerAvailable: Boolean = false,
@@ -149,7 +204,9 @@ class InvokeCommandRegistryTest {
NodeRuntimeFlags(
cameraEnabled = cameraEnabled,
locationEnabled = locationEnabled,
smsAvailable = smsAvailable,
sendSmsAvailable = sendSmsAvailable,
readSmsAvailable = readSmsAvailable,
callLogAvailable = callLogAvailable,
voiceWakeEnabled = voiceWakeEnabled,
motionActivityAvailable = motionActivityAvailable,
motionPedometerAvailable = motionPedometerAvailable,

View File

@@ -0,0 +1,88 @@
package ai.openclaw.app.node
import android.content.Context
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
class LocationHandlerTest : NodeHandlerRobolectricTest() {
@Test
fun handleLocationGet_requiresLocationPermissionWhenNeitherFineNorCoarse() =
runTest {
val handler =
LocationHandler.forTesting(
appContext = appContext(),
dataSource =
FakeLocationDataSource(
fineGranted = false,
coarseGranted = false,
),
)
val result = handler.handleLocationGet(null)
assertFalse(result.ok)
assertEquals("LOCATION_PERMISSION_REQUIRED", result.error?.code)
}
@Test
fun handleLocationGet_requiresForegroundBeforeLocationPermission() =
runTest {
val handler =
LocationHandler.forTesting(
appContext = appContext(),
dataSource =
FakeLocationDataSource(
fineGranted = true,
coarseGranted = true,
),
isForeground = { false },
)
val result = handler.handleLocationGet(null)
assertFalse(result.ok)
assertEquals("LOCATION_BACKGROUND_UNAVAILABLE", result.error?.code)
}
@Test
fun hasFineLocationPermission_reflectsDataSource() {
val denied =
LocationHandler.forTesting(
appContext = appContext(),
dataSource = FakeLocationDataSource(fineGranted = false, coarseGranted = true),
)
assertFalse(denied.hasFineLocationPermission())
assertTrue(denied.hasCoarseLocationPermission())
val granted =
LocationHandler.forTesting(
appContext = appContext(),
dataSource = FakeLocationDataSource(fineGranted = true, coarseGranted = false),
)
assertTrue(granted.hasFineLocationPermission())
assertFalse(granted.hasCoarseLocationPermission())
}
}
private class FakeLocationDataSource(
private val fineGranted: Boolean,
private val coarseGranted: Boolean,
) : LocationDataSource {
override fun hasFinePermission(context: Context): Boolean = fineGranted
override fun hasCoarsePermission(context: Context): Boolean = coarseGranted
override suspend fun fetchLocation(
desiredProviders: List<String>,
maxAgeMs: Long?,
timeoutMs: Long,
isPrecise: Boolean,
): LocationCaptureManager.Payload {
throw IllegalStateException(
"LocationHandlerTest: fetchLocation must not run in this scenario",
)
}
}

View File

@@ -88,4 +88,95 @@ class SmsManagerTest {
assertFalse(plan.useMultipart)
assertEquals(listOf("hello"), plan.parts)
}
@Test
fun parseQueryParamsAcceptsEmptyPayload() {
val result = SmsManager.parseQueryParams(null, json)
assertTrue(result is SmsManager.QueryParseResult.Ok)
val ok = result as SmsManager.QueryParseResult.Ok
assertEquals(25, ok.params.limit)
assertEquals(0, ok.params.offset)
}
@Test
fun parseQueryParamsRejectsInvalidJson() {
val result = SmsManager.parseQueryParams("not-json", json)
assertTrue(result is SmsManager.QueryParseResult.Error)
val error = result as SmsManager.QueryParseResult.Error
assertEquals("INVALID_REQUEST: expected JSON object", error.error)
}
@Test
fun parseQueryParamsRejectsNonObjectJson() {
val result = SmsManager.parseQueryParams("[]", json)
assertTrue(result is SmsManager.QueryParseResult.Error)
val error = result as SmsManager.QueryParseResult.Error
assertEquals("INVALID_REQUEST: expected JSON object", error.error)
}
@Test
fun parseQueryParamsParsesLimitAndOffset() {
val result = SmsManager.parseQueryParams("{\"limit\":10,\"offset\":5}", json)
assertTrue(result is SmsManager.QueryParseResult.Ok)
val ok = result as SmsManager.QueryParseResult.Ok
assertEquals(10, ok.params.limit)
assertEquals(5, ok.params.offset)
}
@Test
fun parseQueryParamsClampsLimitRange() {
val result = SmsManager.parseQueryParams("{\"limit\":300}", json)
assertTrue(result is SmsManager.QueryParseResult.Ok)
val ok = result as SmsManager.QueryParseResult.Ok
assertEquals(200, ok.params.limit)
}
@Test
fun parseQueryParamsParsesPhoneNumber() {
val result = SmsManager.parseQueryParams("{\"phoneNumber\":\"+1234567890\"}", json)
assertTrue(result is SmsManager.QueryParseResult.Ok)
val ok = result as SmsManager.QueryParseResult.Ok
assertEquals("+1234567890", ok.params.phoneNumber)
}
@Test
fun parseQueryParamsParsesContactName() {
val result = SmsManager.parseQueryParams("{\"contactName\":\"lixuankai\"}", json)
assertTrue(result is SmsManager.QueryParseResult.Ok)
val ok = result as SmsManager.QueryParseResult.Ok
assertEquals("lixuankai", ok.params.contactName)
}
@Test
fun parseQueryParamsParsesKeyword() {
val result = SmsManager.parseQueryParams("{\"keyword\":\"test\"}", json)
assertTrue(result is SmsManager.QueryParseResult.Ok)
val ok = result as SmsManager.QueryParseResult.Ok
assertEquals("test", ok.params.keyword)
}
@Test
fun parseQueryParamsParsesTimeRange() {
val result = SmsManager.parseQueryParams("{\"startTime\":1000,\"endTime\":2000}", json)
assertTrue(result is SmsManager.QueryParseResult.Ok)
val ok = result as SmsManager.QueryParseResult.Ok
assertEquals(1000L, ok.params.startTime)
assertEquals(2000L, ok.params.endTime)
}
@Test
fun parseQueryParamsParsesType() {
val result = SmsManager.parseQueryParams("{\"type\":1}", json)
assertTrue(result is SmsManager.QueryParseResult.Ok)
val ok = result as SmsManager.QueryParseResult.Ok
assertEquals(1, ok.params.type)
}
@Test
fun parseQueryParamsParsesReadStatus() {
val result = SmsManager.parseQueryParams("{\"isRead\":true}", json)
assertTrue(result is SmsManager.QueryParseResult.Ok)
val ok = result as SmsManager.QueryParseResult.Ok
assertEquals(true, ok.params.isRead)
}
}

View File

@@ -46,4 +46,18 @@ class OpenClawCanvasA2UIActionTest {
js,
)
}
@Test
fun jsDispatchA2uiStatusQuotesControlCharacters() {
val js =
OpenClawCanvasA2UIAction.jsDispatchA2UIActionStatus(
actionId = "a1\n\u2028\"",
ok = false,
error = "parse failed\n\t\u2029\\",
)
assertEquals(
"window.dispatchEvent(new CustomEvent('openclaw:a2ui-action-status', { detail: { id: \"a1\\n\\u2028\\\"\", ok: false, error: \"parse failed\\n\\t\\u2029\\\\\" } }));",
js,
)
}
}

View File

@@ -90,4 +90,9 @@ class OpenClawProtocolConstantsTest {
fun callLogCommandsUseStableStrings() {
assertEquals("callLog.search", OpenClawCallLogCommand.Search.rawValue)
}
@Test
fun smsCommandsUseStableStrings() {
assertEquals("sms.search", OpenClawSmsCommand.Search.rawValue)
}
}

View File

@@ -4,8 +4,86 @@ import java.util.Base64
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
@RunWith(RobolectricTestRunner::class)
class GatewayConfigResolverTest {
@Test
fun parseGatewayEndpointUsesDefaultTlsPortForBareWssUrls() {
val parsed = parseGatewayEndpoint("wss://gateway.example")
assertEquals(
GatewayEndpointConfig(
host = "gateway.example",
port = 443,
tls = true,
displayUrl = "https://gateway.example",
),
parsed,
)
}
@Test
fun parseGatewayEndpointUsesDefaultCleartextPortForBareWsUrls() {
val parsed = parseGatewayEndpoint("ws://gateway.example")
assertEquals(
GatewayEndpointConfig(
host = "gateway.example",
port = 18789,
tls = false,
displayUrl = "http://gateway.example:18789",
),
parsed,
)
}
@Test
fun parseGatewayEndpointOmitsExplicitDefaultTlsPortFromDisplayUrl() {
val parsed = parseGatewayEndpoint("https://gateway.example:443")
assertEquals(
GatewayEndpointConfig(
host = "gateway.example",
port = 443,
tls = true,
displayUrl = "https://gateway.example",
),
parsed,
)
}
@Test
fun parseGatewayEndpointKeepsExplicitNonDefaultPortInDisplayUrl() {
val parsed = parseGatewayEndpoint("http://gateway.example:8080")
assertEquals(
GatewayEndpointConfig(
host = "gateway.example",
port = 8080,
tls = false,
displayUrl = "http://gateway.example:8080",
),
parsed,
)
}
@Test
fun parseGatewayEndpointKeepsExplicitCleartextPort80InDisplayUrl() {
val parsed = parseGatewayEndpoint("http://gateway.example:80")
assertEquals(
GatewayEndpointConfig(
host = "gateway.example",
port = 80,
tls = false,
displayUrl = "http://gateway.example:80",
),
parsed,
)
}
@Test
fun resolveScannedSetupCodeAcceptsRawSetupCode() {
val setupCode =

View File

@@ -1,100 +0,0 @@
package ai.openclaw.app.voice
import java.io.File
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Test
@Serializable
private data class TalkConfigContractFixture(
@SerialName("selectionCases") val selectionCases: List<SelectionCase>,
@SerialName("timeoutCases") val timeoutCases: List<TimeoutCase>,
) {
@Serializable
data class SelectionCase(
val id: String,
val defaultProvider: String,
val payloadValid: Boolean,
val expectedSelection: ExpectedSelection? = null,
val talk: JsonObject,
)
@Serializable
data class ExpectedSelection(
val provider: String,
val normalizedPayload: Boolean,
val voiceId: String? = null,
val apiKey: String? = null,
)
@Serializable
data class TimeoutCase(
val id: String,
val fallback: Long,
val expectedTimeoutMs: Long,
val talk: JsonObject,
)
}
class TalkModeConfigContractTest {
private val json = Json { ignoreUnknownKeys = true }
@Test
fun selectionFixtures() {
for (fixture in loadFixtures().selectionCases) {
val selection = TalkModeGatewayConfigParser.selectTalkProviderConfig(fixture.talk)
val expected = fixture.expectedSelection
if (expected == null) {
assertNull(fixture.id, selection)
continue
}
assertNotNull(fixture.id, selection)
assertEquals(fixture.id, expected.provider, selection?.provider)
assertEquals(fixture.id, expected.normalizedPayload, selection?.normalizedPayload)
assertEquals(
fixture.id,
expected.voiceId,
(selection?.config?.get("voiceId") as? JsonPrimitive)?.content,
)
assertEquals(
fixture.id,
expected.apiKey,
(selection?.config?.get("apiKey") as? JsonPrimitive)?.content,
)
assertEquals(fixture.id, true, fixture.payloadValid)
}
}
@Test
fun timeoutFixtures() {
for (fixture in loadFixtures().timeoutCases) {
val timeout = TalkModeGatewayConfigParser.resolvedSilenceTimeoutMs(fixture.talk)
assertEquals(fixture.id, fixture.expectedTimeoutMs, timeout)
assertEquals(fixture.id, TalkDefaults.defaultSilenceTimeoutMs, fixture.fallback)
}
}
private fun loadFixtures(): TalkConfigContractFixture {
val fixturePath = findFixtureFile()
return json.decodeFromString(File(fixturePath).readText())
}
private fun findFixtureFile(): String {
val startDir = System.getProperty("user.dir") ?: error("user.dir unavailable")
var current = File(startDir).absoluteFile
while (true) {
val candidate = File(current, "test-fixtures/talk-config-contract.json")
if (candidate.exists()) {
return candidate.absolutePath
}
current = current.parentFile ?: break
}
error("talk-config-contract.json not found from $startDir")
}
}

View File

@@ -2,135 +2,37 @@ package ai.openclaw.app.voice
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.put
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.Test
class TalkModeConfigParsingTest {
private val json = Json { ignoreUnknownKeys = true }
@Test
fun prefersCanonicalResolvedTalkProviderPayload() {
val talk =
fun readsMainSessionKeyAndInterruptFlag() {
val config =
json.parseToJsonElement(
"""
{
"resolved": {
"provider": "elevenlabs",
"config": {
"voiceId": "voice-resolved"
}
"talk": {
"interruptOnSpeech": true,
"silenceTimeoutMs": 1800
},
"provider": "elevenlabs",
"providers": {
"elevenlabs": {
"voiceId": "voice-normalized"
}
"session": {
"mainKey": "voice-main"
}
}
""".trimIndent(),
)
.jsonObject
val selection = TalkModeGatewayConfigParser.selectTalkProviderConfig(talk)
assertNotNull(selection)
assertEquals("elevenlabs", selection?.provider)
assertTrue(selection?.normalizedPayload == true)
assertEquals("voice-resolved", selection?.config?.get("voiceId")?.jsonPrimitive?.content)
}
val parsed = TalkModeGatewayConfigParser.parse(config)
@Test
fun prefersNormalizedTalkProviderPayload() {
val talk =
json.parseToJsonElement(
"""
{
"provider": "elevenlabs",
"providers": {
"elevenlabs": {
"voiceId": "voice-normalized"
}
},
"voiceId": "voice-legacy"
}
""".trimIndent(),
)
.jsonObject
val selection = TalkModeGatewayConfigParser.selectTalkProviderConfig(talk)
assertEquals(null, selection)
}
@Test
fun rejectsNormalizedTalkProviderPayloadWhenProviderMissingFromProviders() {
val talk =
json.parseToJsonElement(
"""
{
"provider": "acme",
"providers": {
"elevenlabs": {
"voiceId": "voice-normalized"
}
}
}
""".trimIndent(),
)
.jsonObject
val selection = TalkModeGatewayConfigParser.selectTalkProviderConfig(talk)
assertEquals(null, selection)
}
@Test
fun rejectsNormalizedTalkProviderPayloadWhenProviderIsAmbiguous() {
val talk =
json.parseToJsonElement(
"""
{
"providers": {
"acme": {
"voiceId": "voice-acme"
},
"elevenlabs": {
"voiceId": "voice-normalized"
}
}
}
""".trimIndent(),
)
.jsonObject
val selection = TalkModeGatewayConfigParser.selectTalkProviderConfig(talk)
assertEquals(null, selection)
}
@Test
fun fallsBackToLegacyTalkFieldsWhenNormalizedPayloadMissing() {
val legacyApiKey = "legacy-key" // pragma: allowlist secret
val talk =
buildJsonObject {
put("voiceId", "voice-legacy")
put("apiKey", legacyApiKey) // pragma: allowlist secret
}
val selection = TalkModeGatewayConfigParser.selectTalkProviderConfig(talk)
assertNotNull(selection)
assertEquals("elevenlabs", selection?.provider)
assertTrue(selection?.normalizedPayload == false)
assertEquals("voice-legacy", selection?.config?.get("voiceId")?.jsonPrimitive?.content)
assertEquals("legacy-key", selection?.config?.get("apiKey")?.jsonPrimitive?.content)
}
@Test
fun readsConfiguredSilenceTimeoutMs() {
val talk = buildJsonObject { put("silenceTimeoutMs", 1500) }
assertEquals(1500L, TalkModeGatewayConfigParser.resolvedSilenceTimeoutMs(talk))
assertEquals("voice-main", parsed.mainSessionKey)
assertEquals(true, parsed.interruptOnSpeech)
assertEquals(1800L, parsed.silenceTimeoutMs)
}
@Test

View File

@@ -1,92 +0,0 @@
package ai.openclaw.app.voice
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Test
class TalkModeVoiceResolverTest {
@Test
fun resolvesVoiceAliasCaseInsensitively() {
val resolved =
TalkModeVoiceResolver.resolveVoiceAlias(
" Clawd ",
mapOf("clawd" to "voice-123"),
)
assertEquals("voice-123", resolved)
}
@Test
fun acceptsDirectVoiceIds() {
val resolved = TalkModeVoiceResolver.resolveVoiceAlias("21m00Tcm4TlvDq8ikWAM", emptyMap())
assertEquals("21m00Tcm4TlvDq8ikWAM", resolved)
}
@Test
fun rejectsUnknownAliases() {
val resolved = TalkModeVoiceResolver.resolveVoiceAlias("nickname", emptyMap())
assertNull(resolved)
}
@Test
fun reusesCachedFallbackVoiceBeforeFetchingCatalog() =
runBlocking {
var fetchCount = 0
val resolved =
TalkModeVoiceResolver.resolveVoiceId(
preferred = null,
fallbackVoiceId = "cached-voice",
defaultVoiceId = null,
currentVoiceId = null,
voiceOverrideActive = false,
listVoices = {
fetchCount += 1
emptyList()
},
)
assertEquals("cached-voice", resolved.voiceId)
assertEquals(0, fetchCount)
}
@Test
fun seedsDefaultVoiceFromCatalogWhenNeeded() =
runBlocking {
val resolved =
TalkModeVoiceResolver.resolveVoiceId(
preferred = null,
fallbackVoiceId = null,
defaultVoiceId = null,
currentVoiceId = null,
voiceOverrideActive = false,
listVoices = { listOf(ElevenLabsVoice("voice-1", "First")) },
)
assertEquals("voice-1", resolved.voiceId)
assertEquals("voice-1", resolved.fallbackVoiceId)
assertEquals("voice-1", resolved.defaultVoiceId)
assertEquals("voice-1", resolved.currentVoiceId)
assertEquals("First", resolved.selectedVoiceName)
}
@Test
fun preservesCurrentVoiceWhenOverrideIsActive() =
runBlocking {
val resolved =
TalkModeVoiceResolver.resolveVoiceId(
preferred = null,
fallbackVoiceId = null,
defaultVoiceId = null,
currentVoiceId = null,
voiceOverrideActive = true,
listVoices = { listOf(ElevenLabsVoice("voice-1", "First")) },
)
assertEquals("voice-1", resolved.voiceId)
assertNull(resolved.currentVoiceId)
}
}

View File

@@ -1,6 +1,6 @@
plugins {
id("com.android.application") version "9.0.1" apply false
id("com.android.test") version "9.0.1" apply false
id("com.android.application") version "9.1.0" apply false
id("com.android.test") version "9.1.0" apply false
id("org.jlleitschuh.gradle.ktlint") version "14.0.1" apply false
id("org.jetbrains.kotlin.plugin.compose") version "2.2.21" apply false
id("org.jetbrains.kotlin.plugin.serialization") version "2.2.21" apply false

View File

@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

View File

@@ -7,7 +7,28 @@ import { fileURLToPath } from "node:url";
const scriptDir = dirname(fileURLToPath(import.meta.url));
const androidDir = join(scriptDir, "..");
const buildGradlePath = join(androidDir, "app", "build.gradle.kts");
const bundlePath = join(androidDir, "app", "build", "outputs", "bundle", "release", "app-release.aab");
const releaseOutputDir = join(androidDir, "build", "release-bundles");
const releaseVariants = [
{
flavorName: "play",
gradleTask: ":app:bundlePlayRelease",
bundlePath: join(androidDir, "app", "build", "outputs", "bundle", "playRelease", "app-play-release.aab"),
},
{
flavorName: "third-party",
gradleTask: ":app:bundleThirdPartyRelease",
bundlePath: join(
androidDir,
"app",
"build",
"outputs",
"bundle",
"thirdPartyRelease",
"app-thirdParty-release.aab",
),
},
] as const;
type VersionState = {
versionName: string;
@@ -88,6 +109,15 @@ async function verifyBundleSignature(path: string): Promise<void> {
await $`jarsigner -verify ${path}`.quiet();
}
async function copyBundle(sourcePath: string, destinationPath: string): Promise<void> {
const sourceFile = Bun.file(sourcePath);
if (!(await sourceFile.exists())) {
throw new Error(`Signed bundle missing at ${sourcePath}`);
}
await Bun.write(destinationPath, sourceFile);
}
async function main() {
const buildGradleFile = Bun.file(buildGradlePath);
const originalText = await buildGradleFile.text();
@@ -102,24 +132,28 @@ async function main() {
console.log(`Android versionCode -> ${nextVersion.versionCode}`);
await Bun.write(buildGradlePath, updatedText);
await $`mkdir -p ${releaseOutputDir}`;
try {
await $`./gradlew :app:bundleRelease`.cwd(androidDir);
await $`./gradlew ${releaseVariants[0].gradleTask} ${releaseVariants[1].gradleTask}`.cwd(androidDir);
} catch (error) {
await Bun.write(buildGradlePath, originalText);
throw error;
}
const bundleFile = Bun.file(bundlePath);
if (!(await bundleFile.exists())) {
throw new Error(`Signed bundle missing at ${bundlePath}`);
for (const variant of releaseVariants) {
const outputPath = join(
releaseOutputDir,
`openclaw-${nextVersion.versionName}-${variant.flavorName}-release.aab`,
);
await copyBundle(variant.bundlePath, outputPath);
await verifyBundleSignature(outputPath);
const hash = await sha256Hex(outputPath);
console.log(`Signed AAB (${variant.flavorName}): ${outputPath}`);
console.log(`SHA-256 (${variant.flavorName}): ${hash}`);
}
await verifyBundleSignature(bundlePath);
const hash = await sha256Hex(bundlePath);
console.log(`Signed AAB: ${bundlePath}`);
console.log(`SHA-256: ${hash}`);
}
await main();

View File

@@ -0,0 +1,430 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
ANDROID_DIR="$(cd -- "$SCRIPT_DIR/.." && pwd)"
RESULTS_DIR="$ANDROID_DIR/benchmark/results"
PACKAGE="ai.openclaw.app"
ACTIVITY=".MainActivity"
DEVICE_SERIAL=""
INSTALL_APP="1"
LAUNCH_RUNS="4"
SCREEN_LOOPS="6"
CHAT_LOOPS="8"
POLL_ATTEMPTS="40"
POLL_INTERVAL_SECONDS="0.3"
SCREEN_MODE="transition"
CHAT_MODE="session-switch"
usage() {
cat <<'EOF'
Usage:
./scripts/perf-online-benchmark.sh [options]
Measures the fully-online Android app path on a connected device/emulator.
Assumes the app can reach a live gateway and will show "Connected" in the UI.
Options:
--device <serial> adb device serial
--package <pkg> package name (default: ai.openclaw.app)
--activity <activity> launch activity (default: .MainActivity)
--skip-install skip :app:installDebug
--launch-runs <n> launch-to-connected runs (default: 4)
--screen-loops <n> screen benchmark loops (default: 6)
--chat-loops <n> chat benchmark loops (default: 8)
--screen-mode <mode> transition | scroll (default: transition)
--chat-mode <mode> session-switch | scroll (default: session-switch)
-h, --help show help
EOF
}
while [[ $# -gt 0 ]]; do
case "$1" in
--device)
DEVICE_SERIAL="${2:-}"
shift 2
;;
--package)
PACKAGE="${2:-}"
shift 2
;;
--activity)
ACTIVITY="${2:-}"
shift 2
;;
--skip-install)
INSTALL_APP="0"
shift
;;
--launch-runs)
LAUNCH_RUNS="${2:-}"
shift 2
;;
--screen-loops)
SCREEN_LOOPS="${2:-}"
shift 2
;;
--chat-loops)
CHAT_LOOPS="${2:-}"
shift 2
;;
--screen-mode)
SCREEN_MODE="${2:-}"
shift 2
;;
--chat-mode)
CHAT_MODE="${2:-}"
shift 2
;;
-h|--help)
usage
exit 0
;;
*)
echo "Unknown arg: $1" >&2
usage >&2
exit 2
;;
esac
done
require_cmd() {
if ! command -v "$1" >/dev/null 2>&1; then
echo "$1 required but missing." >&2
exit 1
fi
}
require_cmd adb
require_cmd awk
require_cmd rg
require_cmd node
adb_cmd() {
if [[ -n "$DEVICE_SERIAL" ]]; then
adb -s "$DEVICE_SERIAL" "$@"
else
adb "$@"
fi
}
device_count="$(adb devices | awk 'NR>1 && $2=="device" {c+=1} END {print c+0}')"
if [[ -z "$DEVICE_SERIAL" && "$device_count" -lt 1 ]]; then
echo "No connected Android device (adb state=device)." >&2
exit 1
fi
if [[ -z "$DEVICE_SERIAL" && "$device_count" -gt 1 ]]; then
echo "Multiple adb devices found. Pass --device <serial>." >&2
adb devices -l >&2
exit 1
fi
if [[ "$SCREEN_MODE" != "transition" && "$SCREEN_MODE" != "scroll" ]]; then
echo "Unsupported --screen-mode: $SCREEN_MODE" >&2
exit 2
fi
if [[ "$CHAT_MODE" != "session-switch" && "$CHAT_MODE" != "scroll" ]]; then
echo "Unsupported --chat-mode: $CHAT_MODE" >&2
exit 2
fi
mkdir -p "$RESULTS_DIR"
timestamp="$(date +%Y%m%d-%H%M%S)"
run_dir="$RESULTS_DIR/online-$timestamp"
mkdir -p "$run_dir"
cleanup() {
rm -f "$run_dir"/ui-*.xml
}
trap cleanup EXIT
if [[ "$INSTALL_APP" == "1" ]]; then
(
cd "$ANDROID_DIR"
./gradlew :app:installDebug --console=plain >"$run_dir/install.log" 2>&1
)
fi
read -r display_width display_height <<<"$(
adb_cmd shell wm size \
| awk '/Physical size:/ { split($3, dims, "x"); print dims[1], dims[2]; exit }'
)"
if [[ -z "${display_width:-}" || -z "${display_height:-}" ]]; then
echo "Failed to read device display size." >&2
exit 1
fi
pct_of() {
local total="$1"
local pct="$2"
awk -v total="$total" -v pct="$pct" 'BEGIN { printf "%d", total * pct }'
}
tab_connect_x="$(pct_of "$display_width" "0.11")"
tab_chat_x="$(pct_of "$display_width" "0.31")"
tab_screen_x="$(pct_of "$display_width" "0.69")"
tab_y="$(pct_of "$display_height" "0.93")"
chat_session_y="$(pct_of "$display_height" "0.13")"
chat_session_left_x="$(pct_of "$display_width" "0.16")"
chat_session_right_x="$(pct_of "$display_width" "0.85")"
center_x="$(pct_of "$display_width" "0.50")"
screen_swipe_top_y="$(pct_of "$display_height" "0.27")"
screen_swipe_mid_y="$(pct_of "$display_height" "0.38")"
screen_swipe_low_y="$(pct_of "$display_height" "0.75")"
screen_swipe_bottom_y="$(pct_of "$display_height" "0.77")"
chat_swipe_top_y="$(pct_of "$display_height" "0.29")"
chat_swipe_mid_y="$(pct_of "$display_height" "0.38")"
chat_swipe_bottom_y="$(pct_of "$display_height" "0.71")"
dump_ui() {
local name="$1"
local file="$run_dir/ui-$name.xml"
adb_cmd shell uiautomator dump "/sdcard/$name.xml" >/dev/null 2>&1
adb_cmd shell cat "/sdcard/$name.xml" >"$file"
printf '%s\n' "$file"
}
ui_has() {
local pattern="$1"
local name="$2"
local file
file="$(dump_ui "$name")"
rg -q "$pattern" "$file"
}
wait_for_pattern() {
local pattern="$1"
local prefix="$2"
for attempt in $(seq 1 "$POLL_ATTEMPTS"); do
if ui_has "$pattern" "$prefix-$attempt"; then
return 0
fi
sleep "$POLL_INTERVAL_SECONDS"
done
return 1
}
ensure_connected() {
if ! wait_for_pattern 'text="Connected"' "connected"; then
echo "App never reached visible Connected state." >&2
exit 1
fi
}
ensure_screen_online() {
adb_cmd shell input tap "$tab_screen_x" "$tab_y" >/dev/null
sleep 2
if ! ui_has 'android\.webkit\.WebView' "screen"; then
echo "Screen benchmark expected a live WebView." >&2
exit 1
fi
}
ensure_chat_online() {
adb_cmd shell input tap "$tab_chat_x" "$tab_y" >/dev/null
sleep 2
if ! ui_has 'Type a message' "chat"; then
echo "Chat benchmark expected the live chat composer." >&2
exit 1
fi
}
capture_mem() {
local file="$1"
adb_cmd shell dumpsys meminfo "$PACKAGE" >"$file"
}
start_cpu_sampler() {
local file="$1"
local samples="$2"
: >"$file"
(
for _ in $(seq 1 "$samples"); do
adb_cmd shell top -b -n 1 \
| awk -v pkg="$PACKAGE" '$NF==pkg { print $9 }' >>"$file"
sleep 0.5
done
) &
CPU_SAMPLER_PID="$!"
}
summarize_cpu() {
local file="$1"
local prefix="$2"
local avg max median count
avg="$(awk '{sum+=$1; n++} END {if(n) printf "%.1f", sum/n; else print 0}' "$file")"
max="$(sort -n "$file" | tail -n 1)"
median="$(
sort -n "$file" \
| awk '{a[NR]=$1} END { if (NR==0) { print 0 } else if (NR%2==1) { printf "%.1f", a[(NR+1)/2] } else { printf "%.1f", (a[NR/2]+a[NR/2+1])/2 } }'
)"
count="$(wc -l <"$file" | tr -d ' ')"
printf '%s.cpu_avg_pct=%s\n' "$prefix" "$avg" >>"$run_dir/summary.txt"
printf '%s.cpu_median_pct=%s\n' "$prefix" "$median" >>"$run_dir/summary.txt"
printf '%s.cpu_peak_pct=%s\n' "$prefix" "$max" >>"$run_dir/summary.txt"
printf '%s.cpu_count=%s\n' "$prefix" "$count" >>"$run_dir/summary.txt"
}
summarize_mem() {
local file="$1"
local prefix="$2"
awk -v prefix="$prefix" '
/TOTAL PSS:/ { printf "%s.pss_kb=%s\n%s.rss_kb=%s\n", prefix, $3, prefix, $6 }
/Graphics:/ { printf "%s.graphics_kb=%s\n", prefix, $2 }
/WebViews:/ { printf "%s.webviews=%s\n", prefix, $NF }
' "$file" >>"$run_dir/summary.txt"
}
summarize_gfx() {
local file="$1"
local prefix="$2"
awk -v prefix="$prefix" '
/Total frames rendered:/ { printf "%s.frames=%s\n", prefix, $4 }
/Janky frames:/ && $4 ~ /\(/ {
pct=$4
gsub(/[()%]/, "", pct)
printf "%s.janky_frames=%s\n%s.janky_pct=%s\n", prefix, $3, prefix, pct
}
/50th percentile:/ { gsub(/ms/, "", $3); printf "%s.p50_ms=%s\n", prefix, $3 }
/90th percentile:/ { gsub(/ms/, "", $3); printf "%s.p90_ms=%s\n", prefix, $3 }
/95th percentile:/ { gsub(/ms/, "", $3); printf "%s.p95_ms=%s\n", prefix, $3 }
/99th percentile:/ { gsub(/ms/, "", $3); printf "%s.p99_ms=%s\n", prefix, $3 }
' "$file" >>"$run_dir/summary.txt"
}
measure_launch() {
: >"$run_dir/launch-runs.txt"
for run in $(seq 1 "$LAUNCH_RUNS"); do
adb_cmd shell am force-stop "$PACKAGE" >/dev/null
sleep 1
start_ms="$(node -e 'console.log(Date.now())')"
am_out="$(adb_cmd shell am start -W -n "$PACKAGE/$ACTIVITY")"
total_time="$(printf '%s\n' "$am_out" | awk -F: '/TotalTime:/{gsub(/ /, "", $2); print $2}')"
connected_ms="timeout"
for _ in $(seq 1 "$POLL_ATTEMPTS"); do
if ui_has 'text="Connected"' "launch-run-$run"; then
now_ms="$(node -e 'console.log(Date.now())')"
connected_ms="$((now_ms - start_ms))"
break
fi
sleep "$POLL_INTERVAL_SECONDS"
done
printf 'run=%s total_time_ms=%s connected_ms=%s\n' "$run" "${total_time:-na}" "$connected_ms" \
| tee -a "$run_dir/launch-runs.txt"
done
awk -F'[ =]' '
/total_time_ms=[0-9]+/ {
value=$4
sum+=value
count+=1
if (min==0 || value<min) min=value
if (value>max) max=value
}
END {
if (count==0) exit
printf "launch.total_time_avg_ms=%.1f\nlaunch.total_time_min_ms=%d\nlaunch.total_time_max_ms=%d\n", sum/count, min, max
}
' "$run_dir/launch-runs.txt" >>"$run_dir/summary.txt"
awk -F'[ =]' '
/connected_ms=[0-9]+/ {
value=$6
sum+=value
count+=1
if (min==0 || value<min) min=value
if (value>max) max=value
}
END {
if (count==0) exit
printf "launch.connected_avg_ms=%.1f\nlaunch.connected_min_ms=%d\nlaunch.connected_max_ms=%d\n", sum/count, min, max
}
' "$run_dir/launch-runs.txt" >>"$run_dir/summary.txt"
}
run_screen_benchmark() {
ensure_screen_online
capture_mem "$run_dir/screen-mem-before.txt"
adb_cmd shell dumpsys gfxinfo "$PACKAGE" reset >/dev/null
start_cpu_sampler "$run_dir/screen-cpu.txt" 18
if [[ "$SCREEN_MODE" == "transition" ]]; then
for _ in $(seq 1 "$SCREEN_LOOPS"); do
adb_cmd shell input tap "$tab_screen_x" "$tab_y" >/dev/null
sleep 1.0
adb_cmd shell input tap "$tab_chat_x" "$tab_y" >/dev/null
sleep 0.8
done
else
adb_cmd shell input tap "$tab_screen_x" "$tab_y" >/dev/null
sleep 1.5
for _ in $(seq 1 "$SCREEN_LOOPS"); do
adb_cmd shell input swipe "$center_x" "$screen_swipe_bottom_y" "$center_x" "$screen_swipe_top_y" 250 >/dev/null
sleep 0.35
adb_cmd shell input swipe "$center_x" "$screen_swipe_mid_y" "$center_x" "$screen_swipe_low_y" 250 >/dev/null
sleep 0.35
done
fi
wait "$CPU_SAMPLER_PID"
adb_cmd shell dumpsys gfxinfo "$PACKAGE" >"$run_dir/screen-gfx.txt"
capture_mem "$run_dir/screen-mem-after.txt"
summarize_gfx "$run_dir/screen-gfx.txt" "screen"
summarize_cpu "$run_dir/screen-cpu.txt" "screen"
summarize_mem "$run_dir/screen-mem-before.txt" "screen.before"
summarize_mem "$run_dir/screen-mem-after.txt" "screen.after"
}
run_chat_benchmark() {
ensure_chat_online
capture_mem "$run_dir/chat-mem-before.txt"
adb_cmd shell dumpsys gfxinfo "$PACKAGE" reset >/dev/null
start_cpu_sampler "$run_dir/chat-cpu.txt" 18
if [[ "$CHAT_MODE" == "session-switch" ]]; then
for _ in $(seq 1 "$CHAT_LOOPS"); do
adb_cmd shell input tap "$chat_session_left_x" "$chat_session_y" >/dev/null
sleep 0.8
adb_cmd shell input tap "$chat_session_right_x" "$chat_session_y" >/dev/null
sleep 0.8
done
else
for _ in $(seq 1 "$CHAT_LOOPS"); do
adb_cmd shell input swipe "$center_x" "$chat_swipe_bottom_y" "$center_x" "$chat_swipe_top_y" 250 >/dev/null
sleep 0.35
adb_cmd shell input swipe "$center_x" "$chat_swipe_mid_y" "$center_x" "$chat_swipe_bottom_y" 250 >/dev/null
sleep 0.35
done
fi
wait "$CPU_SAMPLER_PID"
adb_cmd shell dumpsys gfxinfo "$PACKAGE" >"$run_dir/chat-gfx.txt"
capture_mem "$run_dir/chat-mem-after.txt"
summarize_gfx "$run_dir/chat-gfx.txt" "chat"
summarize_cpu "$run_dir/chat-cpu.txt" "chat"
summarize_mem "$run_dir/chat-mem-before.txt" "chat.before"
summarize_mem "$run_dir/chat-mem-after.txt" "chat.after"
}
printf 'device.serial=%s\n' "${DEVICE_SERIAL:-default}" >"$run_dir/summary.txt"
printf 'device.display=%sx%s\n' "$display_width" "$display_height" >>"$run_dir/summary.txt"
printf 'config.launch_runs=%s\n' "$LAUNCH_RUNS" >>"$run_dir/summary.txt"
printf 'config.screen_loops=%s\n' "$SCREEN_LOOPS" >>"$run_dir/summary.txt"
printf 'config.chat_loops=%s\n' "$CHAT_LOOPS" >>"$run_dir/summary.txt"
printf 'config.screen_mode=%s\n' "$SCREEN_MODE" >>"$run_dir/summary.txt"
printf 'config.chat_mode=%s\n' "$CHAT_MODE" >>"$run_dir/summary.txt"
ensure_connected
measure_launch
ensure_connected
run_screen_benchmark
ensure_connected
run_chat_benchmark
printf 'results_dir=%s\n' "$run_dir"
cat "$run_dir/summary.txt"

View File

@@ -1,8 +1,8 @@
// Shared iOS version defaults.
// Generated overrides live in build/Version.xcconfig (git-ignored).
OPENCLAW_GATEWAY_VERSION = 2026.3.14
OPENCLAW_MARKETING_VERSION = 2026.3.14
OPENCLAW_BUILD_VERSION = 202603140
OPENCLAW_GATEWAY_VERSION = 2026.3.25
OPENCLAW_MARKETING_VERSION = 2026.3.25
OPENCLAW_BUILD_VERSION = 202603250
#include? "../build/Version.xcconfig"

View File

@@ -65,9 +65,9 @@ Release behavior:
- Beta release also switches the app to `OpenClawPushTransport=relay`, `OpenClawPushDistribution=official`, and `OpenClawPushAPNsEnvironment=production`.
- The beta flow does not modify `apps/ios/.local-signing.xcconfig` or `apps/ios/LocalSigning.xcconfig`.
- Root `package.json.version` is the only version source for iOS.
- A root version like `2026.3.13-beta.1` becomes:
- `CFBundleShortVersionString = 2026.3.13`
- `CFBundleVersion = next TestFlight build number for 2026.3.13`
- A root version like `2026.3.22-beta.1` becomes:
- `CFBundleShortVersionString = 2026.3.22`
- `CFBundleVersion = next TestFlight build number for 2026.3.22`
Required env for beta builds:

View File

@@ -46,6 +46,13 @@ struct IOSGatewayChatTransport: OpenClawChatTransport, Sendable {
_ = try await self.gateway.request(method: "sessions.reset", paramsJSON: json, timeoutSeconds: 10)
}
func compactSession(sessionKey: String) async throws {
struct Params: Codable { var key: String }
let data = try JSONEncoder().encode(Params(key: sessionKey))
let json = String(data: data, encoding: .utf8)
_ = try await self.gateway.request(method: "sessions.compact", paramsJSON: json, timeoutSeconds: 10)
}
func requestHistory(sessionKey: String) async throws -> OpenClawChatHistoryPayload {
struct Params: Codable { var sessionKey: String }
let data = try JSONEncoder().encode(Params(sessionKey: sessionKey))

View File

@@ -174,7 +174,12 @@ final class GatewayConnectionController {
let stored = GatewayTLSStore.loadFingerprint(stableID: stableID)
if resolvedUseTLS, stored == nil {
guard let url = self.buildGatewayURL(host: host, port: resolvedPort, useTLS: true) else { return }
guard let fp = await self.probeTLSFingerprint(url: url) else { return }
guard let fp = await self.probeTLSFingerprint(url: url) else {
self.appModel?.gatewayStatusText =
"TLS handshake failed for \(host):\(resolvedPort). "
+ "Remote gateways must use HTTPS/WSS."
return
}
self.pendingTrustConnect = (url: url, stableID: stableID, isManual: true)
self.pendingTrustPrompt = TrustPrompt(
stableID: stableID,

View File

@@ -607,7 +607,7 @@ struct OnboardingWizardView: View {
private var authStep: some View {
Group {
Section("Authentication") {
TextField("Gateway Auth Token", text: self.$gatewayToken)
SecureField("Gateway Auth Token", text: self.$gatewayToken)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
SecureField("Gateway Password", text: self.$gatewayPassword)
@@ -724,6 +724,12 @@ struct OnboardingWizardView: View {
TextField("Discovery Domain (optional)", text: self.$discoveryDomain)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
if self.selectedMode == .remoteDomain {
SecureField("Gateway Auth Token", text: self.$gatewayToken)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
SecureField("Gateway Password", text: self.$gatewayPassword)
}
self.manualConnectButton
}
}

View File

@@ -99,7 +99,7 @@ def normalize_release_version(raw_value)
version = raw_value.to_s.strip.sub(/\Av/, "")
UI.user_error!("Missing root package.json version.") unless env_present?(version)
unless version.match?(/\A\d+\.\d+\.\d+(?:[.-]?beta[.-]\d+)?\z/i)
UI.user_error!("Invalid package.json version '#{raw_value}'. Expected 2026.3.13 or 2026.3.13-beta.1.")
UI.user_error!("Invalid package.json version '#{raw_value}'. Expected YYYY.M.D or YYYY.M.D-beta.N.")
end
version

View File

@@ -783,7 +783,7 @@ extension AppState {
remoteToken: String,
remoteTokenDirty: Bool) -> [String: Any]
{
Self.updatedRemoteGatewayConfig(
self.updatedRemoteGatewayConfig(
current: current,
transport: transport,
remoteUrl: remoteUrl,
@@ -804,7 +804,7 @@ extension AppState {
remoteToken: String,
remoteTokenDirty: Bool) -> [String: Any]
{
Self.syncedGatewayRoot(
self.syncedGatewayRoot(
currentRoot: currentRoot,
connectionMode: connectionMode,
remoteTransport: remoteTransport,

View File

@@ -8,8 +8,8 @@ final class CanvasA2UIActionMessageHandler: NSObject, WKScriptMessageHandler {
static let messageName = "openclawCanvasA2UIAction"
static let allMessageNames = [messageName]
// Compatibility helper for debug/test shims. Runtime dispatch remains
// limited to in-app canvas schemes in `didReceive`.
/// Compatibility helper for debug/test shims. Runtime dispatch remains
/// limited to in-app canvas schemes in `didReceive`.
static func isLocalNetworkCanvasURL(_ url: URL) -> Bool {
guard let scheme = url.scheme?.lowercased(), scheme == "http" || scheme == "https" else {
return false

View File

@@ -81,22 +81,23 @@ final class CanvasSchemeHandler: NSObject, WKURLSchemeHandler {
return self.html("Not Found", title: "Canvas: 404")
}
// Directory traversal guard: served files must live under the session root.
let standardizedRoot = sessionRoot.standardizedFileURL
let standardizedFile = fileURL.standardizedFileURL
guard standardizedFile.path.hasPrefix(standardizedRoot.path) else {
// Resolve symlinks before enforcing the session-root boundary so links inside
// the canvas tree cannot escape to arbitrary host files.
let resolvedRoot = sessionRoot.resolvingSymlinksInPath().standardizedFileURL
let resolvedFile = fileURL.resolvingSymlinksInPath().standardizedFileURL
guard self.isFileURL(resolvedFile, withinDirectory: resolvedRoot) else {
return self.html("Forbidden", title: "Canvas: 403")
}
do {
let data = try Data(contentsOf: standardizedFile)
let mime = CanvasScheme.mimeType(forExtension: standardizedFile.pathExtension)
let servedPath = standardizedFile.path
let data = try Data(contentsOf: resolvedFile)
let mime = CanvasScheme.mimeType(forExtension: resolvedFile.pathExtension)
let servedPath = resolvedFile.path
canvasLogger.debug(
"served \(session, privacy: .public)/\(path, privacy: .public) -> \(servedPath, privacy: .public)")
return CanvasResponse(mime: mime, data: data)
} catch {
let failedPath = standardizedFile.path
let failedPath = resolvedFile.path
let errorText = error.localizedDescription
canvasLogger
.error(
@@ -145,6 +146,11 @@ final class CanvasSchemeHandler: NSObject, WKURLSchemeHandler {
return nil
}
private func isFileURL(_ fileURL: URL, withinDirectory rootURL: URL) -> Bool {
let rootPath = rootURL.path.hasSuffix("/") ? rootURL.path : rootURL.path + "/"
return fileURL.path == rootURL.path || fileURL.path.hasPrefix(rootPath)
}
private func html(_ body: String, title: String = "Canvas") -> CanvasResponse {
let html = """
<!doctype html>

View File

@@ -57,8 +57,7 @@ final class CanvasWindowController: NSWindowController, WKNavigationDelegate, NS
let allowedSchemesJSON = (
try? String(
data: JSONSerialization.data(withJSONObject: CanvasScheme.allSchemes),
encoding: .utf8)
) ?? "[]"
encoding: .utf8)) ?? "[]"
let bridgeScript = """
(() => {
try {

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